diff --git a/MAA.sln.DotSettings b/MAA.sln.DotSettings index c6eb074c77..cb91463023 100644 --- a/MAA.sln.DotSettings +++ b/MAA.sln.DotSettings @@ -1,4 +1,4 @@ - + EN KR JP @@ -166,5 +166,6 @@ True True True + True True True diff --git a/src/MaaWpfGui/Constants/AchievementIds.cs b/src/MaaWpfGui/Constants/AchievementIds.cs new file mode 100644 index 0000000000..b4dc43f71a --- /dev/null +++ b/src/MaaWpfGui/Constants/AchievementIds.cs @@ -0,0 +1,142 @@ +// +// MaaWpfGui - A part of the MaaCoreArknights project +// Copyright (C) 2021 MistEO and Contributors +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License v3.0 only as published by +// the Free Software Foundation, either version 3 of the License, or +// any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY +// + +namespace MaaWpfGui.Constants +{ + public static class AchievementIds + { + #region 基础使用类 + + public const string SanitySpenderGroup = "SanitySpender"; + public const string SanitySpender1 = "SanitySpender1"; + public const string SanitySpender2 = "SanitySpender2"; + public const string SanitySpender3 = "SanitySpender3"; + + public const string SanitySaverGroup = "SanitySaverGroup"; + public const string SanitySaver1 = "SanitySaver1"; + public const string SanitySaver2 = "SanitySaver2"; + public const string SanitySaver3 = "SanitySaver3"; + + public const string RoguelikeGamePassGroup = "RoguelikeGamePass"; + public const string RoguelikeGamePass1 = "RoguelikeGamePass1"; + public const string RoguelikeGamePass2 = "RoguelikeGamePass2"; + public const string RoguelikeGamePass3 = "RoguelikeGamePass3"; + public const string RoguelikeRetreat = "RoguelikeRetreat"; + public const string RoguelikeGoldMax = "RoguelikeGoldMax"; + public const string RoguelikeN04 = "RoguelikeN04"; + public const string RoguelikeN08 = "RoguelikeN08"; + public const string RoguelikeN12 = "RoguelikeN12"; + public const string RoguelikeN15 = "RoguelikeN15"; + + public const string FirstLaunch = "FirstLaunch"; + public const string SanityExpire = "SanityExpire"; + public const string OverLimitAgent = "OverLimitAgent"; + + #endregion + + #region 功能探索类 + + public const string ScheduleMasterGroup = "ScheduleMaster"; + public const string ScheduleMaster1 = "ScheduleMaster1"; + public const string ScheduleMaster2 = "ScheduleMaster2"; + + public const string MirrorChyanFirstUse = "MirrorChyanFirstUse"; + public const string MirrorChyanCdkError = "MirrorChyanCdkError"; + + public const string MosquitoLeg = "MosquitoLeg"; + + public const string Pioneer1 = "Pioneer1"; + public const string Pioneer2 = "Pioneer2"; + public const string Pioneer3 = "Pioneer3"; + + public const string RealGacha = "RealGacha"; + public const string PeekScreen = "PeekScreen"; + public const string CustomizationMaster = "CustomizationMaster"; + + #endregion + + #region 代理关卡类 + + public const string MapOutdated = "MapOutdated"; + + public const string UseCopilotGroup = "UseCopilot"; + public const string UseCopilot1 = "UseCopilot1"; + public const string UseCopilot2 = "UseCopilot2"; + public const string UseCopilot3 = "UseCopilot3"; + public const string CopilotLikeGroup = "CopilotLikeGiven"; + public const string CopilotLikeGiven1 = "CopilotLikeGiven1"; + public const string CopilotLikeGiven2 = "CopilotLikeGiven2"; + public const string CopilotLikeGiven3 = "CopilotLikeGiven3"; + public const string CopilotError = "CopilotError"; + + public const string Irreplaceable = "Irreplaceable"; + + #endregion + + #region 搞笑/梗成就 + + public const string QuickCloser = "QuickCloser"; + public const string TacticalRetreat = "TacticalRetreat"; + public const string Martian = "Martian"; + + public const string RecruitNoSixStar = "RecruitNoSixStar"; + public const string RecruitNoSixStarStreak = "RecruitNoSixStarStreak"; + + public const string SnapshotChallenge1 = "SnapshotChallenge1"; // 999 + public const string SnapshotChallenge2 = "SnapshotChallenge2"; // 800 + public const string SnapshotChallenge3 = "SnapshotChallenge3"; // 400 + public const string SnapshotChallenge4 = "SnapshotChallenge4"; // 100 + public const string SnapshotChallenge5 = "SnapshotChallenge6"; // 10 + public const string SnapshotChallenge6 = "SnapshotChallenge6"; // 5 + + #endregion + + #region BUG 相关 + + public const string CongratulationError = "CongratulationError"; + public const string UnexpectedCrash = "UnexpectedCrash"; + public const string ProblemFeedback = "ProblemFeedback"; + public const string CdnTorture = "CdnTorture"; + + #endregion + + #region 用户行为习惯类 + + public const string MissionStartCount = "MissionStartCount"; + public const string MissionStartCountCustomDataKey = "LastStartDate"; + public const string LongTaskTimeout = "LongTaskTimeout"; + public const string ProxyOnline3Hours = "ProxyOnline3Hours"; + public const string TaskStartCancel = "TaskStartCancel"; + public const string AfkWatcher = "AfkWatcher"; + + public const string UseDailyGroup = "UseDaily"; + public const string UseDailyCustomDataKey = "LastLaunchDate"; + public const string UseDaily1 = "UseDaily1"; // 7 天 + public const string UseDaily2 = "UseDaily2"; // 30 天 + public const string UseDaily3 = "UseDaily3"; // 365 天 + + #endregion + + #region 彩蛋类 + + public const string Rules = "Rules"; + public const string VersionClick = "VersionClick"; + public const string Lucky = "Lucky"; + + public const string AprilFools = "AprilFools"; + public const string MidnightLaunch = "MidnightLaunch"; + public const string LunarNewYear = "LunarNewYear"; + + #endregion + } +} diff --git a/src/MaaWpfGui/Constants/Enums.cs b/src/MaaWpfGui/Constants/Enums.cs index e203726a81..ca72d16e69 100644 --- a/src/MaaWpfGui/Constants/Enums.cs +++ b/src/MaaWpfGui/Constants/Enums.cs @@ -72,5 +72,48 @@ namespace MaaWpfGui.Constants /// Undivided = 1, } + + public enum AchievementCategory + { + /// + /// 基础使用类 + /// + BasicUsage, + + /// + /// 功能探索类 + /// + FeatureExploration, + + /// + /// 自动战斗 + /// + AutoBattle, + + /// + /// 搞笑/梗类成就 + /// + Humor, + + /// + /// BUG 相关 + /// + BugRelated, + + /// + /// 用户行为 习惯 时长类 + /// + Behavior, + + /// + /// 彩蛋类 + /// + EasterEgg, + + /// + /// 超级稀有! + /// + Rare, + } } } diff --git a/src/MaaWpfGui/Helper/AchievementTrackerHelper.cs b/src/MaaWpfGui/Helper/AchievementTrackerHelper.cs new file mode 100644 index 0000000000..35a273a950 --- /dev/null +++ b/src/MaaWpfGui/Helper/AchievementTrackerHelper.cs @@ -0,0 +1,547 @@ +// +// MaaWpfGui - A part of the MaaCoreArknights project +// Copyright (C) 2021 MistEO and Contributors +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License v3.0 only as published by +// the Free Software Foundation, either version 3 of the License, or +// any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY +// + +#nullable enable + +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; +using System.Windows.Input; +using HandyControl.Controls; +using HandyControl.Data; +using HandyControl.Tools.Command; +using MaaWpfGui.Constants; +using MaaWpfGui.Extensions; +using MaaWpfGui.Models; +using Newtonsoft.Json.Linq; +using Stylet; +using static MaaWpfGui.Constants.Enums; + +namespace MaaWpfGui.Helper +{ + public class AchievementTrackerHelper : PropertyChangedBase + { + public AchievementTrackerHelper() + { + InitializeAchievements(); + Load(); + Sort(); + Instances.MainWindowManager.WindowRestored += (_, _) => + { + TryShowPendingGrowls(); + }; + + SearchCmd = new RelayCommand(Search); + } + + public static AchievementTrackerHelper Instance { get; } = new(); + + private Dictionary _achievements = []; + + public Dictionary Achievements + { + get => _achievements; + set + { + SetAndNotify(ref _achievements, value); + NotifyOfPropertyChange(nameof(UnlockedCount)); + NotifyOfPropertyChange(nameof(VisibleAchievements)); + } + } + + // 已解锁及非隐藏的未解锁成就 + public Dictionary VisibleAchievements + { + get => Achievements + .Where(kv => kv.Value.IsVisibleInSearch && (kv.Value.IsUnlocked || !kv.Value.IsHidden)) + .ToDictionary(kv => kv.Key, kv => kv.Value); + } + + public ICommand SearchCmd { get; } + + public void Search(string text = "") + { + text = text.Trim(); + if (string.IsNullOrWhiteSpace(text)) + { + foreach (var achievement in Achievements) + { + achievement.Value.IsVisibleInSearch = true; + } + } + else + { + foreach (var (_, value) in Achievements) + { + value.IsVisibleInSearch = value.Title.Contains(text) || value.Description.Contains(text) || value.Conditions.Contains(text); + } + } + + NotifyOfPropertyChange(nameof(VisibleAchievements)); + } + + public int UnlockedCount => Achievements.Count(a => a.Value.IsUnlocked); + + private void Load() + { + var loadAchievements = JsonDataHelper.Get("Achievement", new Dictionary()) ?? []; + + // 从中读取 IsUnlocked UnlockedTime Progress CustomData 等有关成就状态的信息,覆盖 _achievements 里现有的默认值 + foreach (var (id, saved) in loadAchievements) + { + if (!_achievements.TryGetValue(id, out var achievement)) + { + // EDIT:存个屁,不存了 + // 有可能是高版本移到低版本来的,虽然有点抽象,但存一下 + // RegisterAchievement(saved); + continue; + } + + achievement.IsUnlocked = saved.IsUnlocked; + achievement.UnlockedTime = saved.UnlockedTime; + achievement.Progress = saved.Progress; + achievement.CustomData = saved.CustomData; + } + } + + private bool _allowSave = true; + + public void Save() + { + if (!_allowSave) + { + return; + } + + Sort(); + + JsonDataHelper.Set("Achievement", _achievements); + } + + private void Sort() + { + Achievements = _achievements + .OrderByDescending(kv => kv.Value.IsUnlocked) // 已解锁优先 + .ThenBy(kv => !kv.Value.IsNewUnlock) // 新解锁的排前面 + .ThenBy(kv => kv.Value.Category) // 按类别分组 + // .ThenBy(kv => kv.Value.IsHidden) // 隐藏排后面 + .ThenBy(kv => kv.Value.Id) // 最后按 Id + .ToDictionary(kv => kv.Key, kv => kv.Value); + } + + public void RegisterAchievement(Achievement achievement) + { + if (!_achievements.TryAdd(achievement.Id, achievement)) + { + return; + } + + // 尝试从相同组的成就中找最大已达成进度 + var similarProgress = _achievements.Values + .Where(a => a.Id != achievement.Id && a.Id.StartsWith(GetGroupPrefix(achievement.Id))) + .Select(a => a.Progress) + .DefaultIfEmpty(0) + .Max(); + + // 如果新成就进度是0,且存在类似成就的进度,就追溯赋值 + if (achievement.Progress == 0 && similarProgress > 0) + { + achievement.Progress = similarProgress; + CheckProgressUnlock(achievement); + } + } + + private static string GetGroupPrefix(string id) + { + // 例如 SanitySpender1 => SanitySpender + return new(id.TakeWhile(char.IsLetter).ToArray()); + } + + public void Unlock(string id, bool staysOpen = true) + { + if (!_achievements.TryGetValue(id, out var achievement) || achievement.IsUnlocked) + { + return; + } + + achievement.IsUnlocked = true; + achievement.UnlockedTime = DateTime.UtcNow; + achievement.IsNewUnlock = true; + Save(); + + var growlInfo = new GrowlInfo + { + IsCustom = true, + Message = $"{LocalizationHelper.GetString("AchievementCelebrate")}: {achievement.Title}\n{achievement.Description}", + StaysOpen = staysOpen, + IconKey = "HangoverGeometry", + IconBrushKey = achievement.MedalBrushKey, + }; + ShowInfo(growlInfo); + } + + public async Task UnlockAll() + { + _allowSave = false; // 禁止保存,避免重复触发 Save + foreach (var achievement in _achievements.Values.Where(a => !a.IsUnlocked)) + { + await Task.Delay(100); + Unlock(achievement.Id, false); + } + + _allowSave = true; + Save(); + } + + public void Lock(string id) + { + if (!_achievements.TryGetValue(id, out var achievement)) + { + return; + } + + achievement.UnlockedTime = null; + achievement.Progress = 0; + achievement.CustomData = null; + achievement.IsNewUnlock = false; + + if (achievement.IsUnlocked) + { + achievement.IsUnlocked = false; + NotifyOfPropertyChange(nameof(UnlockedCount)); + } + + Save(); + } + + public void LockAll() + { + _allowSave = false; // 禁止保存,避免重复触发 Save + foreach (var achievement in _achievements.Values) + { + Lock(achievement.Id); + } + + _allowSave = true; + Save(); + } + + private static readonly List _pending = []; + + public static void ShowInfo(GrowlInfo info) + { + Execute.OnUIThread(() => + { + var win = Instances.MainWindowManager.GetWindowIfVisible(); + if (win == null) + { + _pending.Add(info); + return; + } + + Growl.Info(info); + }); + } + + public static void TryShowPendingGrowls() + { + foreach (var info in _pending) + { + Growl.Info(info); + } + + _pending.Clear(); + } + + private void CheckProgressUnlock(Achievement achievement) + { + if (achievement.IsUnlocked || + achievement.Target == default || + achievement.Progress < achievement.Target) + { + return; + } + + Unlock(achievement.Id); + } + + public void AddProgress(string id, int amount = 1) + { + if (!_achievements.TryGetValue(id, out var achievement)) + { + return; + } + + achievement.Progress += amount; + CheckProgressUnlock(achievement); + Save(); + } + + public void AddProgressToGroup(string groupPrefix, int amount = 1) + { + foreach (var achievement in _achievements.Values.Where(achievement => achievement.Id.StartsWith(groupPrefix))) + { + AddProgress(achievement.Id, amount); + } + } + + public void SetProgress(string id, int progress) + { + if (!_achievements.TryGetValue(id, out var achievement)) + { + return; + } + + achievement.Progress = progress; + CheckProgressUnlock(achievement); + Save(); + } + + public void SetProgressToGroup(string groupPrefix, int progress) + { + foreach (var achievement in _achievements.Values.Where(achievement => achievement.Id.StartsWith(groupPrefix))) + { + SetProgress(achievement.Id, progress); + } + } + + public Dictionary GetAchievementCustomData(string id) + { + if (!_achievements.TryGetValue(id, out var achievement) || achievement.CustomData == null) + { + return []; + } + + return achievement.CustomData; + } + + public JToken? GetAchievementCustomData(string id, string key) + { + if (!_achievements.TryGetValue(id, out var achievement) || achievement.CustomData == null) + { + return null; + } + + achievement.CustomData.TryGetValue(key, out var value); + return value; + } + + public void SetAchievementCustomData(string id, string key, JToken value) + { + if (!_achievements.TryGetValue(id, out var achievement)) + { + return; + } + + achievement.CustomData ??= []; + achievement.CustomData[key] = value; + Save(); + } + + public bool IsUnlocked(string id) => _achievements.TryGetValue(id, out var a) && a.IsUnlocked; + + public Achievement? Get(string id) => _achievements.GetValueOrDefault(id); + + #region 工厂函数 + + private static Achievement BasicUsage(string id, int? target = null, bool isHidden = false, bool isRare = false) + => new() { Id = id, Target = target ?? default, IsHidden = isHidden, Category = AchievementCategory.BasicUsage, IsRare = isRare }; + + private static Achievement FeatureExploration(string id, int? target = null, bool isHidden = false, bool isRare = false) + => new() { Id = id, Target = target ?? default, IsHidden = isHidden, Category = AchievementCategory.FeatureExploration, IsRare = isRare }; + + private static Achievement AutoBattle(string id, int? target = null, bool isHidden = false, bool isRare = false) + => new() { Id = id, Target = target ?? default, IsHidden = isHidden, Category = AchievementCategory.AutoBattle, IsRare = isRare }; + + private static Achievement Humor(string id, int? target = null, bool isHidden = false, bool isRare = false) + => new() { Id = id, Target = target ?? default, IsHidden = isHidden, Category = AchievementCategory.Humor, IsRare = isRare }; + + private static Achievement BugRelated(string id, int? target = null, bool isHidden = false, bool isRare = false) + => new() { Id = id, Target = target ?? default, IsHidden = isHidden, Category = AchievementCategory.BugRelated, IsRare = isRare }; + + private static Achievement Behavior(string id, int? target = null, bool isHidden = false, bool isRare = false) + => new() { Id = id, Target = target ?? default, IsHidden = isHidden, Category = AchievementCategory.Behavior, IsRare = isRare }; + + private static Achievement EasterEgg(string id, int? target = null, bool isHidden = false, bool isRare = false) + => new() { Id = id, Target = target ?? default, IsHidden = isHidden, Category = AchievementCategory.EasterEgg, IsRare = isRare }; + + #endregion + + private readonly Achievement[] _allAchievements = + [ + + #region 基础使用类 + BasicUsage(id: AchievementIds.SanitySpender1, target: 10), // 刷理智次数 + BasicUsage(id: AchievementIds.SanitySpender2, target: 100), + BasicUsage(id: AchievementIds.SanitySpender3, target: 1000), + + BasicUsage(id: AchievementIds.SanitySaver1, target: 1), // 使用理智药数 + BasicUsage(id: AchievementIds.SanitySaver2, target: 10), + BasicUsage(id: AchievementIds.SanitySaver3, target: 50), + + BasicUsage(id: AchievementIds.FirstLaunch), // 首次启动 + BasicUsage(id: AchievementIds.SanityExpire, target: 8), // 单次消耗 8 瓶快过期的理智药 + BasicUsage(id: AchievementIds.OverLimitAgent, target: 100, isHidden: true), // 单次代理 100 关 + + BasicUsage(id: AchievementIds.RoguelikeGamePass1, target: 1), // 使用牛牛通关肉鸽 + BasicUsage(id: AchievementIds.RoguelikeGamePass2, target: 5), + BasicUsage(id: AchievementIds.RoguelikeGamePass3, target: 10), + BasicUsage(id: AchievementIds.RoguelikeRetreat, target: 100), // 牛牛放弃探索 100 次 + BasicUsage(id: AchievementIds.RoguelikeGoldMax, target: 999), // 肉鸽源石锭到达 999 + BasicUsage(id: AchievementIds.RoguelikeN04), // 肉鸽 难度 通关 + BasicUsage(id: AchievementIds.RoguelikeN08), + BasicUsage(id: AchievementIds.RoguelikeN12), + BasicUsage(id: AchievementIds.RoguelikeN15, isRare: true), + #endregion + + #region 功能探索类 + FeatureExploration(id: AchievementIds.ScheduleMaster1, target: 1), // 定时执行 + FeatureExploration(id: AchievementIds.ScheduleMaster2, target: 100), + + FeatureExploration(id: AchievementIds.MirrorChyanFirstUse, isHidden: true), // 第一次成功使用 MirrorChyan 下载 + FeatureExploration(id: AchievementIds.MirrorChyanCdkError, isHidden: true), // MirrorChyan CDK 错误 + + FeatureExploration(id: AchievementIds.Pioneer1), // 将 MAA 更新至公测版 + FeatureExploration(id: AchievementIds.Pioneer2, isHidden: true), // 将 MAA 更新至内测版(隐藏) + FeatureExploration(id: AchievementIds.Pioneer3, isHidden: true), // 使用未发布版本的 MAA(隐藏) + + FeatureExploration(id: AchievementIds.MosquitoLeg, target: 5), // 使用「借助战打 OF-1」功能超过 5 次 + + FeatureExploration(id: AchievementIds.RealGacha, isHidden: true), // 真正的抽卡 + FeatureExploration(id: AchievementIds.PeekScreen, isHidden: true), // 窥屏 + FeatureExploration(id: AchievementIds.CustomizationMaster, isHidden: true), // 自定义背景 + #endregion + + #region 自动战斗 + AutoBattle(id: AchievementIds.UseCopilot1, target: 1), // 自动战斗 + AutoBattle(id: AchievementIds.UseCopilot2, target: 10), + AutoBattle(id: AchievementIds.UseCopilot3, target: 100), + AutoBattle(id: AchievementIds.CopilotLikeGiven1, target: 1), // 点赞 1 次 + AutoBattle(id: AchievementIds.CopilotLikeGiven2, target: 10), // 点赞 10 次 + AutoBattle(id: AchievementIds.CopilotLikeGiven3, target: 50), // 点赞 50 次 + AutoBattle(id: AchievementIds.CopilotError), // 代理作战出现失误 + + AutoBattle(id: AchievementIds.MapOutdated, isHidden: true), // 提示需要更新地图资源 + AutoBattle(id: AchievementIds.Irreplaceable, isHidden: true), // 自动编队缺少至少两名干员 + #endregion + + #region 搞笑/梗类成就 + Humor(id: AchievementIds.SnapshotChallenge1, isHidden: true), // 平均截图用时超过 800ms(高 ping 战士) + Humor(id: AchievementIds.SnapshotChallenge2, isHidden: true), // 平均截图用时在 400ms 到 800ms 之间(是不是有点太慢了) + Humor(id: AchievementIds.SnapshotChallenge3), // 平均截图用时小于 400ms(截图挑战 · Normal) + Humor(id: AchievementIds.SnapshotChallenge4), // 平均截图用时小于 100ms(截图挑战 · Fast) + Humor(id: AchievementIds.SnapshotChallenge5), // 平均截图用时小于 10ms(截图挑战 · Ultra) + Humor(id: AchievementIds.SnapshotChallenge6, isHidden: true, isRare: true), // 平均截图用时小于 5ms + + Humor(id: AchievementIds.QuickCloser, isHidden: true), // 快速关闭弹窗 + Humor(id: AchievementIds.TacticalRetreat), // 停止任务 + Humor(id: AchievementIds.Martian, isHidden: true), // 90 天没更新 + Humor(id: AchievementIds.RecruitNoSixStar, target: 500), // 公招中累计 500 次没出现六星tag + Humor(id: AchievementIds.RecruitNoSixStarStreak, target: 500, isHidden: true), // 公招中连续 500 次没出现六星tag + #endregion + + #region BUG 相关 + BugRelated(id: AchievementIds.CongratulationError, isHidden: true), // 喜报 + BugRelated(id: AchievementIds.UnexpectedCrash, isHidden: true), // 不速之客 + BugRelated(id: AchievementIds.ProblemFeedback), // 问题反馈 + BugRelated(id: AchievementIds.CdnTorture, target: 3), // 下载资源失败超过3次 + #endregion + + #region 习惯 行为 时长类 + Behavior(id: AchievementIds.MissionStartCount, target: 3), // 一天内开始任务超过 3 次 + Behavior(id: AchievementIds.LongTaskTimeout), // 触发超时提醒 + Behavior(id: AchievementIds.ProxyOnline3Hours, isHidden: true), // 使用代理功能连续在线超过 3 小时 + Behavior(id: AchievementIds.TaskStartCancel, isHidden: true), // 在开始任务后马上又停止 + Behavior(id: AchievementIds.AfkWatcher), // 窗口尺寸最小化后长时间不操作 + + Behavior(id: AchievementIds.UseDaily1, target: 7), // 连续使用时间 + Behavior(id: AchievementIds.UseDaily2, target: 30), + Behavior(id: AchievementIds.UseDaily3, target: 365, isRare: true), + #endregion + + #region 彩蛋类 + EasterEgg(id: AchievementIds.Rules, isHidden: true), // 我会一直注视着你 + EasterEgg(id: AchievementIds.VersionClick, isHidden: true), // 这也能点? + + EasterEgg(id: AchievementIds.AprilFools, isHidden: true), // 愚人节 + EasterEgg(id: AchievementIds.MidnightLaunch, isHidden: true), // 0~4 点 + EasterEgg(id: AchievementIds.LunarNewYear, isHidden: true), // 春节 + + EasterEgg(id: AchievementIds.Lucky, isHidden: true, isRare: true), // 启动 MAA 时有极小概率触发 + #endregion + ]; + + private void InitializeAchievements() + { + foreach (var achievement in _allAchievements) + { + RegisterAchievement(achievement); + } + } + + #region 带有 CustomData 的辅助函数 + + public void MissionStartCountAdd() + { + const string Id = AchievementIds.MissionStartCount; + const string Key = AchievementIds.MissionStartCountCustomDataKey; + + var today = DateTime.UtcNow.ToYjDate().Date; + DateTime? lastDate = GetAchievementCustomData(AchievementIds.MissionStartCount, Key)?.ToObject(); + + if (lastDate.HasValue && lastDate.Value == today) + { + AddProgress(Id); + } + else + { + SetAchievementCustomData(Id, Key, JToken.FromObject(today)); + SetProgress(Id, 1); + } + } + + public void UseDailyAdd() + { + // group 是不注册的,从第一个成就取 CustomData + const string Id = AchievementIds.UseDaily1; + const string GroupId = AchievementIds.UseDailyGroup; + const string Key = AchievementIds.UseDailyCustomDataKey; + + var today = DateTime.UtcNow.ToYjDate().Date; + DateTime? lastDate = GetAchievementCustomData(Id, Key)?.ToObject(); + if (lastDate.HasValue) + { + var delta = (today - lastDate.Value).TotalDays; + + switch (delta) + { + case 1: + AddProgressToGroup(GroupId); + break; + case > 1: + SetProgressToGroup(GroupId, 1); + break; + } + } + else + { + SetProgressToGroup(GroupId, 1); + } + + SetAchievementCustomData(Id, Key, JToken.FromObject(today)); + } + + #endregion + } +} diff --git a/src/MaaWpfGui/Helper/JsonDataHelper.cs b/src/MaaWpfGui/Helper/JsonDataHelper.cs index 3ff69f2583..8571ec9bb0 100644 --- a/src/MaaWpfGui/Helper/JsonDataHelper.cs +++ b/src/MaaWpfGui/Helper/JsonDataHelper.cs @@ -22,35 +22,21 @@ namespace MaaWpfGui.Helper { public static class JsonDataHelper { - private static readonly string _dataDir = Path.Combine(Environment.CurrentDirectory, "data"); + private static readonly string _dataDir = "data"; private static readonly object _lock = new(); private static readonly ILogger _logger = Log.ForContext("SourceContext", "JsonDataHelper"); - static JsonDataHelper() - { - try - { - if (!Directory.Exists(_dataDir)) - { - Directory.CreateDirectory(_dataDir); - } - } - catch (Exception ex) - { - _logger.Error(ex, "Failed to create data directory."); - } - } - /// /// 从 data/{key}.json 读取对象,如果不存在则返回 defaultValue /// /// 要获取的数据类型 /// 文件名 /// 数据类型 + /// 目标文件夹 /// 反序列化数据 - public static T? Get(string key, T? defaultValue = default) + public static T? Get(string key, T? defaultValue = default, string? dataDir = null) { - var filePath = Path.Combine(_dataDir, $"{key}.json"); + var filePath = Path.Combine(dataDir ?? _dataDir, $"{key}.json"); if (!File.Exists(filePath)) { @@ -80,15 +66,17 @@ namespace MaaWpfGui.Helper /// 要写入的数据类型 /// 文件名 /// 数据 + /// 目标文件夹 /// 是否设置成功 - public static bool Set(string key, T value) + public static bool Set(string key, T value, string? dataDir = null) { - var filePath = Path.Combine(_dataDir, $"{key}.json"); + var filePath = Path.Combine(dataDir ?? _dataDir, $"{key}.json"); lock (_lock) { try { + Directory.CreateDirectory(_dataDir); var json = JsonConvert.SerializeObject(value, Formatting.Indented); File.WriteAllText(filePath, json); return true; diff --git a/src/MaaWpfGui/Helper/LocalizationHelper.cs b/src/MaaWpfGui/Helper/LocalizationHelper.cs index 2f667bb906..f5e83efae0 100644 --- a/src/MaaWpfGui/Helper/LocalizationHelper.cs +++ b/src/MaaWpfGui/Helper/LocalizationHelper.cs @@ -184,9 +184,9 @@ namespace MaaWpfGui.Helper private static readonly string[] _pallasChars = ["💃", "🕺", "🍷", "🍸", "🍺", "🍻", "🍷", "🍸", "🍺", "🍻"]; private static readonly Random _pallasRand = new(); - private static string GetPallasString() + public static string GetPallasString(int low = 3, int high = 6) { - int len = _pallasRand.Next(3, 6); + int len = _pallasRand.Next(low, high); StringBuilder cheersBuilder = new StringBuilder(len); for (int i = 0; i < len; i++) { diff --git a/src/MaaWpfGui/Helper/MessageBoxHelper.cs b/src/MaaWpfGui/Helper/MessageBoxHelper.cs index 8b77ed4410..c431f8ccab 100644 --- a/src/MaaWpfGui/Helper/MessageBoxHelper.cs +++ b/src/MaaWpfGui/Helper/MessageBoxHelper.cs @@ -20,6 +20,7 @@ using System.Runtime.InteropServices; using System.Security; using System.Windows; using HandyControl.Data; +using MaaWpfGui.Constants; using Windows.Win32; using Windows.Win32.UI.Controls; using Windows.Win32.UI.Shell; @@ -157,7 +158,18 @@ namespace MaaWpfGui.Helper YesContent = yes, NoContent = no, }; - return HandyControl.Controls.MessageBox.Show(info); + + DateTime startTime = DateTime.Now; + + var result = HandyControl.Controls.MessageBox.Show(info); + + var duration = DateTime.Now - startTime; + if (duration.TotalSeconds <= 1) + { + AchievementTrackerHelper.Instance.Unlock(AchievementIds.QuickCloser); + } + + return result; } } diff --git a/src/MaaWpfGui/Main/AsstProxy.cs b/src/MaaWpfGui/Main/AsstProxy.cs index 7bdee92e5c..05a8d8f0da 100644 --- a/src/MaaWpfGui/Main/AsstProxy.cs +++ b/src/MaaWpfGui/Main/AsstProxy.cs @@ -35,6 +35,7 @@ using MaaWpfGui.Services; using MaaWpfGui.Services.Notification; using MaaWpfGui.States; using MaaWpfGui.ViewModels.UI; +using MaaWpfGui.ViewModels.UserControl.Settings; using MaaWpfGui.ViewModels.UserControl.TaskQueue; using Newtonsoft.Json; using Newtonsoft.Json.Linq; @@ -646,13 +647,36 @@ namespace MaaWpfGui.Main switch (screencapCostAvgInt) { + // 日志提示 case >= 800: AddLog(string.Format(LocalizationHelper.GetString("FastestWayToScreencapErrorTip"), screencapCostAvgInt), UiLogColor.Warning); + AchievementTrackerHelper.Instance.Unlock(AchievementIds.SnapshotChallenge1); break; - case >= 400: AddLog(string.Format(LocalizationHelper.GetString("FastestWayToScreencapWarningTip"), screencapCostAvgInt), UiLogColor.Warning); + AchievementTrackerHelper.Instance.Unlock(AchievementIds.SnapshotChallenge2); break; + default: + { + AchievementTrackerHelper.Instance.Unlock(AchievementIds.SnapshotChallenge3); + + if (screencapCostAvgInt < 100) + { + AchievementTrackerHelper.Instance.Unlock(AchievementIds.SnapshotChallenge4); + } + + if (screencapCostAvgInt < 10) + { + AchievementTrackerHelper.Instance.Unlock(AchievementIds.SnapshotChallenge5); + } + + if (screencapCostAvgInt < 5) + { + AchievementTrackerHelper.Instance.Unlock(AchievementIds.SnapshotChallenge6); + } + + break; + } } } @@ -756,6 +780,7 @@ namespace MaaWpfGui.Main _runningState.SetIdle(true); Instances.CopilotViewModel.AddLog(LocalizationHelper.GetString("CombatError"), UiLogColor.Error); + AchievementTrackerHelper.Instance.Unlock(AchievementIds.CopilotError); } break; @@ -818,6 +843,7 @@ namespace MaaWpfGui.Main } Instances.CopilotViewModel.AddLog(LocalizationHelper.GetString("CompleteCombat"), UiLogColor.Info); + AchievementTrackerHelper.Instance.AddProgressToGroup(AchievementIds.UseCopilotGroup, 1); } } @@ -1109,6 +1135,11 @@ namespace MaaWpfGui.Main { Instances.CopilotViewModel.AddLog(LocalizationHelper.GetString("MissingOperators"), UiLogColor.Error); } + + if (missingOpers is not null && missingOpers.Count >= 2) + { + AchievementTrackerHelper.Instance.Unlock(AchievementIds.Irreplaceable); + } } break; @@ -1187,6 +1218,7 @@ namespace MaaWpfGui.Main /* 肉鸽相关 */ case "ExitThenAbandon": Instances.TaskQueueViewModel.AddLog(LocalizationHelper.GetString("ExplorationAbandoned"), UiLogColor.Error); + AchievementTrackerHelper.Instance.AddProgress(AchievementIds.RoguelikeRetreat); break; // case "StartAction": @@ -1248,6 +1280,7 @@ namespace MaaWpfGui.Main case "GamePass": Instances.TaskQueueViewModel.AddLog(LocalizationHelper.GetString("RoguelikeGamePass"), UiLogColor.RareOperator); + AchievementTrackerHelper.Instance.AddProgressToGroup(AchievementIds.RoguelikeGamePassGroup); break; case "BattleStartAll": @@ -1309,6 +1342,7 @@ namespace MaaWpfGui.Main case "EndOfActionThenStop": TaskQueueViewModel.MallTask.LastCreditFightTaskTime = DateTime.UtcNow.ToYjDate().ToFormattedString(); Instances.TaskQueueViewModel.AddLog(LocalizationHelper.GetString("CompleteTask") + LocalizationHelper.GetString("CreditFight")); + AchievementTrackerHelper.Instance.AddProgressToGroup(AchievementIds.MosquitoLeg, 1); break; case "VisitLimited" or "VisitNextBlack": TaskQueueViewModel.MallTask.LastCreditVisitFriendsTime = DateTime.UtcNow.ToYjDate().ToFormattedString(); @@ -1387,6 +1421,9 @@ namespace MaaWpfGui.Main $"{allDrops}{(curTimes >= 0 ? $"\n{LocalizationHelper.GetString("CurTimes")} : {curTimes}" : string.Empty)}"); + + AchievementTrackerHelper.Instance.AddProgressToGroup(AchievementIds.SanitySpenderGroup, curTimes > 0 ? curTimes : 1); + break; } @@ -1458,6 +1495,16 @@ namespace MaaWpfGui.Main Instances.TaskQueueViewModel.AddLog(level + " ★ Tags", UiLogColor.Info); } + if (level == 6) + { + AchievementTrackerHelper.Instance.SetProgress(AchievementIds.RecruitNoSixStarStreak, 1); + } + else + { + AchievementTrackerHelper.Instance.AddProgress(AchievementIds.RecruitNoSixStar); // 累计 + AchievementTrackerHelper.Instance.AddProgress(AchievementIds.RecruitNoSixStarStreak); // 连续 + } + /* bool robot = (bool)subTaskDetails["robot"]; if (robot) @@ -1690,6 +1737,11 @@ namespace MaaWpfGui.Main if ((subTaskDetails?.Children())?.Any() is true) { FightTimes = subTaskDetails.ToObject()!; + if (FightTimes.TimesFinished > 0) + { + AchievementTrackerHelper.Instance.SetProgress(AchievementIds.OverLimitAgent, FightTimes.TimesFinished); + } + if (FightSettingsUserControlModel.Instance.HasTimesLimited && FightTimes.TimesFinished + FightTimes.Series > FightSettingsUserControlModel.Instance.MaxTimes) { Instances.TaskQueueViewModel.AddLog(string.Format(LocalizationHelper.GetString("FightTimesUnused"), FightTimes.TimesFinished, FightTimes.Series, FightTimes.TimesFinished + FightTimes.Series, FightSettingsUserControlModel.Instance.MaxTimes), UiLogColor.Error); @@ -1720,11 +1772,14 @@ namespace MaaWpfGui.Main { MedicineUsedTimes += medicineCount; medicineLog = LocalizationHelper.GetString("MedicineUsed") + $" {MedicineUsedTimes}(+{medicineCount})"; + AchievementTrackerHelper.Instance.AddProgressToGroup(AchievementIds.SanitySaverGroup, medicineCount); } else { ExpiringMedicineUsedTimes += medicineCount; medicineLog = LocalizationHelper.GetString("ExpiringMedicineUsed") + $" {ExpiringMedicineUsedTimes}(+{medicineCount})"; + AchievementTrackerHelper.Instance.AddProgressToGroup(AchievementIds.SanitySaverGroup, medicineCount); + AchievementTrackerHelper.Instance.SetProgress(AchievementIds.SanityExpire, ExpiringMedicineUsedTimes); } Instances.TaskQueueViewModel.AddLog(medicineLog, UiLogColor.Info); diff --git a/src/MaaWpfGui/Main/Bootstrapper.cs b/src/MaaWpfGui/Main/Bootstrapper.cs index 451ab68d0d..4208a0f963 100644 --- a/src/MaaWpfGui/Main/Bootstrapper.cs +++ b/src/MaaWpfGui/Main/Bootstrapper.cs @@ -14,6 +14,7 @@ using System; using System.Collections.Generic; using System.Diagnostics; +using System.Globalization; using System.IO; using System.Reflection; using System.Runtime.CompilerServices; @@ -27,6 +28,7 @@ using System.Windows.Media; using System.Windows.Threading; using GlobalHotKey; using MaaWpfGui.Configuration; +using MaaWpfGui.Constants; using MaaWpfGui.Helper; using MaaWpfGui.Properties; using MaaWpfGui.Services; @@ -36,6 +38,7 @@ using MaaWpfGui.Services.RemoteControl; using MaaWpfGui.Services.Web; using MaaWpfGui.States; using MaaWpfGui.ViewModels.UI; +using MaaWpfGui.ViewModels.UserControl.Settings; using MaaWpfGui.Views.UI; using MaaWpfGui.WineCompat; using Serilog; @@ -298,8 +301,61 @@ namespace MaaWpfGui.Main /// protected override void DisplayRootView(object rootViewModel) { + if (Application.Current.IsShuttingDown()) + { + return; + } + Instances.WindowManager.ShowWindow(rootViewModel); Instances.InstantiateOnRootViewDisplayed(Container); + + // 以下是成就解锁逻辑 + AchievementTrackerHelper.Instance.Unlock(AchievementIds.FirstLaunch); + if ((DateTime.Now - VersionUpdateSettingsUserControlModel.BuildDateTime).TotalDays > 90 || + (DateTime.Now - SettingsViewModel.VersionUpdateSettings.ResourceDateTime).TotalDays > 90) + { + AchievementTrackerHelper.Instance.Unlock(AchievementIds.Martian); + } + + if (Instances.VersionUpdateViewModel.IsDebugVersion()) + { + AchievementTrackerHelper.Instance.Unlock(AchievementIds.Pioneer3); + } + else if (Instances.VersionUpdateViewModel.IsBetaVersion()) + { + AchievementTrackerHelper.Instance.Unlock(AchievementIds.Pioneer1); + } + else if (!Instances.VersionUpdateViewModel.IsStdVersion()) // 内测版要传入 SemVersion 判断,这里就取反判断了 + { + AchievementTrackerHelper.Instance.Unlock(AchievementIds.Pioneer2); + } + + // 0.066% 概率触发 + if (new Random().NextDouble() < 0.00066) + { + AchievementTrackerHelper.Instance.Unlock(AchievementIds.Lucky); + } + + var now = DateTime.Now; + + // 0~4 点启动 + if (now.Hour is > 0 and < 4) + { + AchievementTrackerHelper.Instance.Unlock(AchievementIds.MidnightLaunch); + } + + // 愚人节启动 + if (now is { Month: 4, Day: 1 }) + { + AchievementTrackerHelper.Instance.Unlock(AchievementIds.AprilFools); + } + + // 春节 + ChineseLunisolarCalendar chineseCalendar = new(); + if (chineseCalendar.GetMonth(now) == 1 && chineseCalendar.GetDayOfMonth(now) == 1) + { + AchievementTrackerHelper.Instance.Unlock(AchievementIds.LunarNewYear); + } } /// diff --git a/src/MaaWpfGui/Models/Achievement.cs b/src/MaaWpfGui/Models/Achievement.cs new file mode 100644 index 0000000000..d2718de461 --- /dev/null +++ b/src/MaaWpfGui/Models/Achievement.cs @@ -0,0 +1,127 @@ +// +// MaaWpfGui - A part of the MaaCoreArknights project +// Copyright (C) 2021 MistEO and Contributors +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License v3.0 only as published by +// the Free Software Foundation, either version 3 of the License, or +// any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY +// + +#nullable enable + +using System; +using System.Collections.Generic; +using System.Windows; +using System.Windows.Media; +using MaaWpfGui.Helper; +using Newtonsoft.Json; +using Newtonsoft.Json.Linq; +using static MaaWpfGui.Constants.Enums; + +namespace MaaWpfGui.Models +{ + public class Achievement + { + [JsonProperty(DefaultValueHandling = DefaultValueHandling.Ignore)] + public string Id { get; set; } = string.Empty; + + [JsonProperty(DefaultValueHandling = DefaultValueHandling.Ignore)] + public bool IsUnlocked { get; set; } = false; + + [JsonProperty(DefaultValueHandling = DefaultValueHandling.Ignore)] + public DateTime? UnlockedTime { get; set; } = null; + + [JsonProperty(DefaultValueHandling = DefaultValueHandling.Ignore)] + public int Progress { get; set; } = 0; // 用于累计型成就 + + // 用法示例 + // 记录上次打开时间 + // achievement.CustomData["LastLaunchTime"] = DateTime.UtcNow; + // 读取时 + // if (achievement.CustomData.TryGetValue("LastLaunchTime", out var token)) + // { + // var lastTime = token.ToObject(); + // } + [JsonProperty(DefaultValueHandling = DefaultValueHandling.Ignore)] + public Dictionary? CustomData { get; set; } = null; + + [JsonIgnore] + public string Title => IsUnlocked ? LocalizationHelper.GetString($"Achievement.{Id}.Title") : "???"; + + [JsonIgnore] + public string Description => IsUnlocked ? LocalizationHelper.GetString($"Achievement.{Id}.Description") : "???"; + + [JsonIgnore] + public string Conditions => CanShow ? LocalizationHelper.GetString($"Achievement.{Id}.Conditions") : "???"; + + [JsonIgnore] + public DateTime? UnlockedTimeLocal => UnlockedTime?.ToLocalTime(); + + [JsonIgnore] + public bool IsHidden { get; set; } = false; + + [JsonIgnore] + public bool CanShow => !IsHidden || IsUnlocked; + + [JsonIgnore] + public bool IsProgressive => Target != default; + + [JsonIgnore] + public int Target { get; set; } = default; // 可选目标值 + + [JsonIgnore] + public AchievementCategory Category { get; set; } // 分组 + + [JsonIgnore] + public bool IsRare { get; set; } = false; + + [JsonIgnore] + public bool IsVisibleInSearch { get; set; } = true; + + [JsonIgnore] + public string MedalBrushKey + { + get + { + if (!IsUnlocked) + { + return "LockedMedalBrush"; + } + + if (IsRare) + { + return "AchievementBrush.Rare.LinearGradientBrush"; + } + + if (IsHidden) + { + return "HiddenMedalBrush"; + } + + return $"AchievementBrush.{Category}"; + } + } + + [JsonIgnore] + public Brush MedalBrush + { + get + { + if (Application.Current.Resources[MedalBrushKey] is Brush brush) + { + return brush; + } + + return new SolidColorBrush(Colors.Transparent); + } + } + + // 新解锁的成就放前面,仅本次关闭前生效 + [JsonIgnore] + public bool IsNewUnlock { get; set; } = false; + } +} diff --git a/src/MaaWpfGui/Models/ResourceUpdater.cs b/src/MaaWpfGui/Models/ResourceUpdater.cs index ca526caba2..fcb4641d48 100644 --- a/src/MaaWpfGui/Models/ResourceUpdater.cs +++ b/src/MaaWpfGui/Models/ResourceUpdater.cs @@ -169,6 +169,7 @@ namespace MaaWpfGui.Models break; case Enums.MirrorChyanErrorCode.KeyInvalid: ToastNotification.ShowDirect(LocalizationHelper.GetString("MirrorChyanCdkInvalid")); + AchievementTrackerHelper.Instance.Unlock(AchievementIds.MirrorChyanCdkError); break; case Enums.MirrorChyanErrorCode.ResourceQuotaExhausted: ToastNotification.ShowDirect(LocalizationHelper.GetString("MirrorChyanCdkQuotaExhausted")); @@ -279,6 +280,7 @@ namespace MaaWpfGui.Models SettingsViewModel.VersionUpdateSettings.NewResourceFoundInfo = string.Empty; + AchievementTrackerHelper.Instance.Unlock(AchievementIds.MirrorChyanFirstUse); return true; } @@ -289,6 +291,7 @@ namespace MaaWpfGui.Models /// /// :已是最新版本。 /// :有新版本。(海外源不会自动下载) + /// :有新版本,但未填写 cdk /// :下载成功。 /// :网络错误。 /// :其他错误。 diff --git a/src/MaaWpfGui/Res/Localizations/en-us.xaml b/src/MaaWpfGui/Res/Localizations/en-us.xaml index 73ae8840ef..8d04e2dac2 100644 --- a/src/MaaWpfGui/Res/Localizations/en-us.xaml +++ b/src/MaaWpfGui/Res/Localizations/en-us.xaml @@ -26,6 +26,7 @@ UI Theme HotKeys Update + Achievements About us Issue Report MAA Version @@ -1085,4 +1086,285 @@ If you want to run multiple MAA at the same time, please copy the whole MAA and Reminder interval (minutes) The task has been running for more than {0} minutes (currently running for {1} minutes). If there are no further log updates for a long time, manual intervention may be required. + + 🎉 Achievement Unlocked + Unlock Condition: + Achievement Level: + View Achievements + Backup Achievements + Restore Backup + Unlocked at: + Achievement List + Backed up to + Achievement progress restored + Restore failed, invalid file format + + Tactical Practice + You've got the hang of it + Complete 10 auto-battle operations + + Auto-Battle Pioneer + Even more skilled than me + Complete 50 auto-battle operations + + ZOOT + Closure's proxy program is running normally. + Complete 1000 auto-battle operations + + YOLO! + "I know what I'm doing!" —A user, moments before clicking + Close popup within 1 second (you probably didn't read it) + + Tactical Retreat + I'm not giving up, it's a tactical retreat! + Manually stop a running auto-battle operation + + Screen Peeker + Love staring at the screen, huh? .jpg + Use Peep function once + + Good News + Why isn't my MAA working anymore? + Triggered a MAA error + + Uninvited Guest + You've triggered an unknown domain. + Unhandled exception occurred and was recorded in crash logs + + Customization Master + We're all about that 2D life! + Change background image once + + Real Gacha + Isn't this supposed to be a gacha simulator? How did I actually pull??? + Use Gacha function once + + First Timer + Welcome to MaaAssistantArknights. + Launch the software for the first time + + Martian + How's that ancient version working for you? + Resource or version date is more than 3 months old + + Serious Erro- + I'll be watching you. + MAA Employee Guidelines + + That's Clickable? + What's this? Version number, let me click it. + Click the version number badge on settings page + + Penny Pincher + Every point of sanity used perfectly. + Use sanity recovery function 1 time + + Supply Master + You've mastered the use of potions. + Use sanity recovery function 10 times + + Perpetual Motion Machine + Your sanity seems to never run out. + Use sanity recovery function 50 times + + XX Management Master + More efficient planning. + Execute scheduled task 1 time + + Universal Automation Solution 0.3.2.9f2 + You are familiar with PRTS's automated deployment. + Execute scheduled tasks 100 times + + About to Expire + Hurry up and use them!!! + Use "Unlimited expiring (<48h) sanity potions", use more than 8 expiring potions in a single task + + Over-Limit Agent + You performed an agent task that could grind the great dao to dust. + Perform more than 100 auto-battle operations in a single task + + Mirror~~☆ + It's MirrorChyan! We're saved! + Successfully use MirrorChyan for the first time + + Is This... Right? + Who ate my alphabet cookies? + Enter incorrect MirrorChyan CDK + + Risk Assessment + What must be eliminated are the risks from within. + Submit problem feedback once + + Impatient King + I know you're in a hurry, but don't rush. + Use auto-battle without updating resources + + Copying Homework + True hands-off gaming. + Use "Copilot" function to clear 1 time + + Hands-Off All the Way + Leave all events to MAA. + Use "Copilot" function to clear 10 times + + Will Still Mark "Very Easy" Next Time + Still don't know the final boss mechanics in the end. + Use "Copilot" function to clear 100 times + + Superstition Invalid + Things have come to this, let's sleep first. + Accumulated 500 recruitments without 6★ tags appearing + + It Must Be MAA's Fault! + RAGE RAGE RAGE RAGE RAGE RAGE RAGE RAGE RAGE RAGE! + 500 consecutive recruitments without 6★ tags appearing + + Even Mosquito Legs Are Meat! + You deeply understand the principle of accumulating little by little. + Use "Support Unit Combat OF-1" function more than 5 times + + Test Pioneer + You experienced the beta version ahead of others. + Update MAA to beta version + + Super Test Pioneer + You used the latest test version (please report any new issues!) + Update MAA to alpha version + + DEBUG Vanguard + Anyone who can get this version is no ordinary person. + Use unreleased version of MAA + + Irreplaceable + Maybe I should get different homework? + When using "Copilot - Auto Squad", missing at least two operators + + High Ping Warrior + "lag" + Average screenshot time exceeds 800ms + + Isn't This a Bit Too Slow + Screenshots seem a bit sluggish. + Average screenshot time between 400ms and 800ms + + Screenshot Challenge · Normal + Maybe try screenshot enhancement? + Average screenshot time under 400ms + + Screenshot Challenge · Fast + Screenshots so fast I didn't even react. + Average screenshot time under 100ms + + Screenshot Challenge · Ultra + Screenshot speed beats 90% of MAA users. + Average screenshot time under 10ms + + Screenshot Challenge · Beyond + You have discovered MAA's hidden power. + Average screenshot time under 5ms + + Lucky + Legend says those who see this achievement got three 6★s in a row. + Extremely low probability trigger when starting MAA + + Network Torture + Farther than the universe are GitHub's servers. + Resource download failed more than 3 times + + Call-for-Help AFK Party + Are you AFK, or did you forget to close it? + Window minimized for a long time without operation + + Never Give Up + It's trust, and it's dependence. + Start tasks more than 3 times in one day + + First Arrival at the End + If only we could reach... that place... + Clear Integrated Strategies with MAA 1 time + + Return to the End + I've walked this path many times. + Clear Integrated Strategies with MAA 5 times + + Regular at the End + Could walk there with eyes closed. + Clear Integrated Strategies with MAA 10 times + + Failure Is the Mother of Success + One day, we'll reach the shore of success. Failure is the ladder to success. + MAA abandons exploration 100 times + + All Full + Cannott, bring out your most expensive stuff! + Integrated Strategies Originium Ingots reach 999 + + Encouragement + Likes are the greatest encouragement to authors! + Like a copilot 1 time + + Active Interaction + 10 likes, actively participating in community interaction. + Like copilots 10 times total + + Like Master + 50 likes, you've become a like master! + Like copilots 50 times total + + So Indecisive + Ah yes yes, ah no no, ah yes yes, ah no no... + Stop immediately after starting a task + + Oops + An accident happened... + Error occurred during copilot operation + + Long Task + How about we wait a bit longer? + Trigger timeout reminder + + Perfect Attendance Pioneer + You have become the Doctor's stand-in. + Continuously online for more than 3 hours after starting tasks + + Daily Check-in + Seven consecutive days of check-in, too bad there's no reward. + Use MAA for 7 consecutive days + + Perfect Attendance Award + Let's give out this month's perfect attendance award. + Use MAA for 30 consecutive days + + No Rest All Year + So you're the one exploiting MAA every day? + Use MAA for 365 consecutive days + + Fooled Together + Surely no one would go online today? + Open the program on April 1st + + Midnight Operation + Are you a night operation specialist? + Launch after midnight (12 AM) + + Happy Lunar New Year + Good luck and 6★s come together! + Open the program during Lunar New Year + + Simple and Easy + This difficulty can't stop me. + Clear any I.S. at N4 difficulty or above with MAA + + Challenge the Difficult + Brave MAA fears no difficulty. + Clear any I.S. at N8 difficulty or above with MAA + + Beyond Human + Playing better than most people. + Clear any I.S. at N12 difficulty or above with MAA + + Immortal Arts Cup Contestant + Immortal MAA truly immortal. + Clear any I.S. at N15 difficulty or above with MAA + diff --git a/src/MaaWpfGui/Res/Localizations/ja-jp.xaml b/src/MaaWpfGui/Res/Localizations/ja-jp.xaml index fcd1fa9170..152c360985 100644 --- a/src/MaaWpfGui/Res/Localizations/ja-jp.xaml +++ b/src/MaaWpfGui/Res/Localizations/ja-jp.xaml @@ -1089,4 +1089,285 @@ MAA を複数開くには、新しい MAA を他のフォルダにコピーし リマインダー間隔時間(分) タスクが {0} 分以上実行されています(現在 {1} 分実行中)。長時間ログの更新がない場合、手動で介入する必要があるかもしれません。 + + 🎉 実績を獲得した + 達成の条件: + 実績レベル: + 実績を確認 + 実績をバックアップ + バックアップを読み込む + 獲得した: + 実績リスト + バックアップした + 実績の進捗が復元されました + 復元に失敗しました、ファイル形式が無効です + + 戦術実戦 + あなたはすでに始めました。 + 自動作戦を 10 回達成する + + 代理パイオニア + 私よりも熟練しています。 + 自動作戦を 100 回達成する + + ZOOT + クローシャ代理プログラムは正常に動作中。 + 自動作戦を 1000 回達成する + + YOLO! + 「私は自分がやっていることを知っている。」――あるユーザーがクリックする前に。 + 1秒以内にポップアップを閉じる(おそらく見ていなかったのでしょう) + + 臨陣脱逃 + これは放棄したわけではなく、戦術的撤退をしただけです! + 手動で実行中の代理を1回中止する + + 覗き込む + じっと見つめるのが好きですね.jpg + Peepを1回使用する控 + + 喜報 + 私のMAAに何が起こったのかしら? + MAAのエラーメッセージをトリガーする + + 招かれざる客 + 未知の領域をトリガーしました。 + 未処理の異常が発生し、クラッシュログに記録される + + カスタム達人 + うちは二次元です! + 背景画像を1回変更する + + 本当のガチャ + シミュレーションのガチャではないのですか?どうして本物を引いてしまったのでしょう??? + パラスガチャを1回使用する + + 初めて来ました + MaaAssistantArknightsへようこそ。 + ソフトウェアを初めて起動する + + 火星人 + 古いバージョンは使いやすかったですか? + リソースまたはバージョンの時間が3ヶ月以上経過している + + 重大エラー + 私はずっとあなたを見つめています。 + MAAのスタッフ規則 + + これもクリックできるの? + これは何ですか?バージョン番号、クリックしてみて。 + 設定ページのバージョン番号バッジをクリックする + + 精緻な計画 + 理性を一度も無駄にしません。 + 理性回復機能を 1 回使用する + + 補給マスター + あなたは薬剤の使い方がめっちゃ上手していた。 + 理性回復機能を 10 回使用する + + 永遠の動力機 + あなたの理性は永遠に尽きることがないようです。 + 理性回復機能を 50 回使用する + + 時間管理の達人 + より効果的な計画。 + スケジュール実行を 1 回実行する + + 汎用型自動化解決策0.3.2.9f2 + PRTSの自動化デプロイを熟知しました。 + スケジュール実行を 100 回実行する + + もうすぐ期限切れ + 急いで!!! + 「48時間以内に期限が切れる回復剤を無制限に使用」を使用し、1回のタスクで 8 本以上の期限切れ理性回復剤を使用する + + 限界を超える代理 + 大道を磨り減らすような代理タスクを実行しました。 + 1回のタスクで 100 回以上自動作戦を行う。 + + Mirror~~☆ + Mirrorちゃんです!これで私たちは助かります! + 初めてMirrorちゃんを成功裏に使用する + + これ…合ってますか? + 誰が私のアルファベットビスケットを食べたのですか? + Mirrorちゃんの CDK を誤って記入する + + リスク調査 + 必ず排除すべきは、内部からのリスクです。 + 問題報告を 1 回行う + + せっかち者 + 急いでいることは分かっていますが、少しお待ちください。 + リソースを更新せずに自動戦闘を使用する + + 課題をコピー + 本当に放置してクリアしました。 + 「自動戦闘」機能で 1 回クリアする + + ずっと放置 + イベントを全部MAAに任せました。 + 「自動戦闘」機能で 10 回クリアする + + 次回の調査も非常に簡単 + 最後までボスの仕組みが分かりませんでした。 + 「自動戦闘」機能で 100 回クリアする + + 玄学無効 + 事はここまでです、まずは寝ましょう。 + 公開求人中に6星のタグが 500 回出なかった + + MAAがやったに違いない! + 乆乆乆乆乆乆乆乆乆乆! + 公開求人中に6星のタグが 500 回連続で出なかった + + 少しずつ積み上げていく + あなたは少しずつ積み上げていくことの大切さを深く理解しています。 + 「サポートを借りで OF-1 をクリア」機能を5回以上使用する + + テストパイオニア + あなたはテスト中のバージョンを先行体験しました。 + MAAをベータ版に更新する + + スーパーテストパイオニア + あなたは最新のテストバージョンを先行体験しました(新しい問題があれば問題報告をお願いします!) + MAAを内部テスト版に更新する + + デバッグの先駆者 + このバージョンを使える人は特別です。 + 未公開バージョンのMAAを使用する + + 替代不可 + たぶん、別の作業を変更すべきですか? + 「自動戦闘 - 自動編成」使用時、少なくとも2名のオペレーターが不足している。 + + 高遅延危機 + 「kale」 + 平均スクリーンショット時間が 800ms を超えている + + 少し遅くないですか? + スクリーンショットは少し苦労しているように見えます。 + 平均スクリーンショット時間が 400ms から 800ms の間 + + スクリーンショットチャレンジ · Normal + スクリーンショットを強化してみませんか? + 平均スクリーンショット時間が 400ms 未満 + + スクリーンショットチャレンジ · Fast + スクリーンショットが速すぎて反応できませんでした。 + 平均スクリーンショット時間が 100ms 未満 + + スクリーンショットチャレンジ · Ultra + スクリーンショット速度がMAAユーザーの90%を超えました。 + 平均スクリーンショット時間が 10ms 未満 + + スクリーンショットチャレンジ · Beyond + あなたは MAA の隠された力を発見しました。 + 平均スクリーンショット時間が 5ms 未満 + + ラッキー + この実績を見ることができる人は、三連金を引いたらしいです。 + MAAを起動した際に極小確率で発生する + + ネットワークは苦しい + 宇宙より遠い場所、それは Github のサーバーです。 + リソースのダウンロードに 3 回以上失敗する + + 放置仲間 + 放置しているのか、それとも忘れていましたか? + ウィンドウを最小化した後、長時間操作しない + + 離れず一緒に + 信頼でもあり、依存でもあります。 + 1日のうちにタスクを 3 回以上開始する + + 初めて終点に到達 + その場所を……辿り着くできるならば…… + MAAで自動ローグを 1 回クリアする + + 再び終点に到達 + この道はもう何度も通りました。 + MAAで自動ローグを 5 回クリアする + + 終点常連 + 目を閉じても歩けます。 + MAAで自動ローグを 10 回クリアする + + 失敗は成功の母である + いつか成功の岸に到達するために、失敗は成功の階段です。 + MAAで自動ローグを 100 回放棄する + + 満杯した + カノット、あなたの店で一番高いものを出して! + ローグライクで、源石錐投資が999に到達する + + 励ましの一言 + 「Like」は作者への最大の励ましです! + 作業に 1 回「Like」を付ける + + 積極的な交流 + 「Like」を10回付けて、コミュニティに積極的に参加しています。 + 作業に累計 10 回「Like」を付ける + + いいね達人 + 「Like」を50回付けたあなたは、すでに「Like」の達人です! + 作業に累計 50 回「Like」を付ける + + まさにジジジジジ + ああ、合っている、合っていない、ああ、合っている、合っていない… + タスク開始後、すぐに停止する。 + + あらら + 事故が起きました… + 代理戦闘中にエラーが発生 + + 長いタスク + 少し待ってみませんか? + タイムアウトアラートが発生 + + 全勤先鋒 + あなたは、すでに博士の代理人です。 + タスク開始から連続してオンラインで3時間以上 + + 毎日支給 + 7日間連続でサインイン、残念ながら報酬はありません。 + MAAを 7 日間連続で使用する + + 全勤賞 + 今月の全勤賞を私に振り込んでください。 + MAAを 30 日間連続で使用する + + 年中無休 + 毎日でパラスを搾取しているのはあなたでしょうか? + MAAを 365 日間連続で使用する + + 愚かな同行者 + 今日は本当に誰かがログインするのですか? + 4月1日にプログラムを起動する + + 真夜中の戦闘 + あなたは夜行性のエキスパートですか? + 午前0時以降に起動する + + 新春おめでとう + 幸運が6星と一緒にやってきます! + 旧正月の期間にプログラムを起動する + + 簡単なこと + この難易度は私を困らせることはできない。 + 自動ローグで、任意のテーマで N4 難易度以上をクリアする + + 困難に挑戦する + 困難を恐れない。 + 自動ローグで、任意のテーマで N8 難易度以上をクリアする + + 人間を超越 + 普通の人よりも上手くプレイしています。 + 自動ローグで、任意のテーマで N12 難易度以上をクリアする + + 仙術杯選手 + 本物の仙術だなあ。 + 自動ローグで、任意のテーマで N15 難易度以上をクリアする + diff --git a/src/MaaWpfGui/Res/Localizations/ko-kr.xaml b/src/MaaWpfGui/Res/Localizations/ko-kr.xaml index 2c43248aaf..e86c67ef9a 100644 --- a/src/MaaWpfGui/Res/Localizations/ko-kr.xaml +++ b/src/MaaWpfGui/Res/Localizations/ko-kr.xaml @@ -266,7 +266,7 @@ 3. 사용자 그룹이나 댓글 섹션 등 다른 곳에서 개발 버전 문제를 논의하지 마세요. 이는 공공 리소스를 차지하고 신속한 피드백과 해결을 방해합니다. 4. 비개발자 사용자에게 개발 버전 업데이트 방법을 공유하지 마세요. 불필요한 혼란과 위험을 피하기 위해서입니다. -설정 - 이슈 제보 링크를 클릭하여 이 팝업을 취소할 수 있습니다. 이해와 협조에 감사드립니다! +설정 - 문제 신고 링크를 클릭하여 이 팝업을 취소할 수 있습니다. 이해와 협조에 감사드립니다! 업데이트 채널 aria2로 다운로드 소프트웨어 업데이트 @@ -955,7 +955,7 @@ C:\\leidian\\LDPlayer9 문제를 신고하기 전에 자주 묻는 질문에 해당하지 않는지 확인해 주세요 먼저 업그레이드 후 새 버전에서도 동일한 문제가 발생하는지 테스트해 보시고, 일반적인 문제가 아님을 확인한 후 피드백해 주세요. 자주 묻는 질문 - 이슈 제보 + 문제 신고 지원용 ZIP 파일 생성 Debug 폴더 열기 지원용 ZIP 파일이 성공적으로 생성되었습니다 @@ -1086,4 +1086,285 @@ Microsoft Visual C++ redistributable 2015-2022(x64) 가 설치되지 않았거 알림 반복 간격 (분) 작업 실행 시간이 {0}분을 초과했습니다(현재 {1}분 동안 실행 중입니다). 장시간 추가 로그 업데이트가 없을 경우 수동 조치가 필요할 수 있습니다. + + 🎉 도전과제 달성 + 달성 조건: + 도전과제 레벨: + 도전과제 보기 + 도전과제 백업 + 백업 로드 + 해제된 시점: + 도전과제 목록 + 백업 완료 + 도전과제 진행도 복원 완료 + 복원 실패, 파일 형식이 유효하지 않음 + + 뉴비 + 이제 감이 잡혔네요 + 대리 지휘 10회 완료 + + 전문가 + 저보다 더 능숙하네요 + 대리 지휘 100회 완료 + + 고인물 + 클로저 대리 프로그램이 가동 중입니다 + 대리 지휘 1000회 완료 + + YOLO! + "내가 어떻게 해야할지 알고 있어" ㅡ클릭하기 전 어떤 유저의 말 + 1초 내에 팝업 닫기 (못 보셨죠?) + + 전술적 후퇴 + 포기하는게 아니라 전술적 후퇴에요! + 실행 중인 대리 지휘를 수동으로 중단 + + 엿보기 구멍 + 계속 쳐다보는 걸 좋아하는군요.jpg + MAA 모니터링 기능 1회 사용 + + 축하합니다! + 어? 제 MAA가 왜 안 되죠? + MAA 오류 1회 발생 + + 불청객 + 미지의 영역에 발을 들였군요... + 처리되지 않은 예외가 발생으로 크래시 로그 기록 + + 커스터마이징의 달인 + 내 아내임 + 배경 이미지 1회 변경 + + 진짜 가챠 + 시뮬레이션 아니었어요? 내 재화가 어디갔어요? + MAA 뽑기 기능 1회 사용 + + 잘 부탁드려요 + MaaAssistantArknights 사용을 환영합니다 + 소프트웨어 첫 실행 + + 화성인 + 혹시 과거에 살고 계신가요? + 리소스 또는 버전이 현재로부터 3개월 초과 + + 규정 + 제가 계속 지켜보고 있을게요 + MAA 이용 약관 + + 이건 무슨 버튼이지? + 한번 눌러볼까?? + 설정의 버전 뱃지를 클릭 + + 알뜰살뜰 + 이성 하나하나가 아까워요 + 이성 회복 기능 1회 사용 + + 보급 마스터 + 약물 사용이 수준급이시네요 + 이성 회복 기능 10회 사용 + + 영구기관 + 이성이 마르지 않는 것 같아요 + 이성 회복 기능 50회 사용 + + XX관리 마스터 + 더욱 효율적인 계획 + 예약 실행 1회 사용 + + 범용 자동화 솔루션 0.3.2.9f2 + PRTS의 자동화 배치를 완전히 마스터했습니다 + 예약 실행 100회 사용 + + 겜안분 + 박사님, 쉬시면 안돼요!!! + 「48시간 미만 남은 이성 회복제 사용」으로 한 번에 8개 이상의 이성 회복제 사용 + + 특이점 돌파 + 스테이지를 소멸시킬 정도의 대리 지휘를 수행했어요 + 단일 스테이지에서 100회 이상 대리 지휘 진행 + + Mirror~~☆ + MirrorChyan이다! 우리가 살았어! + MirrorChyan 첫 사용 성공 + + 이게... 맞나요? + 누가 제 키보드 키를 빼먹었나요? + MirrorChyan CDK 입력 오류 + + 공익제보 + 가장 위험한 것은 내부의 적이에요 + 문제 신고 1회 진행 + + 혹시 한국인? + 급한건 알겠지만 잠시만요 + 리소스 업데이트 없이 「자동 전투」 기능 사용 + + 컨닝 페이퍼 + 진정한 방치 플레이 + 「자동 전투」 기능으로 1회 클리어 + + 끝까지 방치 + MAA야 너만 믿고 있을게 + 「자동 전투」 기능으로 10회 클리어 + + 딸깍 + 보스 기믹이 뭔가요? + 「자동 전투」 기능으로 100회 클리어 + + 인디언식 기우제는 실패했어요 + 일이 이렇게 되었으니, 잠이나 자야겠어요 + 공개모집에서 누적 500회 동안 6성 태그가 나오지 않음 + + 분명 MAA가 문제일거야! + 으아아아아아아아아아! + 공개모집에서 연속 500회 동안 6성 태그가 나오지 않음 + + 티끌 모아 태산 + 티끌을 모으면 태산이 된다는걸 아시는군요 + 「지원 유닛을 사용해 크레딧 수급」 기능을 5회 이상 사용 + + 베타 테스터 + 테스트 중인 베타 버전을 체험하셨어요 + MAA를 베타 버전으로 업데이트 + + 슈퍼 베타 테스터 + 최신 개발 버전을 사용하셨어요 (문제가 발생하면 문제 신고로 가주세요!) + MAA를 개발 버전으로 업데이트 + + DEBUG 테스터 + 이 버전을 사용할 수 있는 분들은 일반인이 아닐거에요 + DEBUG 버전의 MAA 사용 + + 대체 불가능 + 다른 작업을 사용해야 할까요? + 「자동지휘 - 자동 편성」 사용 시 최소 2명의 오퍼레이터가 부족 + + Hight Ping 전사 + "Disconnect" + 평균 스크린샷 시간이 800ms 초과 + + 좀 많이 느린 거 아닌가요 + 스크린샷을 찍는데 좀 힘들어 보이네요 + 평균 스크린샷 시간이 400ms에서 800ms 사이 + + 스크린샷 도전 · Normal + 스크린샷 향상을 시도해보는 건 어떨까요? + 평균 스크린샷 시간이 400ms 미만 + + 스크린샷 도전 · Fast + 스크린샷이 너무 빨라서 저도 반응을 못했네요 + 평균 스크린샷 시간이 100ms 미만 + + 스크린샷 도전 · Ultra + 스크린샷 속도가 90%의 유저보다 빨라요 + 평균 스크린샷 시간이 10ms 미만 + + 스크린샷 도전 · Beyond + 당신은 MAA의 숨겨진 힘을 발견했습니다 + 평균 스크린샷 시간이 5ms 미만 + + 뜻밖의 행운 + 이 도전과제를 클리어한 사람은 단챠로 6성을 뽑았다고 하네요 + MAA 시작 시 매우! 작은! 확률로 발동 + + Github에게 미움받는 존재 + Github의 서버는 우주보다 멀리있다네요 + 리소스 다운로드 실패 3회 초과 + + 도움 요청 방치족 + 저를 방치하고 있는 건가요, 아니면 끄는 걸 까먹은 건가요? + 창 최소화 후 장시간 조작하지 않음 + + 오늘부터 MAA의 지지를 철회한다 + MAA와 나는 한몸으로 일체가 된다 + 하루 내에 작업 시작 3회 초과 + + 종점에 첫 도착 + 그저... 그 곳에 닿을 수만 있다면... + MAA를 사용해 로그라이크 1회 클리어 + + 종점에 다시 도착 + 여기는 이미 여러 번 와봤어요 + MAA를 사용해 로그라이크 5회 클리어 + + 종점에 살고 있나요? + 눈 감고도 여기까지 올 수 있어요 + MAA를 사용해 로그라이크 10회 클리어 + + 실패는 성공의 어머니 + 언젠가 성공할거에요... 실패는 성공의 어머니니까... + MAA의 로그라이크 탐험 포기 100회 + + 꽉 찼어요 꽉 찼어 + 캔낫, 네 가게에서 가장 비싼 걸 내놔봐! + 로그라이크의 오리지늄 각뿔이 999에 도달 + + 개추 한번 + 좋아요는 작성자에게 가장 큰 격려에요! + 작업에 좋아요 1회 + + 적극적 상호작용 + 좋아요 10회, 커뮤니티 상호작용에 적극 참여 + 작업에 누적 좋아요 10회 + + 개추 요정 + 좋아요 50회, 이미 개추 마스터가 되셨네요! + 작업에 누적 좋아요 50회 + + 정말 우유부단하네요 + 아 맞아, 아 아닌가? 아 맞는거같은데...아 잠깐...! + 작업 시작 후 바로 중지 + + 아이고 + 사고가 났네요... + 대리 작전에서 실수 발생 + + 긴 작업 + 좀 더 기다려볼까요? + 타임아웃 알림 발생 + + 개근 선봉대 + 당신은 이미 박사님을 대체했어요 + 작업 시작 후 3시간 이상 연속 온라인 + + 매일 출석 + 열심히 7일 연속 출석했지만 아쉽게도 보상은 없어요 + 7일 연속 MAA 사용 + + 개근상 + 이번 달 개근상을 수여할게요 + 30일 연속 MAA 사용 + + 연중무휴 + MAA의 인권을 보장해주세요! + 365일 연속 MAA 사용 + + 우리와 함께 + 설마 오늘 접속한 사람이 정말 있을까요? + 4월 1일에 MAA 실행하기 + + 심야 작전 + 당신은 심야 작전 전문가인가요? + 심야 0시 이후 실행 + + 새해 복 많이 받으세요 + 행운과 6성이 함께 찾아옵니다! + 새해 기간에 MAA 열기 + + 간단간단 + 이 정도 난이도로는 저를 막을 수 없어요 + MAA를 사용해 임의 로그라이크 4승천 이상 클리어 + + 어려움에 도전 + 용감한 MAA는 어려움을 두려워하지 않아요 + MAA를 사용해 임의 로그라이크 8승천 이상 클리어 + + 인간 초월 + 저보다 더 잘하는거 같은데요? + MAA를 사용해 임의 로그라이크 12승천 이상 클리어 + + 프로 선수 + MAA, 완전 프로같았어요 + MAA를 사용해 임의 로그라이크 15승천 이상 클리어 + diff --git a/src/MaaWpfGui/Res/Localizations/zh-cn.xaml b/src/MaaWpfGui/Res/Localizations/zh-cn.xaml index 6e1ac4f32c..a34c3cbefe 100644 --- a/src/MaaWpfGui/Res/Localizations/zh-cn.xaml +++ b/src/MaaWpfGui/Res/Localizations/zh-cn.xaml @@ -26,6 +26,7 @@ 界面主题 热键设置 更新设置 + 成就设置 关于我们 问题反馈 MAA 版本 @@ -433,14 +434,14 @@ 自选日志日期格式 日期格式字符串 是否立刻重启应用更改? - 将模拟器分辨率更改为1920x1080! - 确保模拟器设置为1920x1080 + 将模拟器分辨率更改为 1920x1080! + 确保模拟器设置为 1920x1080 允许吃源石保存状态 该功能属于危险功能。按下 「确认」 键即视为你了解并愿意承担可能出现的风险。 使用备选关卡 使用剩余理智 在完成主任务后刷取指定关卡,不使用理智药/碎石 - 无限吃48小时内过期的理智药 + 无限吃 48 小时内过期的理智药 隐藏当日不开放关卡 隐藏代理倍率 显示基建计划 @@ -1089,4 +1090,285 @@ C:\\leidian\\LDPlayer9。\n 提醒间隔时间(分钟) 任务已运行超过 {0} 分钟(当前已运行 {1} 分钟)。如果长时间无进一步日志更新,可能需要手动干预。 + + 🎉 达成成就 + 达成条件: + 成就等级: + 查看成就 + 备份成就 + 加载备份 + 解锁于: + 成就列表 + 已备份到 + 已恢复成就进度 + 恢复失败,文件格式无效 + + 战术实战 + 你已经上手了。 + 完成 10 次代理通关 + + 代理先锋 + 比我还熟练。 + 完成 100 次代理通关 + + ZOOT + 可露希尔代理程序正常运转中。 + 完成 1000 次代理通关 + + YOLO! + “我知道我在做什么。”——某用户在点击之前。 + 1s 内关闭弹窗(你大概是没看吧) + + 临阵脱逃 + 我不是放弃,是战术性撤退! + 手动中止一次正在运行的代理 + + 窥屏 + 喜欢盯着看是吧.jpg + 使用一次牛牛监控 + + 喜报 + 我的MAA怎么用不了了? + 触发过MAA的报错 + + 不速之客 + 你触发了未知的领域。 + 出现未处理异常并被崩溃日志记录 + + 自定义达人 + 咱就是二次元! + 更换一次背景图片 + + 真正的抽卡 + 不是模拟抽卡吗?怎么真抽了??? + 使用一次牛牛抽卡 + + 初来乍到 + 欢迎使用 MaaAssistantArknights. + 第一次启动软件 + + 火星人 + 远古版本好用吗? + 资源时间或版本时间距今超过 3 个月 + + 严重错讠 + 我会一直注视着你。 + MAA员工守则 + + 这也能点? + 这是什么?版本号,点一下。 + 点击设置页的版本号徽章 + + 精打细算 + 每点理智都用得恰到好处。 + 使用理智回复功能 1 次 + + 补给大师 + 你对药剂的使用炉火纯青。 + 使用理智回复功能 10 次 + + 永动机 + 你的理智似乎永远用不完。 + 使用理智回复功能 50 次 + + ××管理大师 + 更有效的规划。 + 定时执行 1 次 + + 泛用型自动化解决方案0.3.2.9f2 + 您已熟知 PRTS 的自动化部署。 + 定时执行 100 次 + + 要过期了 + 赶紧冲哇!!! + 使用「无限吃 48 小时内过期的理智药」,单次任务使用 8 瓶以上临期理智药 + + 超限代理 + 你进行了一次能将大道磨灭的代理任务。 + 在单次任务中,进行超过 100 次代理指挥作战 + + Mirror~~☆ + 是 Mirror酱!我们有救了! + 初次成功使用 Mirror酱 + + 这……对吗? + 谁偷吃了我的字母饼干? + Mirror酱 CDK 填写错误 + + 隐患勘察 + 一定要杜绝的是,来自内部的风险。 + 进行一次问题反馈 + + 急急国王 + 我知道你很急,但你先别急。 + 在未更新资源的情况下使用自动战斗 + + 抄作业 + 真正的摆完挂机。 + 使用 「自动战斗」 功能通关 1 次 + + 一摆到底 + 活动全交给牛牛打。 + 使用 「自动战斗」 功能通关 10 次 + + 下次还填非常简单 + 到最后也不知道关底 boss 什么机制。 + 使用 「自动战斗」 功能通关 100 次 + + 玄学无效 + 事已至此,先睡觉吧。 + 公招中累计 500 次没出现六星 tag + + 一定是 MAA 干的! + 乆乆乆乆乆乆乆乆乆乆! + 公招中连续 500 次没出现六星 tag + + 蚊子腿也是肉! + 您深刻理解了积少成多的道理。 + 使用 「借助战打 OF-1」 功能超过 5 次 + + 测试先锋 + 您率先体验了处于测试中的版本。 + 将 MAA 更新至公测版 + + 超级测试先锋 + 您使用了最新的测试版本(出现任何新的问题请前往问题反馈!) + 将 MAA 更新至内测版 + + DEBUG 急先锋 + 能下到这个版本的都不是一般人。 + 使用未发布版本的 MAA + + 无可替代 + 或许我应该换一份作业? + 使用 「自动战斗 - 自动编队」 时,缺少至少两名干员 + + 高 ping 战士 + “kale” + 平均截图用时超过 800ms + + 是不是有点太慢了 + 截图显得有点吃力。 + 平均截图用时在 400ms 到 800ms 之间 + + 截图挑战 · Normal + 要不试试截图增强? + 平均截图用时小于 400ms + + 截图挑战 · Fast + 截图快得我都没反应过来。 + 平均截图用时小于 100ms + + 截图挑战 · Ultra + 截图速度击败了 90% 的 MAA 用户。 + 平均截图用时小于 10ms + + 截图挑战 · Beyond + 你发现了 MAA 的隐藏力量。 + 平均截图用时小于 5ms + + Lucky + 据说能看到这条成就的人都三连金了。 + 启动 MAA 时有极!小!概率触发 + + 网络折磨人 + 比宇宙更远的地方,是 Github 的服务器。 + 下载资源失败超过 3 次 + + 摇人挂机党 + 你是在挂机,还是忘了关? + 窗口最小化后长时间不操作 + + 不离不弃 + 是信任,也是依赖。 + 一天内开始任务超过 3 次 + + 初抵终点 + 只要能到达...那个地方... + 使用牛牛通关肉鸽 1 次 + + 再临终点 + 这条道路我已走过多次。 + 使用牛牛通关肉鸽 5 次 + + 终点常客 + 闭着眼睛都能走到这。 + 使用牛牛通关肉鸽 10 次 + + 失败是成功之母 + 终有一天,我们会到达成功的彼岸,失败是成功的阶梯。 + 牛牛放弃探索 100 次 + + 满了满了 + 坎诺特,把你店里最贵的端出来! + 肉鸽源石锭到达 999 + + 鼓励一下 + 点赞是对作者最大的鼓励! + 给作业点赞 1 次 + + 积极互动 + 点赞 10 次,积极参与社区互动。 + 给作业累计点赞 10 次 + + 点赞达人 + 点赞 50 次,你已经成为点赞大师! + 给作业累计点赞 50 次 + + 真是纠纠又结结啊 + 啊对的对的,啊不对不对,啊对的对的,啊不对不对… + 在开始任务后马上又停止 + + 哎呀 + 出事故了…… + 代理作战出现失误 + + 漫长的任务 + 要不,咱再等等? + 触发超时提醒 + + 全勤先锋 + 你,已经成为博士的替身了。 + 开始任务能连续在线超过 3 小时 + + 天天打卡 + 连续签到七天,可惜没有奖励。 + 连续 7 天使用 MAA + + 全勤奖 + 这个月的全勤奖发一下。 + 连续 30 天使用 MAA + + 全年无休 + 就是你天天在压榨牛牛? + 连续 365 天使用 MAA + + 愚你同行 + 不会真有人今天上线吧? + 在 4 月 1 日 打开程序 + + 午夜作战 + 你是深夜行动专家吗? + 在午夜 0 点后 启动 + + 新春快乐 + 好运和六星一起到来! + 在农历新年期间打开程序 + + 简简单单 + 这种难度难不倒我。 + 使用牛牛通关任意肉鸽 N4 以上难度 + + 挑战困难 + 勇敢牛牛不怕困难。 + 使用牛牛通关任意肉鸽 N8 以上难度 + + 超越人类 + 比一般人玩的都好。 + 使用牛牛通关任意肉鸽 N12 以上难度 + + 仙术杯选手 + 仙术牛牛真仙术。 + 使用牛牛通关任意肉鸽 N15 以上难度 + diff --git a/src/MaaWpfGui/Res/Localizations/zh-tw.xaml b/src/MaaWpfGui/Res/Localizations/zh-tw.xaml index 4c730d2ee0..41bad0da93 100644 --- a/src/MaaWpfGui/Res/Localizations/zh-tw.xaml +++ b/src/MaaWpfGui/Res/Localizations/zh-tw.xaml @@ -1089,4 +1089,286 @@ C:\\leidian\\LDPlayer9。\n 提醒間隔時間(分鐘) 任務已運行超過 {0} 分鐘(當前已運行 {1} 分鐘)。如果長時間無進一步日誌更新,可能需要手動干預。 + + 🎉 達成成就 + 達成條件: + 成就等級: + 查看成就 + 備份成就 + 載入備份 + 解鎖於: + 成就清單 + 已備份到 + 已恢復成就進度 + 恢復失敗,檔格式無效 + + 戰術實戰 + 你已經上手了。 + 完成 10 次代理通關 + + 代理先鋒 + 比我還熟練。 + 完成 100 次代理通關 + + ZOOT + 可露希爾代理程式正常運轉中。 + 完成 1000 次代理通關 + + YOLO! + “我知道我在做什麼。”——某用戶在點擊之前。 + 1s 內關閉彈窗(你大概是沒看吧) + + 臨陣脫逃 + 我不是放棄,是戰術性撤退! + 手動中止一次正在運行的代理 + + 窺屏 + 喜歡盯著看是吧.jpg + 使用一次牛牛監控 + + 喜報 + 我的MAA怎麼用不了了? + 觸發過MAA的報錯 + + 不速之客 + 你觸發了未知的領域。 + 出現未處理異常並被崩潰日誌記錄 + + 自定義達人 + 咱就是二次元! + 更換一次背景圖片 + + 真正的抽卡 + 不是類比抽卡嗎? 怎麼真抽了??? + 使用一次牛牛抽卡 + + 初來乍到 + 歡迎使用 MaaAssistantArknights. + 第一次啟動軟體 + + 火星人 + 遠古版本好用嗎? + 資源時間或版本時間距今超過 3 個月 + + 嚴重錯讠 + 我會一直注視著你。 + MAA員工守則 + + 這也能點? + 這是什麼? 版本號,點一下。 + 點擊設置頁的版本號徽章 + + 精打細算 + 每點理智都用得恰到好處。 + 使用理智回復功能 1 次 + + 補給大師 + 你對藥劑的使用爐火純青。 + 使用理智回復功能 10 次 + + 永動機 + 你的理智似乎永遠用不完。 + 使用理智回復功能 50 次 + + ××管理大師 + 更有效的規劃。 + 定時執行 1 次 + + 泛用型自動化解決方案0.3.2.9f2 + 您已熟知 PRTS 的自動化部署。 + 定時執行 100 次 + + 要過期了 + 趕緊沖哇!! + 使用「無限吃 48 小時內過期的理智藥」,單次任務使用 8 瓶以上臨期理智藥 + + 超限代理 + 你進行了一次能將大道磨滅的代理任務。 + 在單次任務中,進行超過 100 次代理指揮作戰 + + Mirror~~☆ + 是 Mirror醬! 我們有救了! + 初次成功使用 Mirror醬 + + 這……對嗎? + 誰偷吃了我的字母餅乾? + Mirror醬 CDK 填寫錯誤 + + 隱患勘察 + 一定要杜絕的是,來自內部的風險。 + 進行一次問題反饋 + + 急急國王 + 我知道你很急,但你先別急。 + 在未更新資源的情況下使用自動戰鬥 + + 抄作業 + 真正的擺完掛機。 + 使用 「自動戰鬥」 功能通關 1 次 + + 一擺到底 + 活動全交給牛牛打。 + 使用 「自動戰鬥」 功能通關 10 次 + + 下次還填非常簡單 + 到最後也不知道關底 boss 什麼機制。 + 使用 「自動戰鬥」 功能通關 100 次 + + 玄學無效 + 事已至此,先睡覺吧。 + 公招中累計 500 次沒出現六星 tag + + 一定是 MAA 乾的! + 乆乆乆乆乆乆乆乆乆乆! + 公招中連續 500 次沒出現六星 tag + + 蚊子腿也是肉! + 您深刻理解了積少成多的道理。 + 使用 「借助戰打 OF-1」 功能超過 5 次 + + 測試先鋒 + 您率先體驗了處於測試中的版本。 + 將 MAA 更新至公測版 + + 超級測試先鋒 + 您使用了最新的測試版本(出現任何新的問題請前往問題反饋! ) + 將 MAA 更新至內測版 + + DEBUG 急先鋒 + 能下到這個版本的都不是一般人。 + 使用未發佈版本的 MAA + + 無可替代 + 或許我應該換一份作業? + 使用 「自動戰鬥 - 自動編隊」 時,缺少至少兩名幹員 + + 高 ping 戰士 + “kale” + 平均截圖用時超過 800ms + + 是不是有點太慢了 + 截圖顯得有點吃力。 + 平均截圖用時在 400ms 到 800ms 之間 + + 截圖挑戰 · Normal + 要不試試截圖增強? + 平均截圖用時小於 400ms + + 截圖挑戰 · Fast + 截圖快得我都沒反應過來。 + 平均截圖用時小於 100ms + + 截圖挑戰 · Ultra + 截圖速度擊敗了 90% 的 MAA 使用者。 + 平均截圖用時小於 10ms + + 截圖挑戰 · Beyond + 你發現了 MAA 的隱藏力量。 + 平均截圖用時小於 5ms + + Lucky + 據說能看到這條成就的人都三連金了。 + 啟動 MAA 時有極! 小! 概率觸發 + + 網络折磨人 + 比宇宙更遠的地方,是 Github 的伺服器。 + 下載資源失敗超過 3 次 + + 搖人掛機黨 + 你是在掛機,還是忘了關? + 視窗最小化后長時間不操作 + + 不離不棄 + 是信任,也是依賴。 + 一天內開始任務超過 3 次 + + 初抵終點 + 只要能到達... 那個地方... + 使用牛牛通關肉鴿 1 次 + + 再臨終點 + 這條道路我已走過多次。 + 使用牛牛通關肉鴿 5 次 + + 終點常客 + 閉著眼睛都能走到這。 + 使用牛牛通關肉鴿 10 次 + + 失敗是成功之母 + 終有一天,我們會到達成功的彼岸,失敗是成功的階梯。 + 牛牛放棄探索 100 次 + + 滿了滿了 + 坎諾特,把你店裡最貴的端出來! + 肉鴿源石錠到達 999 + + 鼓勵一下 + 點讚是對作者最大的鼓勵! + 給作業點讚 1 次 + + 積極互動 + 點讚 10 次,積极參與社區互動。 + 給作業累計點讚 10 次 + + 點讚達人 + 點讚 50 次,你已經成為點讚大師! + 給作業累計點讚 50 次 + + 真是糾糾又結結啊 + 啊對的對的,啊不對不對,啊對的對的,啊不對不對... + 在開始任務後馬上又停止 + + 哎呀 + 出事故了...... + 代理作戰出現失誤 + + 漫長的任務 + 要不,咱再等等? + 觸發超時提醒 + + 全勤先鋒 + 你,已經成為博士的替身了。 + 開始任務能連續在線超過 3 小時 + + 天天打卡 + 連續簽到七天,可惜沒有獎勵。 + 連續 7 天使用 MAA + + 全勤獎 + 這個月的全勤獎發一下。 + 連續 30 天使用 MAA + + 全年無休 + 就是你天天在壓榨牛牛? + 連續 365 天使用 MAA + + 愚你同行 + 不會真有人今天上線吧? + 在 4 月 1 日 打開程式 + + 午夜作戰 + 你是深夜行動專家嗎? + 在午夜 0 點後 啟動 + + 新春快樂 + 好運和六星一起到來! + 在農曆新年期間打開程式 + + 簡簡單單 + 這種難度難不倒我。 + 使用牛牛通關任意肉鴿 N4 以上難度 + + 挑戰困難 + 勇敢牛牛不怕困難。 + 使用牛牛通關任意肉鴿 N8 以上難度 + + 超越人類 + 比一般人玩的都好。 + 使用牛牛通關任意肉鴿 N12 以上難度 + + 仙術杯選手 + 仙術牛牛真仙術。 + 使用牛牛通關任意肉鴿 N15 以上難度 + + diff --git a/src/MaaWpfGui/Res/Themes/Dark.xaml b/src/MaaWpfGui/Res/Themes/Dark.xaml index c932f3d8fe..1d090b8639 100644 --- a/src/MaaWpfGui/Res/Themes/Dark.xaml +++ b/src/MaaWpfGui/Res/Themes/Dark.xaml @@ -45,6 +45,32 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/MaaWpfGui/Res/Themes/Light.xaml b/src/MaaWpfGui/Res/Themes/Light.xaml index c13550d9cb..98169f1ea7 100644 --- a/src/MaaWpfGui/Res/Themes/Light.xaml +++ b/src/MaaWpfGui/Res/Themes/Light.xaml @@ -25,6 +25,31 @@ Opacity="0.75" Color="#eeeeee" /> + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/MaaWpfGui/Services/Managers/IMainWindowManager.cs b/src/MaaWpfGui/Services/Managers/IMainWindowManager.cs index d8c08992ed..216d648f18 100644 --- a/src/MaaWpfGui/Services/Managers/IMainWindowManager.cs +++ b/src/MaaWpfGui/Services/Managers/IMainWindowManager.cs @@ -11,6 +11,7 @@ // but WITHOUT ANY WARRANTY // +using System; using System.Windows; namespace MaaWpfGui.Services.Managers @@ -63,5 +64,7 @@ namespace MaaWpfGui.Services.Managers /// /// The if it is visible, or null. Window GetWindowIfVisible(); + + event EventHandler WindowRestored; } } diff --git a/src/MaaWpfGui/Services/Managers/MainWindowManager.cs b/src/MaaWpfGui/Services/Managers/MainWindowManager.cs index b13976a940..282ac6ba21 100644 --- a/src/MaaWpfGui/Services/Managers/MainWindowManager.cs +++ b/src/MaaWpfGui/Services/Managers/MainWindowManager.cs @@ -49,6 +49,12 @@ namespace MaaWpfGui.Services.Managers bool useTrayIcon = Convert.ToBoolean(ConfigurationHelper.GetGlobalValue(ConfigurationKeys.UseTray, bool.TrueString)); SetUseTrayIcon(useTrayIcon); + + _previousState = GetWindowState(); + if (_previousState == WindowState.Minimized) + { + _minimizedSince = DateTime.Now; + } } /// @@ -100,6 +106,9 @@ namespace MaaWpfGui.Services.Managers ((RootView)MainWindow).NotifyIcon.notifyIcon.Visibility = useTrayIcon ? Visibility.Visible : Visibility.Collapsed; } + private WindowState? _previousState = null; + private DateTime? _minimizedSince = null; + /// /// Handle the main window's state changed event /// @@ -107,10 +116,35 @@ namespace MaaWpfGui.Services.Managers /// The event arguments. private void MainWindowStateChanged(object sender, EventArgs e) { + var currentState = MainWindow.WindowState; + if (ShouldMinimizeToTray && ShouldUseTrayIcon) { - ChangeVisibility(MainWindow.WindowState != WindowState.Minimized); + ChangeVisibility(currentState != WindowState.Minimized); } + + // 检测进入 Minimized + if (_previousState != WindowState.Minimized && currentState == WindowState.Minimized) + { + _minimizedSince = DateTime.Now; + } + + // 触发事件:从 Minimized 恢复 + if (_previousState == WindowState.Minimized && currentState != WindowState.Minimized) + { + if (_minimizedSince != null) + { + var duration = DateTime.Now - _minimizedSince.Value; + if (duration.TotalHours >= 1) + { + AchievementTrackerHelper.Instance.Unlock(AchievementIds.AfkWatcher); + } + } + + WindowRestored?.Invoke(this, EventArgs.Empty); + } + + _previousState = currentState; } /// @@ -144,5 +178,10 @@ namespace MaaWpfGui.Services.Managers return MainWindow; } + + /// + /// 在窗口从最小化恢复时触发 + /// + public event EventHandler WindowRestored; } } diff --git a/src/MaaWpfGui/States/RunningState.cs b/src/MaaWpfGui/States/RunningState.cs index f370c68d54..57f7353452 100644 --- a/src/MaaWpfGui/States/RunningState.cs +++ b/src/MaaWpfGui/States/RunningState.cs @@ -101,6 +101,11 @@ namespace MaaWpfGui.States var elapsedMinutes = (DateTime.Now - _taskStartTime.Value).TotalMinutes; + if (elapsedMinutes > 3 * 60) + { + AchievementTrackerHelper.Instance.Unlock(AchievementIds.ProxyOnline3Hours); + } + // 如果任务运行时间未超过超时时间,则直接返回 if (elapsedMinutes <= TaskTimeoutMinutes) { @@ -113,6 +118,8 @@ namespace MaaWpfGui.States TaskTimeoutMinutes, Math.Round(elapsedMinutes)); + AchievementTrackerHelper.Instance.Unlock(AchievementIds.LongTaskTimeout); + TimeoutOccurred?.Invoke(this, message); } diff --git a/src/MaaWpfGui/ViewModels/UI/CopilotViewModel.cs b/src/MaaWpfGui/ViewModels/UI/CopilotViewModel.cs index 2cfebbe4be..bd4e5f9334 100644 --- a/src/MaaWpfGui/ViewModels/UI/CopilotViewModel.cs +++ b/src/MaaWpfGui/ViewModels/UI/CopilotViewModel.cs @@ -520,13 +520,19 @@ namespace MaaWpfGui.ViewModels.UI // ReSharper disable once UnusedMember.Global public void LikeWebJson() { - RateCopilot(CopilotId); + Task.Run(async () => + { + if (await RateCopilot(CopilotId) == PrtsStatus.Success) + { + AchievementTrackerHelper.Instance.AddProgressToGroup(AchievementIds.CopilotLikeGroup); + } + }); } // ReSharper disable once UnusedMember.Global public void DislikeWebJson() { - RateCopilot(CopilotId, false); + _ = RateCopilot(CopilotId, false); } #endregion 方法 @@ -708,6 +714,7 @@ namespace MaaWpfGui.ViewModels.UI AddLog(LocalizationHelper.GetString("UnsupportedStages") + $" {copilot.StageName}", UiLogColor.Error, showTime: false); navigateName = FindStageName(copilot.Documentation?.Title ?? string.Empty); _ = Task.Run(ResourceUpdater.ResourceUpdateAndReloadAsync); + AchievementTrackerHelper.Instance.Unlock(AchievementIds.MapOutdated); } CopilotTaskName = navigateName; @@ -774,6 +781,7 @@ namespace MaaWpfGui.ViewModels.UI { AddLog(LocalizationHelper.GetString("UnsupportedStages") + $" {copilot.StageName}", UiLogColor.Error, showTime: false); _ = Task.Run(ResourceUpdater.ResourceUpdateAndReloadAsync); + AchievementTrackerHelper.Instance.Unlock(AchievementIds.MapOutdated); } if (writeToCache) @@ -1076,7 +1084,7 @@ namespace MaaWpfGui.ViewModels.UI if (model.CopilotId > 0 && _copilotIdList.Remove(model.CopilotId) && _copilotIdList.IndexOf(model.CopilotId) == -1) { - RateCopilot(model.CopilotId); + _ = RateCopilot(model.CopilotId); } break; @@ -1293,11 +1301,11 @@ namespace MaaWpfGui.ViewModels.UI } } - private async void RateCopilot(int copilotId, bool isLike = true) + private async Task RateCopilot(int copilotId, bool isLike = true) { if (copilotId <= 0 || _recentlyRatedCopilotId.Contains(copilotId)) { - return; + return PrtsStatus.NotFound; } var result = await RateWebJsonAsync(copilotId, isLike ? "Like" : "Dislike"); @@ -1312,6 +1320,8 @@ namespace MaaWpfGui.ViewModels.UI AddLog(LocalizationHelper.GetString("FailedToLikeWebJson"), UiLogColor.Error, showTime: false); break; } + + return result; } private async Task VerifyCopilotListTask() @@ -1354,6 +1364,7 @@ namespace MaaWpfGui.ViewModels.UI { AddLog(LocalizationHelper.GetString("UnsupportedStages") + $" {name}", UiLogColor.Error, showTime: false); _ = Task.Run(ResourceUpdater.ResourceUpdateAndReloadAsync); + AchievementTrackerHelper.Instance.Unlock(AchievementIds.MapOutdated); return false; } } diff --git a/src/MaaWpfGui/ViewModels/UI/RecognizerViewModel.cs b/src/MaaWpfGui/ViewModels/UI/RecognizerViewModel.cs index b71fc37c09..115831e79e 100644 --- a/src/MaaWpfGui/ViewModels/UI/RecognizerViewModel.cs +++ b/src/MaaWpfGui/ViewModels/UI/RecognizerViewModel.cs @@ -845,6 +845,8 @@ namespace MaaWpfGui.ViewModels.UI return; } + AchievementTrackerHelper.Instance.Unlock(AchievementIds.RealGacha); + GachaShowDisclaimer = false; } @@ -1048,6 +1050,8 @@ namespace MaaWpfGui.ViewModels.UI // 点击按钮开始 Peep Peeping = true; + AchievementTrackerHelper.Instance.Unlock(AchievementIds.PeekScreen); + // 如果没任务在运行,需要先连接,并标记是由 Peep() 方法启动的 Peep if (Idle) { diff --git a/src/MaaWpfGui/ViewModels/UI/SettingsViewModel.cs b/src/MaaWpfGui/ViewModels/UI/SettingsViewModel.cs index bb196b4fcc..b18b9c80d8 100644 --- a/src/MaaWpfGui/ViewModels/UI/SettingsViewModel.cs +++ b/src/MaaWpfGui/ViewModels/UI/SettingsViewModel.cs @@ -110,6 +110,11 @@ namespace MaaWpfGui.ViewModels.UI /// public static IssueReportUserControlModel IssueReportSettings { get; } = IssueReportUserControlModel.Instance; + /// + /// Gets 成就model + /// + public static AchievementSettingsUserControlModel AchievementSettings { get; } = AchievementSettingsUserControlModel.Instance; + #endregion 设置界面Model /// @@ -141,6 +146,7 @@ namespace MaaWpfGui.ViewModels.UI LocalizationHelper.GetString("BackgroundSettings"), LocalizationHelper.GetString("ExternalNotificationSettings"), LocalizationHelper.GetString("HotKeySettings"), + LocalizationHelper.GetString("AchievementSettings"), LocalizationHelper.GetString("UpdateSettings"), LocalizationHelper.GetString("IssueReport"), LocalizationHelper.GetString("AboutUs"), diff --git a/src/MaaWpfGui/ViewModels/UI/TaskQueueViewModel.cs b/src/MaaWpfGui/ViewModels/UI/TaskQueueViewModel.cs index d992bab7f3..319c673153 100644 --- a/src/MaaWpfGui/ViewModels/UI/TaskQueueViewModel.cs +++ b/src/MaaWpfGui/ViewModels/UI/TaskQueueViewModel.cs @@ -22,6 +22,7 @@ using System.Reflection; using System.Text; using System.Threading.Tasks; using System.Windows; +using System.Windows.Input; using MaaWpfGui.Constants; using MaaWpfGui.Extensions; using MaaWpfGui.Helper; @@ -629,6 +630,8 @@ namespace MaaWpfGui.ViewModels.UI } LinkStart(); + + AchievementTrackerHelper.Instance.AddProgressToGroup(AchievementIds.ScheduleMasterGroup, 1); } private static async Task TimerCanceledAsync(string content = "", string tipContent = "", string buttonContent = "", int seconds = 10) @@ -1247,6 +1250,8 @@ namespace MaaWpfGui.ViewModels.UI } } + private DateTime? _taskStartTime = null; + /// /// Starts. /// @@ -1258,6 +1263,8 @@ namespace MaaWpfGui.ViewModels.UI return; } + _taskStartTime = DateTime.Now; + ClearLog(); var buildDateTimeLong = VersionUpdateSettingsUserControlModel.BuildDateTimeCurrentCultureString; @@ -1409,6 +1416,26 @@ namespace MaaWpfGui.ViewModels.UI await Stop(); SetStopped(); } + + AchievementTrackerHelper.Instance.MissionStartCountAdd(); + AchievementTrackerHelper.Instance.UseDailyAdd(); + } + + public void ManualStop() + { + _ = Stop(); + AchievementTrackerHelper.Instance.Unlock(AchievementIds.TacticalRetreat); + + if (_taskStartTime is null) + { + return; + } + + var duration = DateTime.Now - _taskStartTime.Value; + if (duration.TotalSeconds < 5) + { + AchievementTrackerHelper.Instance.Unlock(AchievementIds.TaskStartCancel); + } } /// diff --git a/src/MaaWpfGui/ViewModels/UI/VersionUpdateViewModel.cs b/src/MaaWpfGui/ViewModels/UI/VersionUpdateViewModel.cs index 19f30fbfaf..874267aa87 100644 --- a/src/MaaWpfGui/ViewModels/UI/VersionUpdateViewModel.cs +++ b/src/MaaWpfGui/ViewModels/UI/VersionUpdateViewModel.cs @@ -703,7 +703,7 @@ public class VersionUpdateViewModel : Screen string? ComparableHash(string version) { - if (IsStdVersion(version)) + if (IsStdVersion(version) || IsBetaVersion(version)) { return version; } @@ -808,6 +808,7 @@ public class VersionUpdateViewModel : Screen return CheckUpdateRetT.NoNeedToUpdate; } + AchievementTrackerHelper.Instance.Unlock(AchievementIds.MirrorChyanFirstUse); return CheckUpdateRetT.OK; } @@ -1050,6 +1051,7 @@ public class VersionUpdateViewModel : Screen break; case Enums.MirrorChyanErrorCode.KeyInvalid: ToastNotification.ShowDirect(LocalizationHelper.GetString("MirrorChyanCdkInvalid")); + AchievementTrackerHelper.Instance.Unlock(AchievementIds.MirrorChyanCdkError); break; case Enums.MirrorChyanErrorCode.ResourceQuotaExhausted: ToastNotification.ShowDirect(LocalizationHelper.GetString("MirrorChyanCdkQuotaExhausted")); @@ -1257,7 +1259,29 @@ public class VersionUpdateViewModel : Screen return false; } - return !IsNightlyVersion(semVersion); + return !semVersion.IsPrerelease; + } + + public bool IsBetaVersion(string? version = null) + { + version ??= _curVersion; + + if (IsDebugVersion(version)) + { + return false; + } + + if (version.StartsWith('c') || version.StartsWith("20") || version.Contains("Local")) + { + return false; + } + + if (!SemVersion.TryParse(version, SemVersionStyles.AllowLowerV, out var semVersion)) + { + return false; + } + + return semVersion.IsPrerelease && !IsNightlyVersion(semVersion); } public static bool IsNightlyVersion(SemVersion version) diff --git a/src/MaaWpfGui/ViewModels/UserControl/Settings/AchievementSettingsUserControlModel.cs b/src/MaaWpfGui/ViewModels/UserControl/Settings/AchievementSettingsUserControlModel.cs new file mode 100644 index 0000000000..baff3b78da --- /dev/null +++ b/src/MaaWpfGui/ViewModels/UserControl/Settings/AchievementSettingsUserControlModel.cs @@ -0,0 +1,195 @@ +// +// MaaWpfGui - A part of the MaaCoreArknights project +// Copyright (C) 2021 MistEO and Contributors +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License v3.0 only as published by +// the Free Software Foundation, either version 3 of the License, or +// any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY +// + +using System; +using System.Collections.Generic; +using System.IO; +using System.Windows; +using System.Windows.Forms; +using System.Windows.Media; +using HandyControl.Controls; +using HandyControl.Data; +using MaaWpfGui.Helper; +using MaaWpfGui.Models; +using MaaWpfGui.Views.UI; +using Stylet; +using Application = System.Windows.Application; + +namespace MaaWpfGui.ViewModels.UserControl.Settings; + +public class AchievementSettingsUserControlModel : PropertyChangedBase +{ + static AchievementSettingsUserControlModel() + { + Instance = new(); + } + + public static AchievementSettingsUserControlModel Instance { get; } + + public void BackupAchievements() + { + using var dialog = new FolderBrowserDialog(); + dialog.SelectedPath = Environment.CurrentDirectory; + dialog.ShowNewFolderButton = true; + + if (dialog.ShowDialog() != DialogResult.OK) + { + return; + } + + var selectedPath = dialog.SelectedPath; + var fileName = $"Achievement_{DateTime.UtcNow:yyyyMMdd_HHmmss}"; + bool success = JsonDataHelper.Set(fileName, AchievementTrackerHelper.Instance.Achievements, selectedPath); + + if (success) + { + var growlInfo = new GrowlInfo + { + IsCustom = true, + Message = $"{LocalizationHelper.GetString("AchievementBackupSuccess")} {Path.Combine(selectedPath, fileName)}.json", + StaysOpen = true, + }; + Growl.Success(growlInfo); + } + else + { + Growl.Error(LocalizationHelper.GetString("AchievementRestoreFailed")); + } + } + + public void RestoreAchievements() + { + var dlg = new Microsoft.Win32.OpenFileDialog + { + Filter = "JSON|*.json", + InitialDirectory = Path.Combine(Environment.CurrentDirectory, "data"), + }; + + if (dlg.ShowDialog() != true) + { + return; + } + + var data = JsonDataHelper.Get(Path.GetFileNameWithoutExtension(dlg.FileName), new Dictionary()); + if (data is { Count: > 0 }) + { + foreach (var pair in data) + { + if (AchievementTrackerHelper.Instance.Get(pair.Key) is not { } existing) + { + continue; + } + + existing.Progress = pair.Value.Progress; + existing.IsUnlocked = pair.Value.IsUnlocked; + existing.UnlockedTime = pair.Value.UnlockedTime; + } + + AchievementTrackerHelper.Instance.Save(); + Growl.Success(LocalizationHelper.GetString("AchievementRestoreSuccess")); + } + else + { + Growl.Error(LocalizationHelper.GetString("AchievementRestoreFailed")); + } + } + + public void OnShowAchievementsClick() + { + var win = new AchievementListWindow + { + Owner = Application.Current.MainWindow, + WindowStartupLocation = WindowStartupLocation.CenterOwner, + }; + win.ShowDialog(); + } + + private static readonly SolidColorBrush _hiddenMedalBrush = Application.Current.Resources["HiddenMedalBrush"] is SolidColorBrush brush ? brush : new(Colors.Gold); + + private static readonly SolidColorBrush _normalMedalBrush = Application.Current.Resources["NormalMedalBrush"] is SolidColorBrush brush ? brush : new(Colors.Silver); + + private int _clickCount = 0; + private static readonly Random _random = new(); + private bool _isTriggered = false; + + public void OnDebugClick() + { + NotifyOfPropertyChange(nameof(Tip)); + if (_isTriggered) + { + ResetDebugState(); + return; + } + + if (Instances.VersionUpdateViewModel.IsDebugVersion()) + { + EnableDebugMode(); + return; + } + + if (++_clickCount < 10) + { + return; + } + + _clickCount = 0; + + bool shouldTriggerDebug = _random.NextDouble() < 0.1; + if (shouldTriggerDebug) + { + EnableDebugMode(); + } + } + + public string Tip => LocalizationHelper.GetPallasString(1, 10); + + private void ResetDebugState() + { + IsDebugVersion = false; + MedalBrush = _normalMedalBrush; + _isTriggered = false; + } + + private void EnableDebugMode() + { + IsDebugVersion = true; + MedalBrush = _hiddenMedalBrush; + _isTriggered = true; + } + + private SolidColorBrush _medalBrush = _normalMedalBrush; + + public SolidColorBrush MedalBrush + { + get => _medalBrush; + set => SetAndNotify(ref _medalBrush, value); + } + + private bool _isDebugVersion = false; + + public bool IsDebugVersion + { + get => _isDebugVersion; + set => SetAndNotify(ref _isDebugVersion, value); + } + + public void UnlockAll() + { + _ = AchievementTrackerHelper.Instance.UnlockAll(); + } + + public void LockAll() + { + AchievementTrackerHelper.Instance.LockAll(); + } +} diff --git a/src/MaaWpfGui/ViewModels/UserControl/Settings/BackgroundSettingsUserControlModel.cs b/src/MaaWpfGui/ViewModels/UserControl/Settings/BackgroundSettingsUserControlModel.cs index 7fa77ec972..e81bde60e6 100644 --- a/src/MaaWpfGui/ViewModels/UserControl/Settings/BackgroundSettingsUserControlModel.cs +++ b/src/MaaWpfGui/ViewModels/UserControl/Settings/BackgroundSettingsUserControlModel.cs @@ -47,6 +47,8 @@ public class BackgroundSettingsUserControlModel : PropertyChangedBase SetAndNotify(ref _backgroundImagePath, value); ConfigurationHelper.SetGlobalValue(ConfigurationKeys.BackgroundImagePath, value); BackgroundImage = RefreshBackgroundImage(value); + + AchievementTrackerHelper.Instance.Unlock(AchievementIds.CustomizationMaster); } } diff --git a/src/MaaWpfGui/ViewModels/UserControl/Settings/ConnectSettingsUserControlModel.cs b/src/MaaWpfGui/ViewModels/UserControl/Settings/ConnectSettingsUserControlModel.cs index 889a7d5b04..d2f7e8880e 100644 --- a/src/MaaWpfGui/ViewModels/UserControl/Settings/ConnectSettingsUserControlModel.cs +++ b/src/MaaWpfGui/ViewModels/UserControl/Settings/ConnectSettingsUserControlModel.cs @@ -829,7 +829,8 @@ public class ConnectSettingsUserControlModel : PropertyChangedBase Width = 800, Height = 481, // (800 - 1 - 1) * 9 / 16 + 32 + 1, Content = new Image { Source = TestLinkImage, }, - WindowStartupLocation = WindowStartupLocation.CenterScreen, + WindowStartupLocation = WindowStartupLocation.CenterOwner, + Owner = Application.Current.MainWindow, }; popupWindow.ShowDialog(); } diff --git a/src/MaaWpfGui/ViewModels/UserControl/Settings/IssueReportUserControlModel.cs b/src/MaaWpfGui/ViewModels/UserControl/Settings/IssueReportUserControlModel.cs index e263bede93..b79383cb04 100644 --- a/src/MaaWpfGui/ViewModels/UserControl/Settings/IssueReportUserControlModel.cs +++ b/src/MaaWpfGui/ViewModels/UserControl/Settings/IssueReportUserControlModel.cs @@ -21,6 +21,7 @@ using System.Windows; using HandyControl.Controls; using HandyControl.Data; using MaaWpfGui.Configuration; +using MaaWpfGui.Constants; using MaaWpfGui.Helper; using MaaWpfGui.Main; using Serilog; @@ -151,6 +152,8 @@ public class IssueReportUserControlModel : PropertyChangedBase // ReSharper disable once UnusedMember.Global public void SetAcknowledgedNightlyWarning() { + // 其实不应该放这里,但懒得写一个新的方法,就塞到这里了 + AchievementTrackerHelper.Instance.Unlock(AchievementIds.ProblemFeedback); VersionUpdateSettingsUserControlModel.Instance.HasAcknowledgedNightlyWarning = true; } diff --git a/src/MaaWpfGui/ViewModels/UserControl/Settings/VersionUpdateSettingsUserControlModel.cs b/src/MaaWpfGui/ViewModels/UserControl/Settings/VersionUpdateSettingsUserControlModel.cs index 34de451398..1d6e69aa55 100644 --- a/src/MaaWpfGui/ViewModels/UserControl/Settings/VersionUpdateSettingsUserControlModel.cs +++ b/src/MaaWpfGui/ViewModels/UserControl/Settings/VersionUpdateSettingsUserControlModel.cs @@ -509,6 +509,10 @@ public class VersionUpdateSettingsUserControlModel : PropertyChangedBase { ResourceUpdater.ResourceReload(); } + else + { + AchievementTrackerHelper.Instance.AddProgress(AchievementIds.CdnTorture); + } IsCheckingForUpdates = false; } diff --git a/src/MaaWpfGui/ViewModels/UserControl/TaskQueue/RoguelikeSettingsUserControlModel.cs b/src/MaaWpfGui/ViewModels/UserControl/TaskQueue/RoguelikeSettingsUserControlModel.cs index 7656a378fe..5526349e8a 100644 --- a/src/MaaWpfGui/ViewModels/UserControl/TaskQueue/RoguelikeSettingsUserControlModel.cs +++ b/src/MaaWpfGui/ViewModels/UserControl/TaskQueue/RoguelikeSettingsUserControlModel.cs @@ -874,6 +874,7 @@ public class RoguelikeSettingsUserControlModel : TaskViewModel { case "RoguelikeInvestmentReachFull": Instances.TaskQueueViewModel.AddLog(LocalizationHelper.GetString("RoguelikeInvestmentReachFull"), UiLogColor.Info); + AchievementTrackerHelper.Instance.SetProgress(AchievementIds.RoguelikeGoldMax, 999); break; case "RoguelikeInvestmentReachLimit": @@ -882,12 +883,14 @@ public class RoguelikeSettingsUserControlModel : TaskViewModel case "RoguelikeInvestment": Instances.TaskQueueViewModel.AddLog(string.Format(LocalizationHelper.GetString("RoguelikeInvestment"), subTaskDetails!["count"], subTaskDetails["total"], subTaskDetails["deposit"]), UiLogColor.Info); + AchievementTrackerHelper.Instance.SetProgress(AchievementIds.RoguelikeGoldMax, (int)subTaskDetails["deposit"]!); break; case "RoguelikeSettlement": {// 肉鸽结算 var report = subTaskDetails; var pass = (bool)report!["game_pass"]!; + var difficulty = (int)(report["difficulty"] ?? -1); var roguelikeInfo = string.Format( LocalizationHelper.GetString("RoguelikeSettlement"), pass ? "✓" : "✗", @@ -898,11 +901,34 @@ public class RoguelikeSettingsUserControlModel : TaskViewModel report["boss"], report["recruit"], report["collection"], - report["difficulty"], + difficulty, report["score"], report["exp"], report["skill"]); + if (pass) + { + if (difficulty > 4) + { + AchievementTrackerHelper.Instance.Unlock(AchievementIds.RoguelikeN04); + } + + if (difficulty > 8) + { + AchievementTrackerHelper.Instance.Unlock(AchievementIds.RoguelikeN08); + } + + if (difficulty > 12) + { + AchievementTrackerHelper.Instance.Unlock(AchievementIds.RoguelikeN12); + } + + if (difficulty > 15) + { + AchievementTrackerHelper.Instance.Unlock(AchievementIds.RoguelikeN15); + } + } + Instances.TaskQueueViewModel.AddLog(roguelikeInfo, UiLogColor.Message); break; } diff --git a/src/MaaWpfGui/Views/UI/AchievementListWindow.xaml b/src/MaaWpfGui/Views/UI/AchievementListWindow.xaml new file mode 100644 index 0000000000..4be7304aa8 --- /dev/null +++ b/src/MaaWpfGui/Views/UI/AchievementListWindow.xaml @@ -0,0 +1,76 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/MaaWpfGui/Views/UI/AchievementListWindow.xaml.cs b/src/MaaWpfGui/Views/UI/AchievementListWindow.xaml.cs new file mode 100644 index 0000000000..6f4f651b78 --- /dev/null +++ b/src/MaaWpfGui/Views/UI/AchievementListWindow.xaml.cs @@ -0,0 +1,37 @@ +// +// MaaWpfGui - A part of the MaaCoreArknights project +// Copyright (C) 2021 MistEO and Contributors +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License v3.0 only as published by +// the Free Software Foundation, either version 3 of the License, or +// any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY +// + +using System.ComponentModel; +using MaaWpfGui.Helper; + +namespace MaaWpfGui.Views.UI +{ + /// + /// AchievementListWindow.xaml 的交互逻辑 + /// + public partial class AchievementListWindow + { + public AchievementListWindow() + { + InitializeComponent(); + DataContext = AchievementTrackerHelper.Instance; + Closing += OnWindowClosing; + } + + private static void OnWindowClosing(object sender, CancelEventArgs e) + { + // 关闭窗口时执行一次空搜索,重置可见性 + AchievementTrackerHelper.Instance.Search(); + } + } +} diff --git a/src/MaaWpfGui/Views/UI/ErrorView.xaml.cs b/src/MaaWpfGui/Views/UI/ErrorView.xaml.cs index 73485ed273..72f0b26564 100644 --- a/src/MaaWpfGui/Views/UI/ErrorView.xaml.cs +++ b/src/MaaWpfGui/Views/UI/ErrorView.xaml.cs @@ -92,6 +92,8 @@ namespace MaaWpfGui.Views.UI var isZhCn = ConfigurationHelper.GetGlobalValue(ConfigurationKeys.Localization, LocalizationHelper.DefaultLanguage) == "zh-cn"; ErrorQqGroupLink.Visibility = isZhCn ? Visibility.Visible : Visibility.Collapsed; + + AchievementTrackerHelper.Instance.Unlock(AchievementIds.CongratulationError); } public event PropertyChangedEventHandler PropertyChanged; @@ -130,6 +132,8 @@ namespace MaaWpfGui.Views.UI return LocalizationHelper.GetString("ErrorSolutionFailedToMove"); } + AchievementTrackerHelper.Instance.Unlock(AchievementIds.UnexpectedCrash); + return LocalizationHelper.GetString("UnknownErrorOccurs"); } diff --git a/src/MaaWpfGui/Views/UI/NotifyIcon.xaml.cs b/src/MaaWpfGui/Views/UI/NotifyIcon.xaml.cs index 20c4f34b84..06319b210f 100644 --- a/src/MaaWpfGui/Views/UI/NotifyIcon.xaml.cs +++ b/src/MaaWpfGui/Views/UI/NotifyIcon.xaml.cs @@ -131,7 +131,7 @@ namespace MaaWpfGui.Views.UI private static void StopTask(object sender, RoutedEventArgs e) { - Instances.TaskQueueViewModel?.Stop(); + Instances.TaskQueueViewModel?.ManualStop(); _logger.Information("Tray service task stop."); } diff --git a/src/MaaWpfGui/Views/UI/SettingsView.xaml b/src/MaaWpfGui/Views/UI/SettingsView.xaml index c118752860..2924378e30 100644 --- a/src/MaaWpfGui/Views/UI/SettingsView.xaml +++ b/src/MaaWpfGui/Views/UI/SettingsView.xaml @@ -17,7 +17,7 @@ xmlns:viewModels="clr-namespace:MaaWpfGui.ViewModels" d:Background="White" d:DataContext="{d:DesignInstance {x:Type ui:SettingsViewModel}}" - d:DesignHeight="4000" + d:DesignHeight="5000" d:DesignWidth="800" mc:Ignorable="d"> @@ -107,19 +107,26 @@ + + + + - + - + diff --git a/src/MaaWpfGui/Views/UI/TaskQueueView.xaml b/src/MaaWpfGui/Views/UI/TaskQueueView.xaml index 9a486b0d1c..3253b0a3c3 100644 --- a/src/MaaWpfGui/Views/UI/TaskQueueView.xaml +++ b/src/MaaWpfGui/Views/UI/TaskQueueView.xaml @@ -116,9 +116,7 @@ - + diff --git a/src/MaaWpfGui/Views/UserControl/Settings/AchievementSettings.xaml b/src/MaaWpfGui/Views/UserControl/Settings/AchievementSettings.xaml new file mode 100644 index 0000000000..407e969f5b --- /dev/null +++ b/src/MaaWpfGui/Views/UserControl/Settings/AchievementSettings.xaml @@ -0,0 +1,68 @@ + + + + + + +