Merge branch 'main' into d-v3

This commit is contained in:
辉鸭蛋
2025-09-06 15:03:37 +08:00
108 changed files with 3185 additions and 1612 deletions

View File

@@ -2,7 +2,7 @@
<PropertyGroup>
<AssemblyName>BetterGI</AssemblyName>
<Version>0.49.0</Version>
<Version>0.49.1-alpha.3</Version>
<IncludeSourceRevisionInInformationalVersion>false</IncludeSourceRevisionInInformationalVersion>
<OutputType>WinExe</OutputType>
<TargetFramework>net8.0-windows10.0.22621.0</TargetFramework>
@@ -43,7 +43,7 @@
<PackageReference Include="BetterGI.VCRuntime" Version="14.44.35208" />
<PackageReference Include="BetterGI.Assets.Map" Version="1.0.9" />
<PackageReference Include="BetterGI.Assets.Model" Version="1.0.9" />
<PackageReference Include="BetterGI.Assets.Model" Version="1.0.10" />
<PackageReference Include="BetterGI.Assets.Other" Version="1.0.8" />
<PackageReference Include="CommunityToolkit.Mvvm" Version="8.2.2" />
@@ -177,12 +177,6 @@
<None Update="User\**">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</None>
<None Update="GameTask\GetGridIcons\gridIcon.onnx">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None>
<None Update="GameTask\GetGridIcons\训练集原型特征.csv">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None>
<None Update="GameTask\LogParse\Assets\log.css">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</None>
@@ -192,6 +186,9 @@
<None Update="GameTask\GameLoading\Assets\1920x1080\enter_game.png">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</None>
<None Update="GameTask\Common\Element\Assets\1920x1080\in_domain.png">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</None>
</ItemGroup>
<ItemGroup>

View File

@@ -1,4 +1,5 @@
using CommunityToolkit.Mvvm.ComponentModel;
using BetterGenshinImpact.Helpers;
using CommunityToolkit.Mvvm.ComponentModel;
using System;
using System.Collections.Generic;
using Wpf.Ui.Controls;
@@ -47,7 +48,7 @@ public partial class CommonConfig : ObservableObject
/// 当前主题类型(新版主题)
/// </summary>
[ObservableProperty]
private ThemeType _currentThemeType = ThemeType.DarkMica;
private ThemeType _currentThemeType = OsVersionHelper.IsWindows11_22523_OrGreater? ThemeType.DarkMica : ThemeType.DarkNone;
/// <summary>
/// 主题(旧版主题,兼容性保留)

View File

@@ -101,7 +101,7 @@ public partial class OtherConfig : ObservableObject
/// PaddleOCR模型配置
/// </summary>
[ObservableProperty]
private PaddleOcrModelConfig _paddleOcrModelConfig = PaddleOcrModelConfig.V4;
private PaddleOcrModelConfig _paddleOcrModelConfig = PaddleOcrModelConfig.V4Auto;
}
//public partial class OtherConfig : ObservableObject

View File

@@ -89,6 +89,10 @@ public class OcrFactory : IDisposable
{
return _config.PaddleOcrModelConfig switch
{
PaddleOcrModelConfig.V4Auto =>
new PaddleOcrService(App.ServiceProvider.GetRequiredService<BgiOnnxFactory>(),
PaddleOcrService.PaddleOcrModelType.FromCultureInfoV4(GetCultureInfo()) ??
PaddleOcrService.PaddleOcrModelType.V4),
PaddleOcrModelConfig.V5Auto =>
new PaddleOcrService(App.ServiceProvider.GetRequiredService<BgiOnnxFactory>(),
PaddleOcrService.PaddleOcrModelType.FromCultureInfo(GetCultureInfo()) ??

View File

@@ -201,6 +201,43 @@ public class PaddleOcrService : IOcrService, IDisposable
return null;
}
/// <summary>
/// 中英文优先使用V4模型其他语言使用V5模型
/// </summary>
/// <param name="cultureInfo"></param>
/// <returns></returns>
public static PaddleOcrModelType? FromCultureInfoV4(CultureInfo cultureInfo)
{
var v5 = FromCultureInfo(cultureInfo);
// 如果用的是v5, 那么优先用V4的细分模型
if (v5 == V5)
{
List<string> names =
[
cultureInfo.EnglishName.ToLowerInvariant(), cultureInfo.Name.ToLowerInvariant(),
cultureInfo.ThreeLetterISOLanguageName.ToLowerInvariant(),
cultureInfo.TwoLetterISOLanguageName.ToLowerInvariant()
];
foreach (var name in names)
{
if (name.Equals("en"))
{
return V4En;
}
else if (name.Equals("zh-hant") || name.Equals("zh-tw") || name.Equals("zh-hk"))
{
return V5;
}
}
return V4;
}
else
{
return v5;
}
}
}
public PaddleOcrService(BgiOnnxFactory bgiOnnxFactory, PaddleOcrModelType modelType)

View File

@@ -2,11 +2,12 @@
public enum PaddleOcrModelConfig
{
V5Auto,
V5,
V4,
V4En,
V5Latin,
V5Eslav,
V5Korean,
V4Auto = 2,
V5Auto = 0,
V4 = 7,
V4En = 3,
V5 = 1,
V5Latin = 4,
V5Eslav = 5,
V5Korean = 6,
}

View File

@@ -7,14 +7,14 @@ using BetterGenshinImpact.GameTask.AutoFishing;
using BetterGenshinImpact.GameTask.AutoGeniusInvokation;
using BetterGenshinImpact.GameTask.AutoPathing.Handler;
using BetterGenshinImpact.GameTask.AutoWood;
using BetterGenshinImpact.GameTask.Common.Job;
using BetterGenshinImpact.GameTask.Model.GameUI;
using BetterGenshinImpact.Helpers;
using BetterGenshinImpact.ViewModel.Pages;
using Microsoft.ClearScript;
using Microsoft.Extensions.Logging;
using System;
using System.Threading;
using Microsoft.ClearScript;
using BetterGenshinImpact.Helpers;
using System.Threading.Tasks;
namespace BetterGenshinImpact.Core.Script.Dependence;
@@ -23,7 +23,7 @@ public class Dispatcher
{
private readonly ILogger<Dispatcher> _logger = App.GetLogger<Dispatcher>();
private object _config = null;
private readonly object _config;
public Dispatcher(object config)
{
@@ -111,7 +111,7 @@ public class Dispatcher
/// <param name="customCt">自定义取消令牌允许从JS控制任务取消</param>
/// <exception cref="ArgumentNullException"></exception>
/// <exception cref="ArgumentException"></exception>
public async Task RunTask(SoloTask soloTask, CancellationToken? customCt = null)
public async Task<object?> RunTask(SoloTask soloTask, CancellationToken? customCt = null)
{
if (soloTask == null)
{
@@ -140,119 +140,129 @@ public class Dispatcher
// 根据名称执行任务
switch (soloTask.Name)
{
case "AutoGeniusInvokation":
string content;
case "AutoGeniusInvokation":
string content;
// 检查是否有自定义策略内容
if (soloTask.Config != null)
{
var jsObject = (ScriptObject)soloTask.Config;
content = ScriptObjectConverter.GetValue(jsObject, "strategy", "");
if (string.IsNullOrEmpty(content))
{
if (soloTask.Config != null)
{
var jsObject = (ScriptObject)soloTask.Config;
content = ScriptObjectConverter.GetValue(jsObject, "strategy", "");
if (string.IsNullOrEmpty(content))
{
// 回退到原有逻辑
if (taskSettingsPageViewModel.GetTcgStrategy(out content))
{
return;
}
}
}
else
{
if (taskSettingsPageViewModel.GetTcgStrategy(out content))
{
return null;
}
}
}
else
{
// 回退到原有逻辑
if (taskSettingsPageViewModel.GetTcgStrategy(out content))
{
return;
}
}
await new AutoGeniusInvokationTask(new GeniusInvokationTaskParam(content)).Start(cancellationToken);
break;
if (taskSettingsPageViewModel.GetTcgStrategy(out content))
{
return null;
}
}
await new AutoGeniusInvokationTask(new GeniusInvokationTaskParam(content)).Start(cancellationToken);
return null;
case "AutoWood":
await new AutoWoodTask(new WoodTaskParam(taskSettingsPageViewModel.AutoWoodRoundNum,
taskSettingsPageViewModel.AutoWoodDailyMaxCount)).Start(cancellationToken);
break;
return null;
case "AutoFight":
await new AutoFightHandler().RunAsyncByScript(cancellationToken, null, _config);
break;
return null;
case "AutoDomain":
if (taskSettingsPageViewModel.GetFightStrategy(out var path))
{
return;
return null;
}
await new AutoDomainTask(new AutoDomainParam(0, path)).Start(cancellationToken);
break;
return null;
case "AutoFishing":
await new AutoFishingTask(AutoFishingTaskParam.BuildFromSoloTaskConfig(soloTask.Config)).Start(
cancellationToken);
break;
return null;
case "AutoEat":
string? foodName = soloTask.Config == null ? null : ScriptObjectConverter.GetValue<string?>((ScriptObject)soloTask.Config, "foodName", null);
FoodEffectType? foodEffectType = soloTask.Config == null ? null : (FoodEffectType?)ScriptObjectConverter.GetValue<int?>((ScriptObject)soloTask.Config, "foodEffectType", null);
if (foodName != null && foodEffectType != null)
{
throw new NotSupportedException("不能同时指定foodName和foodEffectType");
}
string? foodName = soloTask.Config == null ? null : ScriptObjectConverter.GetValue<string?>((ScriptObject)soloTask.Config, "foodName", null);
FoodEffectType? foodEffectType = soloTask.Config == null ? null : (FoodEffectType?)ScriptObjectConverter.GetValue<int?>((ScriptObject)soloTask.Config, "foodEffectType", null);
if (foodName == null)
{
if (foodEffectType != null)
if (foodName != null && foodEffectType != null)
{
PathingPartyConfig? pathingPartyConfig = _config as PathingPartyConfig;
if (pathingPartyConfig == null)
throw new NotSupportedException("不能同时指定foodName和foodEffectType");
}
if (foodName == null)
{
if (foodEffectType != null)
{
throw new NotSupportedException("foodEffectType参数需要调度器配置请在调度器下使用");
}
else
{
switch (foodEffectType)
PathingPartyConfig? pathingPartyConfig = _config as PathingPartyConfig;
if (pathingPartyConfig == null)
{
case FoodEffectType.ATKBoostingDish:
foodName = pathingPartyConfig.AutoEatConfig.DefaultAtkBoostingDishName;
if (foodName == null)
{
_logger.LogInformation("缺少{Text}配置跳过吃Buff", "默认的攻击类料理");
return;
}
break;
case FoodEffectType.AdventurersDish:
foodName = pathingPartyConfig.AutoEatConfig.DefaultAdventurersDishName;
if (foodName == null)
{
_logger.LogInformation("缺少{Text}配置跳过吃Buff", "默认的冒险类料理");
return;
}
break;
case FoodEffectType.DEFBoostingDish:
foodName = pathingPartyConfig.AutoEatConfig.DefaultDefBoostingDishName;
if (foodName == null)
{
_logger.LogInformation("缺少{Text}配置跳过吃Buff", "默认的防御类料理");
return;
}
break;
default:
throw new NotSupportedException("JS脚本入参错误错误的foodEffectType");
throw new NotSupportedException("foodEffectType参数需要调度器配置请在调度器下使用");
}
else
{
switch (foodEffectType)
{
case FoodEffectType.ATKBoostingDish:
foodName = pathingPartyConfig.AutoEatConfig.DefaultAtkBoostingDishName;
if (foodName == null)
{
_logger.LogInformation("缺少{Text}配置跳过吃Buff", "默认的攻击类料理");
return null;
}
break;
case FoodEffectType.AdventurersDish:
foodName = pathingPartyConfig.AutoEatConfig.DefaultAdventurersDishName;
if (foodName == null)
{
_logger.LogInformation("缺少{Text}配置跳过吃Buff", "默认的冒险类料理");
return null;
}
break;
case FoodEffectType.DEFBoostingDish:
foodName = pathingPartyConfig.AutoEatConfig.DefaultDefBoostingDishName;
if (foodName == null)
{
_logger.LogInformation("缺少{Text}配置跳过吃Buff", "默认的防御类料理");
return null;
}
break;
default:
throw new NotSupportedException("JS脚本入参错误错误的foodEffectType");
}
}
}
}
var autoEatConfig = TaskContext.Instance().Config.AutoEatConfig;
return await new AutoEatTask(new AutoEatParam()
{
CheckInterval = autoEatConfig.CheckInterval,
EatInterval = autoEatConfig.EatInterval,
ShowNotification = autoEatConfig.ShowNotification,
FoodName = foodName
}).Start(cancellationToken);
}
var autoEatConfig = TaskContext.Instance().Config.AutoEatConfig;
await new AutoEatTask(new AutoEatParam()
case "CountInventoryItem":
{
CheckInterval = autoEatConfig.CheckInterval,
EatInterval = autoEatConfig.EatInterval,
ShowNotification = autoEatConfig.ShowNotification,
FoodName = foodName
}).Start(cancellationToken);
break;
if (soloTask.Config == null)
{
throw new NullReferenceException($"{nameof(soloTask.Config)}为空");
}
GridScreenName gridScreenName = ScriptObjectConverter.GetValue<GridScreenName?>((ScriptObject)soloTask.Config, "gridScreenName", null) ?? throw new Exception("gridScreenName为空或错误");
string foodName = ScriptObjectConverter.GetValue<string?>((ScriptObject)soloTask.Config, "foodName", null) ?? throw new Exception("foodName为空");
return await new CountInventoryItem(gridScreenName, foodName).Start(cancellationToken);
}
default:
throw new ArgumentException($"未知的任务名称: {soloTask.Name}", nameof(soloTask.Name));
}

View File

@@ -57,7 +57,12 @@ public class EngineExtend
engine.AddHostType("Region", typeof(Region));
engine.AddHostType("CombatScenes", typeof(CombatScenes));
engine.AddHostType("CombatScenes", typeof(Avatar));
engine.AddHostType("Avatar", typeof(Avatar));
engine.AddHostObject("OpenCvSharp", new HostTypeCollection("OpenCvSharp"));
// 添加C#的类型

View File

@@ -7,6 +7,8 @@ using BetterGenshinImpact.Core.Config;
using BetterGenshinImpact.GameTask.Common;
using BetterGenshinImpact.Model;
using Microsoft.Extensions.Logging;
using System.Text.Json.Serialization;
using System.Linq;
namespace BetterGenshinImpact.Core.Script.Project;
@@ -77,4 +79,21 @@ public class Manifest
return settingItems;
}
[JsonIgnore]
public string ShortDescription
{
get
{
var lines = this.Description.Split('\n');
if (lines.Length > 6)
{
return String.Join('\n', lines.Take(6).Append("……"));
}
else
{
return this.Description;
}
}
}
}

View File

@@ -18,6 +18,7 @@ using System.Linq;
using System.Net.Http;
using System.Threading.Tasks;
using System.Windows;
using Windows.UI.Xaml.Automation;
using BetterGenshinImpact.View.Windows;
using LibGit2Sharp;
using LibGit2Sharp.Handlers;
@@ -120,9 +121,7 @@ public class ScriptRepoUpdater : Singleton<ScriptRepoUpdater>
// 如果仓库不存在,执行浅克隆操作
_logger.LogInformation($"浅克隆仓库: {repoUrl} 到 {repoPath}");
CloneRepository(repoUrl, repoPath, onCheckoutProgress);
// CloneRepository(repoUrl, repoPath);
CloneRepository(repoUrl, repoPath, "release", onCheckoutProgress);
updated = true;
}
else
@@ -142,68 +141,76 @@ public class ScriptRepoUpdater : Singleton<ScriptRepoUpdater>
_logger.LogWarning(ex, "备份repo.json失败继续更新仓库");
}
// 检查是否为有效的Git仓库
if (!Repository.IsValid(repoPath))
{
Toast.Error($"不是有效的Git仓库将重新克隆");
UIDispatcherHelper.Invoke(() => Toast.Error("不是有效的Git仓库将重新克隆"));
CloneRepository(repoUrl, repoPath, "release", onCheckoutProgress);
updated = true;
return;
}
using var repo = new Repository(repoPath);
// 检查远程URL是否需要更新
var origin = repo.Network.Remotes["origin"];
if (origin.Url != repoUrl)
{
// 远程URL已更改需要更新
_logger.LogInformation($"更新远程URL: 从 {origin.Url} 到 {repoUrl}");
repo.Network.Remotes.Update("origin", r => r.Url = repoUrl);
// 远程URL已更改需要删除重新克隆
_logger.LogInformation($"远程URL已更改: 从 {origin.Url} 到 {repoUrl},将重新克隆");
CloneRepository(repoUrl, repoPath, "release", onCheckoutProgress);
updated = true;
return;
}
// 获取远程分支信息
// 获取远程分支信息并拉取最新数据
var remote = repo.Network.Remotes["origin"];
var refSpecs = remote.FetchRefSpecs.Select(x => x.Specification);
// 使用浅拉取选项
var fetchOptions = new FetchOptions();
fetchOptions.ProxyOptions.ProxyType = ProxyType.None;
var fetchOptions = new FetchOptions
{
ProxyOptions = { ProxyType = ProxyType.None }
};
Commands.Fetch(repo, remote.Name, refSpecs, fetchOptions, "拉取最新更新");
// 获取当前分支
var branch = repo.Branches["refs/heads/origin/main"] ?? repo.Branches["main"];
if (branch == null)
{
throw new Exception("未找到main或master分支");
}
// 如果是本地分支,需要设置上游分支
if (!branch.IsRemote)
{
var trackingBranch = repo.Branches[$"origin/{branch.FriendlyName}"];
if (trackingBranch != null && branch.TrackedBranch == null)
{
branch = repo.Branches.Update(branch,
b => b.TrackedBranch = trackingBranch.CanonicalName);
}
}
// 检查是否有更新
// 获取本地和远程commit
var currentCommitSha = repo.Head.Tip.Sha;
// 合并或重置到最新
if (branch.TrackedBranch != null)
// 获取远程release分支的最新commit
var remoteBranch = repo.Branches["refs/remotes/origin/release"];
if (remoteBranch == null)
{
var trackingBranch = branch.TrackedBranch;
var mergeResult = Commands.Pull(
repo,
new Signature("BetterGI", "auto@bettergi.com", DateTimeOffset.Now),
new PullOptions());
throw new Exception("未找到远程release分支");
}
// 检查是否有更新
updated = currentCommitSha != repo.Head.Tip.Sha;
var remoteCommitSha = remoteBranch.Tip.Sha;
// 比较本地和远程commit
if (currentCommitSha == remoteCommitSha)
{
_logger.LogInformation("本地仓库已是最新版本,无需更新");
updated = false;
}
else
{
_logger.LogInformation($"检测到远程更新: 本地 {currentCommitSha[..7]} -> 远程 {remoteCommitSha[..7]},将重新克隆");
// commit不一致删除本地仓库重新克隆
CloneRepository(repoUrl, repoPath, "release", onCheckoutProgress);
updated = true;
}
}
}
catch (Exception ex)
{
_logger.LogError(ex, "Git仓库更新失败");
throw;
UIDispatcherHelper.Invoke(() => Toast.Error("脚本仓库更新异常,直接删除后重新克隆\n原因" + ex.Message));
CloneRepository(repoUrl, repoPath, "release", onCheckoutProgress);
}
});
// 如果仓库有更新则标记新repo.json中的更新节点
if (updated)
{
@@ -214,12 +221,12 @@ public class ScriptRepoUpdater : Singleton<ScriptRepoUpdater>
if (newRepoJsonPath != null)
{
var newRepoJsonContent = await File.ReadAllTextAsync(newRepoJsonPath);
// 检查是否存在repo_update.json如果存在则直接与它比对
var parentDir = Path.GetDirectoryName(repoPath);
var repoUpdateJsonPath = Path.Combine(parentDir!, "repo_update.json");
string updatedContent;
if (File.Exists(repoUpdateJsonPath))
{
try
@@ -355,7 +362,6 @@ public class ScriptRepoUpdater : Singleton<ScriptRepoUpdater>
}
/// <summary>
/// 解析lastUpdated时间戳
/// </summary>
@@ -384,6 +390,7 @@ public class ScriptRepoUpdater : Singleton<ScriptRepoUpdater>
{
var options = new CloneOptions
{
BranchName = "release", // 指定分支
Checkout = true,
IsBare = false,
RecurseSubmodules = false, // 不递归克隆子模块
@@ -396,54 +403,45 @@ public class ScriptRepoUpdater : Singleton<ScriptRepoUpdater>
/// <summary>
///
/// 相当于 Repository.Clone(repoUrl, repoPath, options);
/// 相当于 Repository.Clone(repoUrl, repoPath, options);
/// 用这个方法可以无视本地代理
/// </summary>
/// <param name="repoUrl"></param>
/// <param name="repoPath"></param>
/// <param name="branchName"></param>
/// <param name="onCheckoutProgress"></param>
/// <exception cref="Exception"></exception>
private void CloneRepository(string repoUrl, string repoPath, CheckoutProgressHandler? onCheckoutProgress)
private void CloneRepository(string repoUrl, string repoPath, string branchName, CheckoutProgressHandler? onCheckoutProgress)
{
// 1. 创建目录
DirectoryHelper.DeleteReadOnlyDirectory(repoPath);
Directory.CreateDirectory(repoPath);
// 2. 初始化 Git 仓库
Repository.Init(repoPath);
using var repo = new Repository(repoPath);
GitConfig(repo);
// 3. 添加远程源
// 添加远程源
Remote remote = repo.Network.Remotes.Add("origin", repoUrl);
// 4. 获取数据(使用浅克隆选项)
// 只拉取指定分支
var fetchOptions = new FetchOptions
{
TagFetchMode = TagFetchMode.None // 不获取标签
TagFetchMode = TagFetchMode.None,
ProxyOptions = { ProxyType = ProxyType.None }
};
fetchOptions.ProxyOptions.ProxyType = ProxyType.None;
string refSpec = $"+refs/heads/{branchName}:refs/remotes/origin/{branchName}";
Commands.Fetch(repo, remote.Name, new[] { refSpec }, fetchOptions, "初始化拉取");
// 5. 执行获取操作
Commands.Fetch(repo, remote.Name, ["+refs/heads/*:refs/remotes/origin/*"], fetchOptions, "初始化拉取");
// 6. 创建本地分支并跟踪远程分支
var remoteBranch = repo.Branches["refs/remotes/origin/main"] ?? repo.Branches["refs/remotes/origin/master"];
// 获取远程分支
var remoteBranch = repo.Branches[$"refs/remotes/origin/{branchName}"];
if (remoteBranch == null)
{
throw new Exception("远程仓库中未找到 main 或 master 分支");
}
throw new Exception($"远程仓库中未找到 {branchName} 分支");
// 7. 创建并检出本地分支
var localBranch = repo.CreateBranch(remoteBranch.FriendlyName, remoteBranch.Tip);
// 8. 设置本地分支跟踪远程分支
// 创建并检出本地分支
var localBranch = repo.CreateBranch(branchName, remoteBranch.Tip);
repo.Branches.Update(localBranch, b => b.TrackedBranch = remoteBranch.CanonicalName);
// 9. 检出分支
CheckoutOptions checkoutOptions = new CheckoutOptions
{
OnCheckoutProgress = onCheckoutProgress
};
var checkoutOptions = new CheckoutOptions { OnCheckoutProgress = onCheckoutProgress };
Commands.Checkout(repo, localBranch, checkoutOptions);
}
@@ -795,14 +793,14 @@ public class ScriptRepoUpdater : Singleton<ScriptRepoUpdater>
{
var scriptPath = Path.Combine(repoPath, path);
var destPath = Path.Combine(userPath, remainingPath);
// 备份需要保存的文件
List<string> backupFiles = new List<string>();
if (first == "js") // 只对JS脚本进行备份
{
backupFiles = BackupScriptFiles(path, repoPath);
}
if (Directory.Exists(scriptPath))
{
if (Directory.Exists(destPath))
@@ -827,7 +825,7 @@ public class ScriptRepoUpdater : Singleton<ScriptRepoUpdater>
File.Copy(scriptPath, destPath, true);
}
// 恢复备份的文件
if (first == "js" && backupFiles.Count > 0) // 只对JS脚本进行恢复
{
@@ -891,7 +889,7 @@ public class ScriptRepoUpdater : Singleton<ScriptRepoUpdater>
{
var jsonContent = File.ReadAllText(repoJsonPath);
var jsonObj = JObject.Parse(jsonContent);
if (jsonObj["indexes"] is JArray indexes)
{
// 递归收集所有路径
@@ -915,7 +913,7 @@ public class ScriptRepoUpdater : Singleton<ScriptRepoUpdater>
}
}
}
CollectPaths(indexes, "");
}
}
@@ -925,7 +923,7 @@ public class ScriptRepoUpdater : Singleton<ScriptRepoUpdater>
{
// 构建父子关系映射,只记录直接子节点
var parentChildMap = new Dictionary<string, List<string>>();
// 遍历所有路径,找到每个节点的父节点
foreach (var path in allAvailablePaths)
{
@@ -937,31 +935,31 @@ public class ScriptRepoUpdater : Singleton<ScriptRepoUpdater>
{
parentChildMap[parentPath] = new List<string>();
}
if (!parentChildMap[parentPath].Contains(path))
{
parentChildMap[parentPath].Add(path);
}
}
}
// 递归检查父节点,直到没有新的父节点需要添加
bool hasNewPaths;
do
{
hasNewPaths = false;
var pathsToAdd = new HashSet<string>();
// 检查每个父节点
foreach (var kvp in parentChildMap)
{
var parentPath = kvp.Key;
var directChildren = kvp.Value;
// 检查所有直接子节点是否都已被订阅
bool allDirectChildrenSubscribed = directChildren.All(child =>
bool allDirectChildrenSubscribed = directChildren.All(child =>
pathsToKeep.Contains(child));
// 如果所有直接子节点都已被订阅,且父节点本身未被订阅,则添加父节点
if (allDirectChildrenSubscribed && !pathsToKeep.Contains(parentPath))
{
@@ -969,7 +967,7 @@ public class ScriptRepoUpdater : Singleton<ScriptRepoUpdater>
hasNewPaths = true;
}
}
// 将需要添加的父节点加入订阅列表
foreach (var pathToAdd in pathsToAdd)
{
@@ -1103,18 +1101,18 @@ public class ScriptRepoUpdater : Singleton<ScriptRepoUpdater>
private List<string> GetMatchedFiles(string basePath, string pattern)
{
var matchedFiles = new List<string>();
try
{
// 检查是否是正则表达式(以^开头或包含特殊字符)
bool isRegex = pattern.StartsWith("^") || pattern.Contains(".*") || pattern.Contains("\\d") || pattern.Contains("\\w");
if (isRegex)
{
// 使用正则表达式匹配
var regex = new System.Text.RegularExpressions.Regex(pattern, System.Text.RegularExpressions.RegexOptions.IgnoreCase);
var allFiles = Directory.GetFiles(basePath, "*", SearchOption.AllDirectories);
foreach (var file in allFiles)
{
var relativePath = Path.GetRelativePath(basePath, file);
@@ -1129,7 +1127,7 @@ public class ScriptRepoUpdater : Singleton<ScriptRepoUpdater>
// 使用通配符匹配
var searchPattern = Path.GetFileName(pattern);
var searchDir = Path.GetDirectoryName(pattern);
if (string.IsNullOrEmpty(searchDir))
{
// 只在当前目录搜索
@@ -1152,7 +1150,7 @@ public class ScriptRepoUpdater : Singleton<ScriptRepoUpdater>
{
_logger.LogError(ex, $"获取匹配文件时发生错误: {pattern}");
}
return matchedFiles;
}
@@ -1215,6 +1213,7 @@ public class ScriptRepoUpdater : Singleton<ScriptRepoUpdater>
savedFile += "/";
isDir = true;
}
if (isDir)
{
var dirPath = Path.Combine(scriptUserPath, savedFile.TrimEnd('/', '\\'));
@@ -1241,6 +1240,7 @@ public class ScriptRepoUpdater : Singleton<ScriptRepoUpdater>
{
Directory.CreateDirectory(backupFileDir);
}
try
{
File.Copy(matchedFile, backupFilePath, true);
@@ -1251,6 +1251,7 @@ public class ScriptRepoUpdater : Singleton<ScriptRepoUpdater>
_logger.LogError(ex, $"备份文件失败: {matchedFile}");
}
}
if (matchedFiles.Count == 0)
{
_logger.LogWarning($"没有找到匹配的文件: {savedFile}");
@@ -1262,6 +1263,7 @@ public class ScriptRepoUpdater : Singleton<ScriptRepoUpdater>
{
_logger.LogError(ex, $"备份脚本文件时发生错误: {scriptPath}");
}
return backupFiles;
}
@@ -1302,6 +1304,7 @@ public class ScriptRepoUpdater : Singleton<ScriptRepoUpdater>
_logger.LogWarning($"未知的脚本路径映射: {scriptPath}");
return;
}
var scriptUserPath = Path.Combine(userPath, remainingPath);
// 还原所有备份文件
@@ -1316,6 +1319,7 @@ public class ScriptRepoUpdater : Singleton<ScriptRepoUpdater>
{
Directory.CreateDirectory(restoreDir);
}
try
{
File.Copy(file, restorePath, true);
@@ -1349,4 +1353,4 @@ public class ScriptRepoUpdater : Singleton<ScriptRepoUpdater>
_logger.LogError(ex, $"恢复脚本文件时发生错误: {scriptPath}");
}
}
}
}

View File

@@ -1,3 +1,5 @@
using System.Text;
namespace BetterGenshinImpact.GameTask.AutoArtifactSalvage
{
/// <summary>
@@ -34,5 +36,24 @@ namespace BetterGenshinImpact.GameTask.AutoArtifactSalvage
public int Level { get; private set; }
// PS圣遗物的种类和品质在点击查看之前就可以通过识别图标获悉所以不必在此模型类中获取
/// <summary>
/// 生成一个手工拼接的成员结构示意字符串
/// </summary>
/// <returns></returns>
public string ToStructuredString()
{
StringBuilder sb = new StringBuilder();
sb.Append("Properties").Append('\n');
sb.Append("├─").Append("Name: ").Append(this.Name).Append('\n');
sb.Append("├─").Append("MainAffix: ").Append(this.MainAffix.Type).Append(", ").Append(this.MainAffix.Value).Append('\n');
sb.Append("├─").Append("MinorAffixes: ").Append('\n');
for (int i = 0; i < this.MinorAffixes.Length; i++)
{
sb.Append('│').Append('\t').Append(i == this.MinorAffixes.Length - 1 ? "└─" : "├─").Append($"[{i}]: ").Append(this.MinorAffixes[i].Type).Append(", ").Append(this.MinorAffixes[i].Value).Append('\n');
}
sb.Append("└─").Append("Level: ").Append(this.Level);
return sb.ToString();
}
}
}

View File

@@ -8,12 +8,15 @@ public partial class AutoArtifactSalvageConfig : ObservableObject
{
// JavaScript
[ObservableProperty]
private string _javaScript =
@"(async function (artifact) {
var hasATK = Array.from(artifact.MinorAffixes).some(affix => affix.Type == 'ATK');
var hasDEF = Array.from(artifact.MinorAffixes).some(affix => affix.Type == 'DEF');
Output = hasATK && hasDEF;
})(ArtifactStat);";
private string _javaScript = @"(async function (artifact) {
var hasATK = Array.from(artifact.MinorAffixes).some(affix => affix.Type == 'ATK');
var hasDEF = Array.from(artifact.MinorAffixes).some(affix => affix.Type == 'DEF');
Output = hasATK && hasDEF;
})(ArtifactStat);";
// JavaScript
[ObservableProperty]
private string _artifactSetFilter = "";
// 正则表达式
[Obsolete]
@@ -28,4 +31,8 @@ public partial class AutoArtifactSalvageConfig : ObservableObject
// 最多检查多少个圣遗物
[ObservableProperty]
private int _maxNumToCheck = 100;
// 单次识别失败政策
[ObservableProperty]
private RecognitionFailurePolicy _recognitionFailurePolicy = RecognitionFailurePolicy.Skip;
}

View File

@@ -7,6 +7,7 @@ using BetterGenshinImpact.GameTask.Common;
using BetterGenshinImpact.GameTask.Common.BgiVision;
using BetterGenshinImpact.GameTask.Common.Element.Assets;
using BetterGenshinImpact.GameTask.Common.Job;
using BetterGenshinImpact.GameTask.GetGridIcons;
using BetterGenshinImpact.GameTask.Model.Area;
using BetterGenshinImpact.GameTask.Model.GameUI;
using BetterGenshinImpact.Helpers;
@@ -16,8 +17,10 @@ using Microsoft.ClearScript;
using Microsoft.ClearScript.V8;
using Microsoft.Extensions.Localization;
using Microsoft.Extensions.Logging;
using Microsoft.ML.OnnxRuntime;
using OpenCvSharp;
using System;
using System.Collections.Frozen;
using System.Collections.Generic;
using System.Diagnostics;
using System.Globalization;
@@ -35,7 +38,7 @@ namespace BetterGenshinImpact.GameTask.AutoArtifactSalvage;
/// </summary>
public class AutoArtifactSalvageTask : ISoloTask
{
private readonly ILogger logger = App.GetLogger<AutoArtifactSalvageTask>();
private readonly ILogger logger;
private readonly InputSimulator input = Simulation.SendInput;
private CancellationToken ct;
@@ -50,19 +53,28 @@ public class AutoArtifactSalvageTask : ISoloTask
private readonly string? javaScript;
private readonly string? artifactSetFilter;
private readonly int? maxNumToCheck;
private readonly RecognitionFailurePolicy? recognitionFailurePolicy;
private readonly bool returnToMainUi = true;
private readonly CultureInfo cultureInfo;
private readonly CultureInfo? cultureInfo;
public AutoArtifactSalvageTask(int star, string? javaScript = null, int? maxNumToCheck = null)
private readonly FrozenDictionary<ArtifactAffixType, string> artifactAffixStrDic;
public AutoArtifactSalvageTask(AutoArtifactSalvageTaskParam param, ILogger? logger = null)
{
this.star = star;
this.javaScript = javaScript;
this.maxNumToCheck = maxNumToCheck;
IStringLocalizer<AutoArtifactSalvageTask> stringLocalizer = App.GetService<IStringLocalizer<AutoArtifactSalvageTask>>() ?? throw new NullReferenceException();
this.cultureInfo = new CultureInfo(TaskContext.Instance().Config.OtherConfig.GameCultureInfoName);
this.star = param.Star;
this.javaScript = param.JavaScript;
this.artifactSetFilter = param.ArtifactSetFilter;
this.maxNumToCheck = param.MaxNumToCheck;
this.recognitionFailurePolicy = param.RecognitionFailurePolicy;
this.logger = logger ?? App.GetLogger<AutoArtifactSalvageTask>();
var stringLocalizer = param.StringLocalizer ?? App.GetService<IStringLocalizer<AutoArtifactSalvageTask>>() ?? throw new NullReferenceException();
this.cultureInfo = param.GameCultureInfo;
quickSelectLocalizedString = stringLocalizer.WithCultureGet(cultureInfo, "快速选择");
numOfStarLocalizedString =
[
@@ -71,14 +83,11 @@ public class AutoArtifactSalvageTask : ISoloTask
stringLocalizer.WithCultureGet(cultureInfo, "3星圣遗物"),
stringLocalizer.WithCultureGet(cultureInfo, "4星圣遗物")
];
artifactAffixStrDic = ArtifactAffix.DefaultStrDic.Select(kvp => new KeyValuePair<ArtifactAffixType, string>(kvp.Key, stringLocalizer.WithCultureGet(cultureInfo, kvp.Value))).ToFrozenDictionary();
}
public AutoArtifactSalvageTask(int star, bool returnToMainUi) : this(star)
{
this.returnToMainUi = returnToMainUi;
}
public static async Task OpenBag(GridScreenName gridScreenName, InputSimulator input, ILogger logger, CancellationToken ct)
public static async Task OpenInventory(GridScreenName gridScreenName, InputSimulator input, ILogger logger, CancellationToken ct)
{
RecognitionObject? recognitionObjectChecked;
RecognitionObject? recognitionObjectUnchecked;
@@ -175,7 +184,7 @@ public class AutoArtifactSalvageTask : ISoloTask
await new ReturnMainUiTask().Start(ct);
}
await OpenBag(GridScreenName.Artifacts, this.input, this.logger, this.ct);
await OpenInventory(GridScreenName.Artifacts, this.input, this.logger, this.ct);
// 点击分解按钮打开分解界面
using var ra2 = CaptureToRectArea();
@@ -224,7 +233,11 @@ public class AutoArtifactSalvageTask : ISoloTask
}
}
Bv.ClickWhiteConfirmButton(ra4);
using var quickSelectConfirmBtn = ra4.Find(ElementAssets.Instance.BtnWhiteConfirm);
if (quickSelectConfirmBtn.IsExist())
{
quickSelectConfirmBtn.Click();
}
await Delay(1500, ct);
@@ -260,7 +273,39 @@ public class AutoArtifactSalvageTask : ISoloTask
// 分解5星
if (javaScript != null)
{
await Salvage5Star(this.javaScript, this.maxNumToCheck ?? throw new ArgumentException($"{nameof(this.maxNumToCheck)}不能为空"));
if (!string.IsNullOrWhiteSpace(this.artifactSetFilter))
{
// 其实是点击筛选按钮……快速选择确认的这个按钮正好和筛选按钮位置重合,摆烂直接用了
quickSelectConfirmBtn.Click();
await Delay(400, ct);
// 点击所属套装
ra5.ClickTo(315, 205);
await Delay(1000, ct);
// 遍历套装Grid勾选套装
using InferenceSession session = GridIconsAccuracyTestTask.LoadModel(out Dictionary<string, float[]> prototypes);
ArtifactSetFilterScreen gridScreen = new ArtifactSetFilterScreen(new GridParams(new Rect(40, 100, 1300, 852), 2, 3, 40, 40, 0.024), this.logger, this.ct);
await foreach (ImageRegion itemRegion in gridScreen)
{
using Mat img125 = GetGridIconsTask.CropResizeArtifactSetFilterGridIcon(itemRegion);
(string predName, _) = GridIconsAccuracyTestTask.Infer(img125, session, prototypes);
if (this.artifactSetFilter.Contains(predName))
{
itemRegion.Click();
await Delay(100, ct);
}
}
// 点击确认筛选
using var confirmFilterBtnRegion = CaptureToRectArea();
Bv.ClickWhiteConfirmButton(confirmFilterBtnRegion);
await Delay(1500, ct);
// 点击确认
using var confirmBtnRegion = CaptureToRectArea();
Bv.ClickWhiteConfirmButton(confirmBtnRegion);
await Delay(600, ct);
}
// 逐一点选查看面板筛选
await Salvage5Star();
logger.LogInformation("筛选完毕,请复查并手动分解");
}
else
@@ -274,14 +319,14 @@ public class AutoArtifactSalvageTask : ISoloTask
}
}
private async Task Salvage5Star(string javaScript, int maxNumToCheck)
private async Task Salvage5Star()
{
int count = maxNumToCheck;
string javaScript = this.javaScript ?? throw new ArgumentException($"{nameof(this.javaScript)}不能为空");
int count = this.maxNumToCheck ?? throw new ArgumentException($"{nameof(this.maxNumToCheck)}不能为空");
RecognitionFailurePolicy recognitionFailurePolicy = this.recognitionFailurePolicy ?? throw new ArgumentException($"{nameof(this.recognitionFailurePolicy)}不能为空");
using var ra0 = CaptureToRectArea();
GridScreenParams gridParams = GridScreenParams.Templates[GridScreenName.ArtifactSalvage];
Rect gridRoi = gridParams.GetRect(ra0);
GridScreen gridScreen = new GridScreen(gridRoi, gridParams, this.logger, this.ct); // 圣遗物分解Grid有4行9列
GridParams gridParams = GridParams.Templates[GridScreenName.ArtifactSalvage];
GridScreen gridScreen = new GridScreen(gridParams, this.logger, this.ct); // 圣遗物分解Grid有4行9列
await foreach (ImageRegion itemRegion in gridScreen)
{
Rect gridRect = itemRegion.ToRect();
@@ -291,11 +336,31 @@ public class AutoArtifactSalvageTask : ISoloTask
await Delay(300, ct);
using var ra1 = CaptureToRectArea();
using ImageRegion itemRegion1 = ra1.DeriveCrop(gridRect + new Point(gridRoi.X, gridRoi.Y));
using ImageRegion itemRegion1 = ra1.DeriveCrop(gridRect + new Point(gridParams.Roi.X, gridParams.Roi.Y));
if (GetArtifactStatus(itemRegion1.SrcMat) == ArtifactStatus.Selected)
{
using ImageRegion card = ra1.DeriveCrop(new Rect((int)(ra1.Width * 0.70), (int)(ra1.Width * 0.055), (int)(ra1.Width * 0.24), (int)(ra1.Width * 0.29)));
ArtifactStat artifact = GetArtifactStat(card.SrcMat, OcrFactory.Paddle, this.cultureInfo, out string allText);
using ImageRegion card = ra1.DeriveCrop(new Rect((int)(ra1.Width * 0.70), (int)(ra1.Height * 0.112), (int)(ra1.Width * 0.275), (int)(ra1.Height * 0.50)));
ArtifactStat artifact;
try
{
artifact = GetArtifactStat(card.SrcMat, OcrFactory.Paddle, out string allText);
}
catch (Exception e)
{
if (recognitionFailurePolicy == RecognitionFailurePolicy.Skip)
{
logger.LogError("识别失败,跳过当前圣遗物:{msg}", e.Message);
itemRegion.Click(); // 反选取消
await Delay(100, ct);
continue;
}
else
{
throw;
}
}
if (IsMatchJavaScript(artifact, javaScript))
{
@@ -379,35 +444,78 @@ public class AutoArtifactSalvageTask : ISoloTask
return match.Success;
}
public static ArtifactStat GetArtifactStat(Mat src, IOcrService ocrService, CultureInfo cultureInfo, out string allText)
public ArtifactStat GetArtifactStat(Mat src, IOcrService ocrService, out string allText)
{
var ocrResult = ocrService.OcrResult(src);
allText = ocrResult.Text;
var lines = ocrResult.Text.Split('\n');
using Mat gray = src.CvtColor(ColorConversionCodes.BGR2GRAY);
Mat hatKernel = Cv2.GetStructuringElement(MorphShapes.Rect, new Size(15, 15)/*需根据实际文本大小调整*/); // 顶帽运算核
Mat nameRoi = gray.SubMat(new Rect(0, 0, src.Width, (int)(src.Height * 0.106)));
//Cv2.ImShow("name", nameRoi);
Mat typeRoi = gray.SubMat(new Rect(0, (int)(src.Height * 0.106), src.Width, (int)(src.Height * 0.106)));
#region
Mat mainAffixRoi = gray.SubMat(new Rect(0, (int)(src.Height * 0.22), (int)(src.Width * 0.55), (int)(src.Height * 0.30)));
using Mat mainAffixRoiBottomHat = mainAffixRoi.MorphologyEx(MorphTypes.TopHat, hatKernel);
using Mat mainAffixRoiThreshold = mainAffixRoiBottomHat.Threshold(30, 255, ThresholdTypes.Binary);
//Cv2.ImShow("mainAffix", mainAffixRoiThreshold);
#endregion
#region
Mat levelAndMinorAffixRoi = gray.SubMat(new Rect(0, (int)(src.Height * 0.52), src.Width, (int)(src.Height * 0.48)));
//using Mat levelAndMinorAffixRoiThreshold = new Mat();
//double otsu = Cv2.Threshold(levelAndMinorAffixRoi, levelAndMinorAffixRoiThreshold, 0, 255, ThresholdTypes.Binary | ThresholdTypes.Otsu);
// //using Mat levelAndMinorAffixRoiThreshold = levelAndMinorAffixRoi.Threshold(170, 255, ThresholdTypes.Binary);
//Cv2.ImShow($"levelAndMinorAffixRoi = {otsu}", levelAndMinorAffixRoiThreshold);
#endregion
//Cv2.WaitKey();
var nameOcrResult = ocrService.OcrResult(nameRoi);
var typeOcrResult = ocrService.OcrResult(typeRoi);
var mainAffixOcrResult = ocrService.OcrResult(mainAffixRoiThreshold);
string mainAffixText = string.Join("\n", mainAffixOcrResult.Regions.Where(r => r.Score > 0.5).OrderBy(r => r.Rect.Center.Y).ThenBy(r => r.Rect.Center.X).Select(r => r.Text));
var mainAffixLines = mainAffixText.Split('\n');
var levelAndMinorAffixOcrResult = ocrService.OcrResult(levelAndMinorAffixRoi);
string levelAndMinorAffixText = string.Join("\n", levelAndMinorAffixOcrResult.Regions.Where(r => r.Score > 0.5)
.Where(r => r.Rect.BoundingRect().Left < levelAndMinorAffixRoi.Width * 0.1) // 一定是贴着左边的,排除套装效果文字也存在类似+15%的情况
.OrderBy(r => r.Rect.Center.Y).ThenBy(r => r.Rect.Center.X).Select(r => r.Text));
var levelAndMinorAffixLines = levelAndMinorAffixText.Split('\n');
allText = String.Join('\n', nameOcrResult.Text, typeOcrResult.Text, mainAffixText, levelAndMinorAffixText);
string percentStr = "%";
// 名称
string name = lines[0];
string name = nameOcrResult.Text;
#region
var defaultMainAffix = ArtifactAffix.DefaultStrDic.Select(kvp => kvp.Value).Distinct();
string mainAffixTypeLine = lines.Single(l => defaultMainAffix.Contains(l));
ArtifactAffixType mainAffixType = ArtifactAffix.DefaultStrDic.First(kvp => kvp.Value == mainAffixTypeLine).Key;
string mainAffixValueLine = lines.Select(l =>
var defaultMainAffix = this.artifactAffixStrDic.Select(kvp => kvp.Value).Distinct();
string mainAffixTypeLine = mainAffixLines.SingleOrDefault(l => defaultMainAffix.Contains(l)) ?? throw new Exception($"未找到主词条对应的行:\n{mainAffixText}");
ArtifactAffixType mainAffixType = this.artifactAffixStrDic.First(kvp => kvp.Value == mainAffixTypeLine).Key;
string mainAffixValueLine = mainAffixLines.Select(l =>
{
string pattern = @"^(\d+\.?\d*)(%?)$";
string pattern = @"^([\d., ]*)(%?)$";
pattern = pattern.Replace("%", percentStr); // 这样一行一行写只是为了IDE能保持正则字符串高亮
Match match = Regex.Match(l, pattern);
if (match.Success)
{
if (mainAffixType == ArtifactAffixType.ATK && !String.IsNullOrEmpty(match.Groups[2].Value))
{
mainAffixType = ArtifactAffixType.ATKPercent;
}
if (mainAffixType == ArtifactAffixType.DEF && !String.IsNullOrEmpty(match.Groups[2].Value))
{
mainAffixType = ArtifactAffixType.DEFPercent;
}
if (mainAffixType == ArtifactAffixType.HP && !String.IsNullOrEmpty(match.Groups[2].Value))
{
mainAffixType = ArtifactAffixType.HPPercent;
}
return match.Groups[1].Value;
}
else
{
return null;
}
}).Where(l => l != null).Cast<string>().Single();
if (!float.TryParse(mainAffixValueLine, NumberStyles.Any, cultureInfo, out float value))
}).Where(l => l != null).Cast<string>().SingleOrDefault() ?? throw new Exception($"未找到主词条数值对应的行:\n{mainAffixText}");
if (!float.TryParse(mainAffixValueLine, NumberStyles.Any, this.cultureInfo, out float value))
{
throw new Exception($"未识别的主词条数值:{mainAffixValueLine}");
}
@@ -415,15 +523,15 @@ public class AutoArtifactSalvageTask : ISoloTask
#endregion
#region
ArtifactAffix[] minorAffixes = lines.Select(l =>
ArtifactAffix[] minorAffixes = levelAndMinorAffixLines.Select(l =>
{
string pattern = @"^[•·]?([^+]+)\+(\d+\.?\d*)(%?)$";
string pattern = @"^[•·]?([^+:]+)\+([\d., ]*)(%?)$";
pattern = pattern.Replace("%", percentStr);
Match match = Regex.Match(l, pattern);
if (match.Success)
{
ArtifactAffixType artifactAffixType;
var dic = ArtifactAffix.DefaultStrDic;
var dic = this.artifactAffixStrDic;
if (match.Groups[1].Value.Contains(dic[ArtifactAffixType.ATK]))
{
@@ -493,7 +601,7 @@ public class AutoArtifactSalvageTask : ISoloTask
#endregion
#region
string levelLine = lines.Select(l =>
string levelLine = levelAndMinorAffixLines.Select(l =>
{
string pattern = @"^\+(\d*)$";
Match match = Regex.Match(l, pattern);
@@ -505,7 +613,7 @@ public class AutoArtifactSalvageTask : ISoloTask
{
return null;
}
}).Where(l => l != null).Cast<string>().Single();
}).Where(l => l != null).Cast<string>().SingleOrDefault() ?? throw new Exception($"未找到等级对应的行:\n{levelAndMinorAffixText}");
if (!int.TryParse(levelLine, out int level) || level < 0 || level > 20)
{
throw new Exception($"未识别的等级:{levelLine}");

View File

@@ -1,4 +1,4 @@
<?xml version="1.0" encoding="utf-8"?>
<?xml version="1.0" encoding="utf-8"?>
<root>
<!--
Microsoft ResX Schema
@@ -129,7 +129,55 @@
<data name="4星圣遗物" xml:space="preserve">
<value>4-Star.*Artifacts</value>
</data>
<data name="元素充能效率" xml:space="preserve">
<value>Energy Recharge</value>
</data>
<data name="元素精通" xml:space="preserve">
<value>Elemental Mastery</value>
</data>
<data name="冰元素伤害加成" xml:space="preserve">
<value>Cryo DMG Bonus</value>
</data>
<data name="岩元素伤害加成" xml:space="preserve">
<value>Geo DMG Bonus</value>
</data>
<data name="快速选择" xml:space="preserve">
<value>Quick.*Select</value>
</data>
<data name="攻击力" xml:space="preserve">
<value>ATK</value>
</data>
<data name="暴击伤害" xml:space="preserve">
<value>CRIT DMG</value>
</data>
<data name="暴击率" xml:space="preserve">
<value>CRIT Rate</value>
</data>
<data name="水元素伤害加成" xml:space="preserve">
<value>Hydro DMG Bonus</value>
</data>
<data name="治疗加成" xml:space="preserve">
<value>Healing Bonus</value>
</data>
<data name="火元素伤害加成" xml:space="preserve">
<value>Pyro DMG Bonus</value>
</data>
<data name="物理伤害加成" xml:space="preserve">
<value>Physical DMG Bonus</value>
</data>
<data name="生命值" xml:space="preserve">
<value>HP</value>
</data>
<data name="草元素伤害加成" xml:space="preserve">
<value>Dendro DMG Bonus</value>
</data>
<data name="防御力" xml:space="preserve">
<value>DEF</value>
</data>
<data name="雷元素伤害加成" xml:space="preserve">
<value>Electro DMG Bonus</value>
</data>
<data name="风元素伤害加成" xml:space="preserve">
<value>Anemo DMG Bonus</value>
</data>
</root>

View File

@@ -1,4 +1,4 @@
<?xml version="1.0" encoding="utf-8"?>
<?xml version="1.0" encoding="utf-8"?>
<root>
<!--
Microsoft ResX Schema
@@ -129,7 +129,55 @@
<data name="4星圣遗物" xml:space="preserve">
<value>Artéfact.*4★?</value>
</data>
<data name="元素充能效率" xml:space="preserve">
<value>Recharge d'énergie</value>
</data>
<data name="元素精通" xml:space="preserve">
<value>Maîtrise élémentaire</value>
</data>
<data name="冰元素伤害加成" xml:space="preserve">
<value>Bonus de DGT Cryo</value>
</data>
<data name="岩元素伤害加成" xml:space="preserve">
<value>Bonus de DGT Géo</value>
</data>
<data name="快速选择" xml:space="preserve">
<value>Sélection.*rapide</value>
</data>
<data name="攻击力" xml:space="preserve">
<value>ATQ</value>
</data>
<data name="暴击伤害" xml:space="preserve">
<value>DGT CRIT</value>
</data>
<data name="暴击率" xml:space="preserve">
<value>Taux CRIT</value>
</data>
<data name="水元素伤害加成" xml:space="preserve">
<value>Bonus de DGT Hydro</value>
</data>
<data name="治疗加成" xml:space="preserve">
<value>Bonus de soins</value>
</data>
<data name="火元素伤害加成" xml:space="preserve">
<value>Bonus de DGT Pyro</value>
</data>
<data name="物理伤害加成" xml:space="preserve">
<value>Bonus de DGT physiques</value>
</data>
<data name="生命值" xml:space="preserve">
<value>PV</value>
</data>
<data name="草元素伤害加成" xml:space="preserve">
<value>Bonus de DGT Dendro</value>
</data>
<data name="防御力" xml:space="preserve">
<value>DÉF</value>
</data>
<data name="雷元素伤害加成" xml:space="preserve">
<value>Bonus de DGT Électro</value>
</data>
<data name="风元素伤害加成" xml:space="preserve">
<value>Bonus de DGT Anémo</value>
</data>
</root>

View File

@@ -1,4 +1,4 @@
<?xml version="1.0" encoding="utf-8"?>
<?xml version="1.0" encoding="utf-8"?>
<root>
<!--
Microsoft ResX Schema
@@ -129,7 +129,55 @@
<data name="4星圣遗物" xml:space="preserve">
<value>4星圣遗物</value>
</data>
<data name="元素充能效率" xml:space="preserve">
<value>元素充能效率</value>
</data>
<data name="元素精通" xml:space="preserve">
<value>元素精通</value>
</data>
<data name="冰元素伤害加成" xml:space="preserve">
<value>冰元素伤害加成</value>
</data>
<data name="岩元素伤害加成" xml:space="preserve">
<value>岩元素伤害加成</value>
</data>
<data name="快速选择" xml:space="preserve">
<value>快速选择</value>
</data>
<data name="攻击力" xml:space="preserve">
<value>攻击力</value>
</data>
<data name="暴击伤害" xml:space="preserve">
<value>暴击伤害</value>
</data>
<data name="暴击率" xml:space="preserve">
<value>暴击率</value>
</data>
<data name="水元素伤害加成" xml:space="preserve">
<value>水元素伤害加成</value>
</data>
<data name="治疗加成" xml:space="preserve">
<value>治疗加成</value>
</data>
<data name="火元素伤害加成" xml:space="preserve">
<value>火元素伤害加成</value>
</data>
<data name="物理伤害加成" xml:space="preserve">
<value>物理伤害加成</value>
</data>
<data name="生命值" xml:space="preserve">
<value>生命值</value>
</data>
<data name="草元素伤害加成" xml:space="preserve">
<value>草元素伤害加成</value>
</data>
<data name="防御力" xml:space="preserve">
<value>防御力</value>
</data>
<data name="雷元素伤害加成" xml:space="preserve">
<value>雷元素伤害加成</value>
</data>
<data name="风元素伤害加成" xml:space="preserve">
<value>风元素伤害加成</value>
</data>
</root>

View File

@@ -1,4 +1,4 @@
<?xml version="1.0" encoding="utf-8"?>
<?xml version="1.0" encoding="utf-8"?>
<root>
<!--
Microsoft ResX Schema
@@ -129,7 +129,55 @@
<data name="4星圣遗物" xml:space="preserve">
<value>四星聖遺物</value>
</data>
<data name="元素充能效率" xml:space="preserve">
<value>元素充能效率</value>
</data>
<data name="元素精通" xml:space="preserve">
<value>元素精通</value>
</data>
<data name="冰元素伤害加成" xml:space="preserve">
<value>冰元素傷害加成</value>
</data>
<data name="岩元素伤害加成" xml:space="preserve">
<value>岩元素傷害加成</value>
</data>
<data name="快速选择" xml:space="preserve">
<value>快速選擇</value>
</data>
<data name="攻击力" xml:space="preserve">
<value>攻擊力</value>
</data>
<data name="暴击伤害" xml:space="preserve">
<value>暴擊傷害</value>
</data>
<data name="暴击率" xml:space="preserve">
<value>暴擊率</value>
</data>
<data name="水元素伤害加成" xml:space="preserve">
<value>水元素傷害加成</value>
</data>
<data name="治疗加成" xml:space="preserve">
<value>治療加成</value>
</data>
<data name="火元素伤害加成" xml:space="preserve">
<value>火元素傷害加成</value>
</data>
<data name="物理伤害加成" xml:space="preserve">
<value>物理傷害加成</value>
</data>
<data name="生命值" xml:space="preserve">
<value>生命值</value>
</data>
<data name="草元素伤害加成" xml:space="preserve">
<value>草元素傷害加成</value>
</data>
<data name="防御力" xml:space="preserve">
<value>防禦力</value>
</data>
<data name="雷元素伤害加成" xml:space="preserve">
<value>雷元素傷害加成</value>
</data>
<data name="风元素伤害加成" xml:space="preserve">
<value>風元素傷害加成</value>
</data>
</root>

View File

@@ -0,0 +1,27 @@
using BetterGenshinImpact.GameTask.Model;
using Microsoft.Extensions.Localization;
using System;
using System.Collections.Generic;
using System.Globalization;
using System.Text;
namespace BetterGenshinImpact.GameTask.AutoArtifactSalvage
{
public class AutoArtifactSalvageTaskParam : BaseTaskParam<AutoArtifactSalvageTask>
{
public AutoArtifactSalvageTaskParam(int star, string? javaScript, string? artifactSetFilter, int? maxNumToCheck, RecognitionFailurePolicy? recognitionFailurePolicy, CultureInfo? cultureInfo = null, IStringLocalizer<AutoArtifactSalvageTask>? stringLocalizer = null) : base(cultureInfo, stringLocalizer)
{
Star = star;
JavaScript = javaScript;
ArtifactSetFilter = artifactSetFilter;
MaxNumToCheck = maxNumToCheck;
RecognitionFailurePolicy = recognitionFailurePolicy;
}
public int Star { get; set; }
public string? JavaScript { get; set; }
public string? ArtifactSetFilter { get; set; }
public int? MaxNumToCheck { get; set; }
public RecognitionFailurePolicy? RecognitionFailurePolicy { get; set; }
}
}

View File

@@ -0,0 +1,12 @@
using System.ComponentModel;
namespace BetterGenshinImpact.GameTask.AutoArtifactSalvage
{
public enum RecognitionFailurePolicy
{
[Description("跳过")]
Skip,
[Description("终止")]
Abort
}
}

View File

@@ -1,10 +1,10 @@
using System.Collections.Generic;
using System.Collections.Generic;
using BetterGenshinImpact.GameTask.Model;
using System.Threading;
namespace BetterGenshinImpact.GameTask.AutoDomain;
public class AutoDomainParam : BaseTaskParam
public class AutoDomainParam : BaseTaskParam<AutoDomainParam>
{
public int DomainRoundNum { get; set; }
@@ -15,7 +15,7 @@ public class AutoDomainParam : BaseTaskParam
// 需要刷取的副本名称
public string DomainName { get; set; } = string.Empty;
// 需要刷取的副本名称
public string SundaySelectedValue { get; set; } = string.Empty;
@@ -27,9 +27,9 @@ public class AutoDomainParam : BaseTaskParam
public string MaxArtifactStar { get; set; } = "4";
public bool SpecifyResinUse { get; set; } = false;
// 使用树脂优先级
public List<string> ResinPriorityList { get; set; } =
public List<string> ResinPriorityList { get; set; } =
[
"浓缩树脂",
"原粹树脂"
@@ -47,7 +47,7 @@ public class AutoDomainParam : BaseTaskParam
// 使用脆弱树脂刷取副本次数
public int FragileResinUseCount { get; set; } = 0;
public AutoDomainParam(int domainRoundNum, string path)
public AutoDomainParam(int domainRoundNum, string path) : base(null, null)
{
DomainRoundNum = domainRoundNum;
if (domainRoundNum == 0)

View File

@@ -1,4 +1,4 @@
using BetterGenshinImpact.Core.Config;
using BetterGenshinImpact.Core.Config;
using BetterGenshinImpact.Core.Recognition.OCR;
using BetterGenshinImpact.Core.Recognition.ONNX;
using BetterGenshinImpact.Core.Simulator;
@@ -18,8 +18,6 @@ using OpenCvSharp;
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Drawing.Imaging;
using System.IO;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
@@ -29,7 +27,6 @@ using BetterGenshinImpact.GameTask.Common.BgiVision;
using BetterGenshinImpact.GameTask.Common.Element.Assets;
using BetterGenshinImpact.GameTask.Common.Job;
using BetterGenshinImpact.Service.Notification.Model.Enum;
using Vanara.PInvoke;
using static BetterGenshinImpact.GameTask.Common.TaskControl;
using static Vanara.PInvoke.Kernel32;
using static Vanara.PInvoke.User32;
@@ -97,7 +94,7 @@ public class AutoDomainTask : ISoloTask
this.rapidformationString = stringLocalizer.WithCultureGet(cultureInfo, "快速编队");
this.limitedFullyString = stringLocalizer.WithCultureGet(cultureInfo, "限时全开");
}
private static RecognitionObject GetConfirmRa(params string[] targetText)
{
var screenArea = CaptureToRectArea();
@@ -163,18 +160,18 @@ public class AutoDomainTask : ISoloTask
// 前置进入秘境
await EnterDomain();
var combatScenes = new CombatScenes();
for (var i = 0; i < _taskParam.DomainRoundNum; i++)
{
// 0. 关闭秘境提示
Logger.LogDebug("0. 关闭秘境提示");
await CloseDomainTip();
//0.5. 初始化队伍,只执行一次
if (i == 0)
{
combatScenes = new CombatScenes().InitializeTeam(CaptureToRectArea());
combatScenes = new CombatScenes().InitializeTeam(CaptureToRectArea());
}
RetryTeamInit(combatScenes);// 队伍没初始化成功则重试
@@ -218,7 +215,7 @@ public class AutoDomainTask : ISoloTask
{
TaskTriggerDispatcher.Instance().AddTrigger("AutoEat", null);
}
if (_config.SpecifyResinUse)
{
Logger.LogInformation("→ {Text} 指定使用树脂", "自动秘境,");
@@ -273,13 +270,13 @@ public class AutoDomainTask : ISoloTask
var menuFound = false;
if ("芬德尼尔之顶".Equals(_taskParam.DomainName))
{
menuFound = await NewRetry.WaitForElementAppear(
AutoPickAssets.Instance.PickRo,
() => Simulation.SendInput.SimulateAction(GIActions.MoveBackward, KeyType.KeyDown),
_ct,
20,
500
);
menuFound = await NewRetry.WaitForElementAppear(
AutoPickAssets.Instance.PickRo,
() => Simulation.SendInput.SimulateAction(GIActions.MoveBackward, KeyType.KeyDown),
_ct,
20,
500
);
Simulation.SendInput.SimulateAction(GIActions.MoveBackward, KeyType.KeyUp);
}
else if ("无妄引咎密宫".Equals(_taskParam.DomainName))
@@ -290,13 +287,13 @@ public class AutoDomainTask : ISoloTask
menuFound = await NewRetry.WaitForElementAppear(
AutoPickAssets.Instance.PickRo,
() => Simulation.SendInput.SimulateAction(GIActions.MoveLeft, KeyType.KeyDown),
() => Simulation.SendInput.SimulateAction(GIActions.MoveLeft, KeyType.KeyDown),
_ct,
20,
500
);
Simulation.SendInput.SimulateAction(GIActions.MoveLeft, KeyType.KeyUp);
}
else if ("太山府".Equals(_taskParam.DomainName))
{
@@ -319,12 +316,12 @@ public class AutoDomainTask : ISoloTask
);
Simulation.SendInput.SimulateAction(GIActions.MoveForward, KeyType.KeyUp);
}
if (!menuFound)
{
throw new Exception("请检查是否在秘境门前");
}
}
var menu = await NewRetry.WaitForElementAppear(
GetConfirmRa("单人挑战"),
() => Simulation.SendInput.Keyboard.KeyPress(AutoPickAssets.Instance.PickVk),
@@ -336,7 +333,7 @@ public class AutoDomainTask : ISoloTask
{
throw new Exception("请检查是否已进入秘境页面");
}
}
else
{
@@ -366,7 +363,7 @@ public class AutoDomainTask : ISoloTask
private async Task EnterDomain()
{
var fightAssets = AutoFightAssets.Instance;
var menuFound = await NewRetry.WaitForElementAppear(
GetConfirmRa("单人挑战"),
() => Simulation.SendInput.Keyboard.KeyPress(AutoPickAssets.Instance.PickVk),
@@ -378,7 +375,7 @@ public class AutoDomainTask : ISoloTask
{
Logger.LogWarning("单人挑战 按键未出现,请检查是否已进入秘境页面");
}
using var limitedFullyStringRa = CaptureToRectArea();
var limitedFullyStringRaocrList =
limitedFullyStringRa.FindMulti(RecognitionObject.Ocr(0, 0, limitedFullyStringRa.Width * 0.5,
@@ -390,7 +387,7 @@ public class AutoDomainTask : ISoloTask
{
Logger.LogInformation("自动秘境:{Text}", "检测到秘境限时全开");
}
DateTime now = DateTime.Now;
if ((now.DayOfWeek == DayOfWeek.Sunday && now.Hour >= 4 || now.DayOfWeek == DayOfWeek.Monday && now.Hour < 4) || limitedFullyStringRaocrListdone != null)
{
@@ -479,13 +476,13 @@ public class AutoDomainTask : ISoloTask
10,
1000
);
// 等待队伍选择界面出现
var teamUiFound = await NewRetry.WaitForElementAppear(
ElementAssets.Instance.PartyBtnChooseView,
() =>
{
Logger.LogInformation("自动秘境:进入 {Text}", "队伍选择界面");
Logger.LogInformation("自动秘境:进入 {Text}", "队伍选择界面");
},
_ct,
10,
@@ -494,18 +491,21 @@ public class AutoDomainTask : ISoloTask
if (!teamUiFound)
{
Logger.LogWarning("队伍选择界面未出现,跳过切换队伍。");
}else
}
else
{
await SwitchParty(_taskParam.PartyName);
}
// 点击开始挑战确认并等待“开始挑战”文字消失
var startFightFound = await NewRetry.WaitForElementDisappear(
GetConfirmRa("开始挑战"),
screen => {
screen.Find(fightAssets.ConfirmRa, ra => {
ra.Click();
ra.Dispose();
screen =>
{
screen.Find(fightAssets.ConfirmRa, ra =>
{
ra.Click();
ra.Dispose();
Logger.LogInformation("自动秘境:点击 {Text}", "开始挑战");
});
},
@@ -527,11 +527,11 @@ public class AutoDomainTask : ISoloTask
var domainTipFound = await NewRetry.WaitForAction(() =>
{
using var ra = CaptureToRectArea();
var ocrList = ra.FindMulti(RecognitionObject.Ocr(0, ra.Height * 0.2, ra.Width, ra.Height * 0.6));
var ocrListLeft = ra.Find(AutoFightAssets.Instance.AbnormalIconRa);
return (ocrList.Any(t => t.Text.Contains(leyLineDisorderLocalizedString) ||
t.Text.Contains(clickanywheretocloseLocalizedString)))|| ocrListLeft.IsExist();
return (ocrList.Any(t => t.Text.Contains(leyLineDisorderLocalizedString) ||
t.Text.Contains(clickanywheretocloseLocalizedString))) || ocrListLeft.IsExist();
}, _ct, 20, 500);
if (!domainTipFound)
{
@@ -561,10 +561,10 @@ public class AutoDomainTask : ISoloTask
if (!leftBottomFound)
{
//尝试随意点击一下右下角
GameCaptureRegion.GameRegion1080PPosClick(1515,892);
GameCaptureRegion.GameRegion1080PPosClick(1515, 892);
Logger.LogWarning("秘境提示未出现或未能点击。");
}
await Delay(500, _ct);
}
@@ -754,7 +754,7 @@ public class AutoDomainTask : ISoloTask
if (!IsTakeFood())
{
Logger.LogInformation("未装备 “{Tool}”,不启用红血自动吃药功能", "便携营养袋");
Logger.LogInformation("未装备 “{Tool}”,不启用红血自动吃药功能", "便携营养袋");
return;
}
@@ -1111,7 +1111,7 @@ public class AutoDomainTask : ISoloTask
await ExitDomain();
return false;
}
bool resinUsed = false;
if (resinStatus.CondensedResinCount > 0)
{
@@ -1123,7 +1123,7 @@ public class AutoDomainTask : ISoloTask
resinUsed = PressUseResin(ra3, "原粹树脂");
resinStatus.OriginalResinCount -= 20;
}
if (!resinUsed)
{
Logger.LogWarning("自动秘境:未找到可用的树脂,可能是{Msg1} 或者 {Msg2}。", "树脂不足", "OCR 识别失败");
@@ -1209,7 +1209,7 @@ public class AutoDomainTask : ISoloTask
confirmRectArea.Click();
await Delay(60, _ct); // 双击
confirmRectArea.Click();
if (!chooseResinPrompt)
{
// 真没树脂了还有提示兜底
@@ -1312,6 +1312,6 @@ public class AutoDomainTask : ISoloTask
star = 4;
}
await new AutoArtifactSalvageTask(star).Start(_ct);
await new AutoArtifactSalvageTask(new AutoArtifactSalvageTaskParam(star, javaScript: null, artifactSetFilter: null, maxNumToCheck: null, recognitionFailurePolicy: null)).Start(_ct);
}
}

View File

@@ -44,7 +44,7 @@ public partial class AutoEatConfig : ObservableObject
/// 默认的攻击类料理名称
/// </summary>
[ObservableProperty]
private string? _defaultAtkBoostingDishName;
private string? _defaultAtkBoostingDishName = "炸萝卜丸子";
/// <summary>
/// 默认的冒险类料理名称

View File

@@ -1,12 +1,18 @@
using BetterGenshinImpact.GameTask.Model;
using Microsoft.Extensions.Localization;
using System.Globalization;
namespace BetterGenshinImpact.GameTask.AutoEat;
/// <summary>
/// 自动吃药任务参数
/// </summary>
public class AutoEatParam : BaseTaskParam
public class AutoEatParam : BaseTaskParam<AutoEatTask>
{
public AutoEatParam() : base(null, null)
{
}
/// <summary>
/// 是否显示通知
/// </summary>

View File

@@ -12,6 +12,7 @@ using BetterGenshinImpact.GameTask.Model.GameUI;
using Fischless.WindowsInput;
using Microsoft.Extensions.Logging;
using Microsoft.ML.OnnxRuntime;
using OpenCvSharp;
using System;
using System.Collections.Generic;
using System.Threading;
@@ -24,7 +25,7 @@ namespace BetterGenshinImpact.GameTask.AutoEat;
/// 自动吃药任务
/// 检测红血自动使用便携营养袋
/// </summary>
public class AutoEatTask : BaseIndependentTask, ISoloTask
public class AutoEatTask : BaseIndependentTask, ISoloTask<int?>
{
public string Name => "自动吃药";
@@ -40,7 +41,12 @@ public class AutoEatTask : BaseIndependentTask, ISoloTask
_config = TaskContext.Instance().Config.AutoEatConfig;
}
public async Task Start(CancellationToken ct)
async Task ISoloTask.Start(CancellationToken ct)
{
await Start(ct);
}
public async Task<int?> Start(CancellationToken ct)
{
_ct = ct;
@@ -52,7 +58,7 @@ public class AutoEatTask : BaseIndependentTask, ISoloTask
if (!IsTakeFood())
{
_logger.LogWarning("未装备 \"{Tool}\",无法启用自动吃药功能", "便携营养袋");
return;
return null;
}
try
@@ -68,45 +74,62 @@ public class AutoEatTask : BaseIndependentTask, ISoloTask
{
_logger.LogInformation("自动吃药任务结束");
}
return null;
}
else
{
_logger.LogInformation("打开背包寻找{name}……", _taskParam.FoodName);
await new ReturnMainUiTask().Start(ct);
await AutoArtifactSalvageTask.OpenBag(GridScreenName.Food, _input, _logger, _ct);
await AutoArtifactSalvageTask.OpenInventory(GridScreenName.Food, _input, _logger, _ct);
using InferenceSession session = GridIconsAccuracyTestTask.LoadModel(out Dictionary<string, float[]> prototypes);
using var ra0 = CaptureToRectArea();
GridScreenParams gridParams = GridScreenParams.Templates[GridScreenName.Food];
var gridRoi = gridParams.GetRect(ra0);
GridScreen gridScreen = new GridScreen(gridRoi, gridParams, _logger, _ct);
bool isAte = false;
GridScreen gridScreen = new GridScreen(GridParams.Templates[GridScreenName.Food], _logger, _ct);
int? count = null;
await foreach (ImageRegion itemRegion in gridScreen)
{
var result = GridIconsAccuracyTestTask.Infer(itemRegion.SrcMat, session, prototypes);
using Mat icon = itemRegion.SrcMat.GetGridIcon();
var result = GridIconsAccuracyTestTask.Infer(icon, session, prototypes);
string predName = result.Item1;
if (predName == _taskParam.FoodName)
{
// 点击item
itemRegion.Click();
#region
string numStr = itemRegion.SrcMat.GetGridItemIconText(OcrFactory.Paddle);
if (int.TryParse(numStr, out int num))
{
count = num - 1; // 算上吃掉的1个
}
else
{
count = -2;
_logger.LogWarning("无法识别食物数量:{text},依然尝试使用", numStr);
}
#endregion
await Delay(300, ct);
// 点击确定
using var ra0 = CaptureToRectArea();
using var ra = ra0.Find(ElementAssets.Instance.BtnWhiteConfirm);
if (ra.IsExist())
{
ra.Click();
}
_logger.LogInformation("吃了一份{name},真香!", predName);
isAte = true;
break;
}
}
if (!isAte)
if (count == null)
{
count = -1;
_logger.LogInformation("没有找到{name}", _taskParam.FoodName);
}
await new ReturnMainUiTask().Start(ct);
return count;
}
}

View File

@@ -1,4 +1,4 @@
using BetterGenshinImpact.GameTask.Model;
using BetterGenshinImpact.GameTask.Model;
namespace BetterGenshinImpact.GameTask.AutoFight;
@@ -7,21 +7,21 @@ namespace BetterGenshinImpact.GameTask.AutoFight;
public class AutoFightParam : BaseTaskParam
public class AutoFightParam : BaseTaskParam<AutoFightTask>
{
public class FightFinishDetectConfig
public class FightFinishDetectConfig
{
public string BattleEndProgressBarColor { get; set; }= "";
public string BattleEndProgressBarColor { get; set; } = "";
public string BattleEndProgressBarColorTolerance { get; set; }= "";
public string BattleEndProgressBarColorTolerance { get; set; } = "";
public bool FastCheckEnabled = false;
public string FastCheckParams = "";
public string CheckEndDelay = "";
public string BeforeDetectDelay = "";
public bool RotateFindEnemyEnabled = false;
}
public AutoFightParam(string path, AutoFightConfig autoFightConfig)
public AutoFightParam(string path, AutoFightConfig autoFightConfig) : base(null, null)
{
CombatStrategyPath = path;
Timeout = autoFightConfig.Timeout;
@@ -30,21 +30,21 @@ public class AutoFightParam : BaseTaskParam
PickDropsAfterFightSeconds = autoFightConfig.PickDropsAfterFightSeconds;
KazuhaPickupEnabled = autoFightConfig.KazuhaPickupEnabled;
ActionSchedulerByCd = autoFightConfig.ActionSchedulerByCd;
FinishDetectConfig.FastCheckEnabled = autoFightConfig.FinishDetectConfig.FastCheckEnabled;
FinishDetectConfig.FastCheckParams = autoFightConfig.FinishDetectConfig.FastCheckParams;
FinishDetectConfig.CheckEndDelay = autoFightConfig.FinishDetectConfig.CheckEndDelay;
FinishDetectConfig.BeforeDetectDelay = autoFightConfig.FinishDetectConfig.BeforeDetectDelay;
FinishDetectConfig.RotateFindEnemyEnabled = autoFightConfig.FinishDetectConfig.RotateFindEnemyEnabled;
KazuhaPartyName = autoFightConfig.KazuhaPartyName;
OnlyPickEliteDropsMode = autoFightConfig.OnlyPickEliteDropsMode;
BattleThresholdForLoot = autoFightConfig.BattleThresholdForLoot ?? BattleThresholdForLoot;
//下面参数固定,只取自动战斗里面的
FinishDetectConfig.BattleEndProgressBarColor = TaskContext.Instance().Config.AutoFightConfig.FinishDetectConfig.BattleEndProgressBarColor;
FinishDetectConfig.BattleEndProgressBarColorTolerance = TaskContext.Instance().Config.AutoFightConfig.FinishDetectConfig.BattleEndProgressBarColorTolerance;
GuardianAvatar = autoFightConfig.GuardianAvatar;
GuardianCombatSkip = autoFightConfig.GuardianCombatSkip;
SkipModel = autoFightConfig.SkipModel;
@@ -64,7 +64,7 @@ public class AutoFightParam : BaseTaskParam
public bool KazuhaPickupEnabled = true;
public string ActionSchedulerByCd = "";
public string KazuhaPartyName;
public string OnlyPickEliteDropsMode="";
public string OnlyPickEliteDropsMode = "";
public string GuardianAvatar { get; set; } = " ";
public bool GuardianCombatSkip { get; set; } = false;
public bool SkipModel = false;

View File

@@ -1,5 +1,6 @@
using BetterGenshinImpact.Core.Recognition.OCR;
using BetterGenshinImpact.Core.Recognition.OpenCv;
using BetterGenshinImpact.Core.Script.Dependence;
using BetterGenshinImpact.Core.Simulator;
using BetterGenshinImpact.Core.Simulator.Extensions;
using BetterGenshinImpact.GameTask.AutoFight.Config;
@@ -834,7 +835,7 @@ public class Avatar
public void MoveBy(int x, int y)
{
Simulation.SendInput.Mouse.MoveMouseBy(x, y);
GlobalMethod.MoveMouseBy(x, y);
}
public void KeyDown(string key)
@@ -969,4 +970,4 @@ public class Avatar
return null;
}
}
}

View File

@@ -104,6 +104,7 @@ public class CombatScriptParser
private static List<CombatCommand> ParseLine(string line, HashSet<string> combatAvatarNames, bool validate = true)
{
line = line.Trim();
var oneLineCombatCommands = new List<CombatCommand>();
// 以空格分隔角色和指令 截取第一个空格前的内容为角色名称,后面的为指令
// 20241116更新 不输入角色名称时,直接以当前角色为准

Binary file not shown.

Before

Width:  |  Height:  |  Size: 8.0 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 17 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 14 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 17 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 16 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 17 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 17 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 8.0 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 17 KiB

View File

@@ -1,30 +1,35 @@
using BetterGenshinImpact.GameTask.Common;
using Microsoft.Extensions.Logging;
using System;
using System.Threading;
using System.Threading.Tasks;
using BetterGenshinImpact.GameTask.Common.BgiVision;
using BehaviourTree;
using BehaviourTree.Composites;
using BehaviourTree.FluentBuilder;
using BehaviourTree;
using BetterGenshinImpact.Core.Simulator;
using BetterGenshinImpact.View.Drawable;
using BetterGenshinImpact.GameTask.AutoFishing.Assets;
using Vanara.PInvoke;
using System.Linq;
using BetterGenshinImpact.GameTask.AutoFishing.Model;
using BetterGenshinImpact.GameTask.Common.Job;
using Fischless.WindowsInput;
using BetterGenshinImpact.GameTask.Model.Area;
using BetterGenshinImpact.Core.Recognition.ONNX;
using static Vanara.PInvoke.User32;
using BetterGenshinImpact.GameTask.AutoFight.Assets;
using System.Globalization;
using Microsoft.Extensions.Localization;
using BetterGenshinImpact.Helpers;
using BetterGenshinImpact.Core.Recognition.OCR;
using BetterGenshinImpact.Core.Recognition.ONNX;
using BetterGenshinImpact.Core.Simulator;
using BetterGenshinImpact.GameTask.AutoFight.Assets;
using BetterGenshinImpact.GameTask.AutoFishing.Assets;
using BetterGenshinImpact.GameTask.AutoFishing.Model;
using BetterGenshinImpact.GameTask.Common;
using BetterGenshinImpact.GameTask.Common.BgiVision;
using BetterGenshinImpact.GameTask.Common.Job;
using BetterGenshinImpact.GameTask.GetGridIcons;
using BetterGenshinImpact.GameTask.Model.Area;
using BetterGenshinImpact.Helpers;
using BetterGenshinImpact.Helpers.Extensions;
using BetterGenshinImpact.View.Drawable;
using Compunet.YoloSharp;
using Fischless.WindowsInput;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Localization;
using Microsoft.Extensions.Logging;
using Microsoft.ML.OnnxRuntime;
using OpenCvSharp;
using System;
using System.Collections.Generic;
using System.Globalization;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using Vanara.PInvoke;
using static Vanara.PInvoke.User32;
namespace BetterGenshinImpact.GameTask.AutoFishing
{
@@ -51,10 +56,7 @@ namespace BetterGenshinImpact.GameTask.AutoFishing
this._ct = ct;
IOcrService ocrService = OcrFactory.Paddle;
IStringLocalizer<AutoFishingImageRecognition> stringLocalizer =
App.GetService<IStringLocalizer<AutoFishingImageRecognition>>() ??
throw new NullReferenceException(nameof(stringLocalizer));
using InferenceSession session = GridIconsAccuracyTestTask.LoadModel(out Dictionary<string, float[]> prototypes);
Blackboard blackboard = new Blackboard(_predictor, this.Sleep, AutoFishingAssets.Instance);
@@ -68,7 +70,7 @@ namespace BetterGenshinImpact.GameTask.AutoFishing
.PushLeaf(() => new TurnAround("转圈圈调整视角", blackboard, _logger, param.SaveScreenshotOnKeyTick, input))
.PushLeaf(() => new FindFishTimeout("找到鱼", 20, blackboard, _logger, param.SaveScreenshotOnKeyTick))
.End()
.PushLeaf(() => new EnterFishingMode("进入钓鱼模式", blackboard, _logger, param.SaveScreenshotOnKeyTick, input, cultureInfo: param.GameCultureInfo))
.PushLeaf(() => new EnterFishingMode("进入钓鱼模式", blackboard, _logger, param.SaveScreenshotOnKeyTick, input, session, prototypes, cultureInfo: param.GameCultureInfo, stringLocalizer: param.StringLocalizer))
.UntilFailed(@"\")
.Sequence("一直钓鱼直到没鱼")
.AlwaysSucceed(@"\")
@@ -83,7 +85,7 @@ namespace BetterGenshinImpact.GameTask.AutoFishing
.End()
.PushLeaf(() => new FindFishTimeout("确认初始状态和找到鱼", 10, blackboard, _logger, param.SaveScreenshotOnKeyTick))
.End()
.PushLeaf(() => new ChooseBait("选择鱼饵", blackboard, _logger, param.SaveScreenshotOnKeyTick, TaskContext.Instance().SystemInfo, input))
.PushLeaf(() => new ChooseBait("选择鱼饵", blackboard, _logger, param.SaveScreenshotOnKeyTick, TaskContext.Instance().SystemInfo, input, session, prototypes))
.MySimpleParallel("抛竿直到成功或出错", policy: SimpleParallelPolicy.OnlyOneMustSucceed)
.UntilSuccess("重复抛竿")
.Sequence("-")
@@ -98,7 +100,7 @@ namespace BetterGenshinImpact.GameTask.AutoFishing
.End()
.MySimpleParallel("下杆中", SimpleParallelPolicy.OnlyOneMustSucceed)
.PushLeaf(() => new CheckThrowRod("检查抛竿结果", blackboard, _logger, param.SaveScreenshotOnKeyTick)) // todo 后面串联一个召回率高的下杆中检测方法
.PushLeaf(() => new FishBite("自动提竿", blackboard, _logger, param.SaveScreenshotOnKeyTick, input, ocrService, cultureInfo: param.GameCultureInfo, stringLocalizer: stringLocalizer))
.PushLeaf(() => new FishBite("自动提竿", blackboard, _logger, param.SaveScreenshotOnKeyTick, input, ocrService, cultureInfo: param.GameCultureInfo, stringLocalizer: param.StringLocalizer))
.PushLeaf(() => new FishBiteTimeout("下杆超时检查", param.ThrowRodTimeOutTimeoutSeconds, _logger, param.SaveScreenshotOnKeyTick, input))
.End()
.MySimpleParallel("拉条中", policy: SimpleParallelPolicy.OnlyOneMustSucceed)
@@ -116,7 +118,7 @@ namespace BetterGenshinImpact.GameTask.AutoFishing
.End()
.PushLeaf(() => new WholeProcessTimeout("检查整体超时", param.WholeProcessTimeoutSeconds, _logger, param.SaveScreenshotOnKeyTick))
.End()
.PushLeaf(() => new QuitFishingMode("退出钓鱼模式", blackboard, _logger, param.SaveScreenshotOnKeyTick, input, param.GameCultureInfo))
.PushLeaf(() => new QuitFishingMode("退出钓鱼模式", blackboard, _logger, param.SaveScreenshotOnKeyTick, input, param.GameCultureInfo, param.StringLocalizer))
.End()
.Build();
// @formatter:on
@@ -349,6 +351,8 @@ namespace BetterGenshinImpact.GameTask.AutoFishing
{
private readonly IInputSimulator input;
private readonly Blackboard blackboard;
private readonly InferenceSession session;
private readonly Dictionary<string, float[]> prototypes;
private readonly TimeProvider timeProvider;
private DateTimeOffset? pressFWaitEndTime;
private DateTimeOffset? clickWhiteConfirmButtonWaitEndTime;
@@ -356,16 +360,15 @@ namespace BetterGenshinImpact.GameTask.AutoFishing
private readonly string fishingLocalizedString;
public EnterFishingMode(string name, Blackboard blackboard, ILogger logger, bool saveScreenshotOnTerminate,
IInputSimulator input, TimeProvider? timeProvider = null, CultureInfo? cultureInfo = null) : base(name,
IInputSimulator input, InferenceSession session, Dictionary<string, float[]> prototypes, TimeProvider? timeProvider = null, CultureInfo? cultureInfo = null, IStringLocalizer? stringLocalizer = null) : base(name,
logger, saveScreenshotOnTerminate)
{
this.blackboard = blackboard;
this.input = input;
this.session = session;
this.prototypes = prototypes;
this.timeProvider = timeProvider ?? TimeProvider.System;
IStringLocalizer<AutoFishingImageRecognition> stringLocalizer =
App.GetService<IStringLocalizer<AutoFishingImageRecognition>>() ??
throw new NullReferenceException(nameof(stringLocalizer));
this.fishingLocalizedString = stringLocalizer.WithCultureGet(cultureInfo, "钓鱼");
this.fishingLocalizedString = stringLocalizer == null ? "钓鱼" : stringLocalizer.WithCultureGet(cultureInfo, "钓鱼");
}
protected override BehaviourStatus Update(ImageRegion imageRegion)
@@ -387,7 +390,17 @@ namespace BetterGenshinImpact.GameTask.AutoFishing
clickWhiteConfirmButtonWaitEndTime < timeProvider.GetLocalNow()) &&
Bv.ClickWhiteConfirmButton(imageRegion))
{
logger.LogInformation("点击开始钓鱼");
Mat subMat = imageRegion.SrcMat.SubMat(new Rect((int)(0.824 * imageRegion.Width), (int)(0.669 * imageRegion.Height), (int)(0.065 * imageRegion.Width), (int)(0.065 * imageRegion.Width)));
using Mat resized = subMat.Resize(new Size(125, 125));
(string predName, _) = GridIconsAccuracyTestTask.Infer(resized, this.session, this.prototypes);
if (predName.TryGetEnumValueFromDescription(out this.blackboard.selectedBait))
{
logger.LogInformation("点击开始钓鱼,当前鱼饵为{bait}", this.blackboard.selectedBait.Value.GetDescription());
}
else
{
logger.LogInformation("点击开始钓鱼,当前鱼饵未识别");
}
this.blackboard.pitchReset = true;
@@ -423,14 +436,11 @@ namespace BetterGenshinImpact.GameTask.AutoFishing
private readonly string fishingLocalizedString;
public QuitFishingMode(string name, Blackboard blackboard, ILogger logger, bool saveScreenshotOnTerminate,
IInputSimulator input, CultureInfo? cultureInfo = null) : base(name, logger, saveScreenshotOnTerminate)
IInputSimulator input, CultureInfo? cultureInfo = null, IStringLocalizer? stringLocalizer = null) : base(name, logger, saveScreenshotOnTerminate)
{
this.blackboard = blackboard;
this.input = input;
IStringLocalizer<AutoFishingImageRecognition> stringLocalizer =
App.GetService<IStringLocalizer<AutoFishingImageRecognition>>() ??
throw new NullReferenceException(nameof(stringLocalizer));
this.fishingLocalizedString = stringLocalizer.WithCultureGet(cultureInfo, "钓鱼");
this.fishingLocalizedString = stringLocalizer == null ? "钓鱼" : stringLocalizer.WithCultureGet(cultureInfo, "钓鱼");
}
protected override BehaviourStatus Update(ImageRegion imageRegion)

View File

@@ -1,18 +1,19 @@
using BetterGenshinImpact.GameTask.Model;
using System;
using System.Collections.Generic;
using System.Text;
using BetterGenshinImpact.GameTask.Model;
using BetterGenshinImpact.Helpers;
using Microsoft.ClearScript;
using Microsoft.Extensions.Localization;
using System;
using System.Collections.Generic;
using System.Globalization;
using System.Runtime.InteropServices;
using System.Text;
using TorchSharp;
namespace BetterGenshinImpact.GameTask.AutoFishing
{
public class AutoFishingTaskParam : BaseTaskParam
public class AutoFishingTaskParam : BaseTaskParam<AutoFishingTask>
{
public AutoFishingTaskParam(int wholeProcessTimeoutSeconds, int throwRodTimeOutTimeoutSeconds, FishingTimePolicy fishingTimePolicy, bool saveScreenshotOnKeyTick, CultureInfo? cultureInfo, bool useTorch) : base(cultureInfo)
public AutoFishingTaskParam(int wholeProcessTimeoutSeconds, int throwRodTimeOutTimeoutSeconds, FishingTimePolicy fishingTimePolicy, bool saveScreenshotOnKeyTick, bool useTorch, CultureInfo? cultureInfo, IStringLocalizer<AutoFishingTask>? stringLocalizer) : base(cultureInfo, stringLocalizer)
{
WholeProcessTimeoutSeconds = wholeProcessTimeoutSeconds;
ThrowRodTimeOutTimeoutSeconds = throwRodTimeOutTimeoutSeconds;
@@ -63,7 +64,7 @@ namespace BetterGenshinImpact.GameTask.AutoFishing
useTorch = false;
}
return new AutoFishingTaskParam(wholeProcessTimeoutSeconds, throwRodTimeOutTimeoutSeconds, fishingTimePolicy, saveScreenshotOnKeyTick, null, useTorch);
return new AutoFishingTaskParam(wholeProcessTimeoutSeconds, throwRodTimeOutTimeoutSeconds, fishingTimePolicy, saveScreenshotOnKeyTick, useTorch, null, null);
}
/// <summary>
@@ -89,7 +90,7 @@ namespace BetterGenshinImpact.GameTask.AutoFishing
{
useTorch = false;
}
return new AutoFishingTaskParam(config.WholeProcessTimeoutSeconds, config.AutoThrowRodTimeOut, config.FishingTimePolicy, saveScreenshotOnKeyTick, cultureInfo, useTorch);
return new AutoFishingTaskParam(config.WholeProcessTimeoutSeconds, config.AutoThrowRodTimeOut, config.FishingTimePolicy, saveScreenshotOnKeyTick, useTorch, cultureInfo, null);
}
}
}

View File

@@ -1,4 +1,4 @@
using BehaviourTree;
using BehaviourTree;
using BehaviourTree.FluentBuilder;
using BehaviourTree.Composites;
using BetterGenshinImpact.Core.Simulator;
@@ -52,9 +52,7 @@ namespace BetterGenshinImpact.GameTask.AutoFishing
AutoFishingTaskParam autoFishingTaskParam =
AutoFishingTaskParam.BuildFromConfig(TaskContext.Instance().Config.AutoFishingConfig);
IOcrService ocrService = OcrFactory.Paddle;
IStringLocalizer<AutoFishingImageRecognition> stringLocalizer =
App.GetService<IStringLocalizer<AutoFishingImageRecognition>>() ??
throw new NullReferenceException(nameof(stringLocalizer));
this.blackboard = new Blackboard(_predictor, this.Sleep, AutoFishingAssets.Instance);
BehaviourTreeLaTiao = FluentBuilder.Create<ImageRegion>()
@@ -63,7 +61,7 @@ namespace BetterGenshinImpact.GameTask.AutoFishing
.UntilSuccess("拉条循环")
.Sequence("拉条")
.PushLeaf(() => new FishBite("自动提竿", blackboard, _logger, false, input, ocrService,
cultureInfo: autoFishingTaskParam.GameCultureInfo, stringLocalizer: stringLocalizer))
cultureInfo: autoFishingTaskParam.GameCultureInfo, stringLocalizer: autoFishingTaskParam.StringLocalizer))
.PushLeaf(() => new GetFishBoxArea("等待拉条出现", blackboard, _logger, false))
.PushLeaf(() => new Fishing("钓鱼拉条", blackboard, _logger, false, input))
.End()

View File

@@ -1,26 +1,30 @@
using BehaviourTree;
using BehaviourTree;
using BetterGenshinImpact.Core.Recognition;
using BetterGenshinImpact.Core.Recognition.OCR;
using BetterGenshinImpact.Core.Recognition.OpenCv;
using BetterGenshinImpact.Core.Simulator;
using BetterGenshinImpact.GameTask.AutoFishing.Model;
using BetterGenshinImpact.GameTask.GetGridIcons;
using BetterGenshinImpact.GameTask.Model;
using BetterGenshinImpact.GameTask.Model.Area;
using BetterGenshinImpact.GameTask.Model.GameUI;
using BetterGenshinImpact.Helpers;
using BetterGenshinImpact.Helpers.Extensions;
using BetterGenshinImpact.View.Drawable;
using Compunet.YoloSharp;
using Fischless.WindowsInput;
using Microsoft.Extensions.Localization;
using Microsoft.Extensions.Logging;
using Microsoft.ML.OnnxRuntime;
using OpenCvSharp;
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Globalization;
using System.Linq;
using static Vanara.PInvoke.User32;
using Color = System.Drawing.Color;
using Pen = System.Drawing.Pen;
using System.Linq;
using Fischless.WindowsInput;
using BetterGenshinImpact.Core.Recognition.OpenCv;
using BetterGenshinImpact.Core.Simulator;
using BetterGenshinImpact.GameTask.Model;
using System.Globalization;
using Compunet.YoloSharp;
using Microsoft.Extensions.Localization;
namespace BetterGenshinImpact.GameTask.AutoFishing
{
@@ -66,11 +70,11 @@ namespace BetterGenshinImpact.GameTask.AutoFishing
{
blackboard.fishpond = fishpond;
string[] chooseBaitfailuresIgnoredBaits = blackboard.chooseBaitFailures.GroupBy(f => f).Where(g => g.Count() >= ChooseBait.MAX_FAILED_TIMES).Select(g => g.Key).ToArray();
string[] throwRodNoTargetFishfailuresIgnoredBaits = blackboard.throwRodNoBaitFishFailures.GroupBy(f => f).Where(g => g.Count() >= ThrowRod.MAX_NO_BAIT_FISH_TIMES).Select(g => g.Key).ToArray();
BaitType[] chooseBaitfailuresIgnoredBaits = blackboard.chooseBaitFailures.GroupBy(f => f).Where(g => g.Count() >= ChooseBait.MAX_FAILED_TIMES).Select(g => g.Key).ToArray();
BaitType[] throwRodNoTargetFishfailuresIgnoredBaits = blackboard.throwRodNoBaitFishFailures.GroupBy(f => f).Where(g => g.Count() >= ThrowRod.MAX_NO_BAIT_FISH_TIMES).Select(g => g.Key).ToArray();
logger.LogInformation("定位到鱼塘:" + string.Join('、', fishpond.Fishes.GroupBy(f => f.FishType)
.Select(g => $"{g.Key.ChineseName}{g.Count()}条" + ((chooseBaitfailuresIgnoredBaits.Contains(g.Key.BaitName) || throwRodNoTargetFishfailuresIgnoredBaits.Contains(g.Key.BaitName)) ? "(忽略)" : ""))
.Select(g => $"{g.Key.ChineseName}{g.Count()}条" + ((chooseBaitfailuresIgnoredBaits.Contains(g.Key.BaitType) || throwRodNoTargetFishfailuresIgnoredBaits.Contains(g.Key.BaitType)) ? "(忽略)" : ""))
));
int i = 0;
foreach (var fish in fishpond.Fishes)
@@ -80,8 +84,8 @@ namespace BetterGenshinImpact.GameTask.AutoFishing
blackboard.Sleep(1000);
drawContent.ClearAll();
if (blackboard.fishpond.Fishes.Any(f =>
!chooseBaitfailuresIgnoredBaits.Contains(f.FishType.BaitName)
&& !throwRodNoTargetFishfailuresIgnoredBaits.Contains(f.FishType.BaitName)))
!chooseBaitfailuresIgnoredBaits.Contains(f.FishType.BaitType)
&& !throwRodNoTargetFishfailuresIgnoredBaits.Contains(f.FishType.BaitType)))
{
return BehaviourStatus.Succeeded;
}
@@ -100,6 +104,8 @@ namespace BetterGenshinImpact.GameTask.AutoFishing
{
private readonly ISystemInfo systemInfo;
private readonly IInputSimulator input;
private readonly InferenceSession session;
private readonly Dictionary<string, float[]> prototypes;
private readonly Blackboard blackboard;
private readonly TimeProvider timeProvider;
private DateTimeOffset? chooseBaitUIOpenWaitEndTime; // 等待选鱼饵界面出现并尝试找鱼饵的结束时间
@@ -110,11 +116,13 @@ namespace BetterGenshinImpact.GameTask.AutoFishing
/// </summary>
/// <param name="name"></param>
/// <param name="autoFishingTrigger"></param>
public ChooseBait(string name, Blackboard blackboard, ILogger logger, bool saveScreenshotOnTerminat, ISystemInfo systemInfo, IInputSimulator input, TimeProvider? timeProvider = null) : base(name, logger, saveScreenshotOnTerminat)
public ChooseBait(string name, Blackboard blackboard, ILogger logger, bool saveScreenshotOnTerminat, ISystemInfo systemInfo, IInputSimulator input, InferenceSession session, Dictionary<string, float[]> prototypes, TimeProvider? timeProvider = null) : base(name, logger, saveScreenshotOnTerminat)
{
this.blackboard = blackboard;
this.systemInfo = systemInfo;
this.input = input;
this.session = session;
this.prototypes = prototypes;
this.timeProvider = timeProvider ?? TimeProvider.System;
}
@@ -122,7 +130,7 @@ namespace BetterGenshinImpact.GameTask.AutoFishing
{
if (this.Status == BehaviourStatus.Ready)
{
if (blackboard.fishpond.Fishes.Any(f => f.FishType.BaitName == blackboard.selectedBaitName)) // 如果该种鱼没钓完就不用换饵
if (blackboard.fishpond.Fishes.Any(f => f.FishType.BaitType == blackboard.selectedBait)) // 如果该种鱼没钓完就不用换饵
{
return BehaviourStatus.Succeeded;
}
@@ -136,72 +144,86 @@ namespace BetterGenshinImpact.GameTask.AutoFishing
return BehaviourStatus.Running;
}
blackboard.selectedBaitName = blackboard.fishpond.Fishes.GroupBy(f => f.FishType.BaitName)
blackboard.selectedBait = blackboard.fishpond.Fishes.GroupBy(f => f.FishType.BaitType)
.Where(b => !blackboard.chooseBaitFailures.GroupBy(f => f).Where(g => g.Count() >= MAX_FAILED_TIMES).Any(g => g.Key == b.Key)) // 不能是已经失败两次的饵
.OrderByDescending(g => g.Count()).First().Key; // 选择最多鱼吃的饵料
logger.LogInformation("选择鱼饵 {Text}", BaitType.FromName(blackboard.selectedBaitName).ChineseName);
logger.LogInformation("选择鱼饵 {Text}", blackboard.selectedBait.GetDescription());
// 寻找鱼饵
var ro = new RecognitionObject
{
Name = "ChooseBait",
RecognitionType = RecognitionTypes.TemplateMatch,
TemplateImageMat = GameTaskManager.LoadAssetImage("AutoFishing", $"bait\\{blackboard.selectedBaitName}.png", systemInfo),
Threshold = 0.8,
Use3Channels = true,
DrawOnWindow = false
}.InitTemplate();
using ImageRegion singleRowGrid = imageRegion.DeriveCrop(0.28 * imageRegion.Width, 0.37 * imageRegion.Height, 0.45 * imageRegion.Width, 0.22 * imageRegion.Height);
using Mat grey = singleRowGrid.SrcMat.CvtColor(ColorConversionCodes.BGR2GRAY);
using Mat canny = grey.Canny(20, 40);
using var resRa = imageRegion.Find(ro);
if (resRa.IsEmpty())
{
if (timeProvider.GetLocalNow() >= chooseBaitUIOpenWaitEndTime)
Cv2.FindContours(canny, out Point[][] contours, out _, RetrievalModes.External, ContourApproximationModes.ApproxSimple, null);
contours = contours
.Where(c =>
{
logger.LogWarning("没有找到目标鱼饵");
input.Keyboard.KeyPress(VK.VK_ESCAPE);
Rect r = Cv2.BoundingRect(c);
if (r.Width < 0.065 * imageRegion.Width * 0.80) // 剔除太小的
{
return false;
}
if (r.Height == 0)
{
return false;
}
return Math.Abs((float)r.Width / r.Height - 0.81) < 0.05; // 按形状筛选
}).ToArray();
IEnumerable<Rect> boxes = contours.Select(Cv2.BoundingRect);
foreach (Rect box in boxes)
{
using ImageRegion resRa = singleRowGrid.DeriveCrop(box);
using Mat img125 = resRa.SrcMat.GetGridIcon();
(string predName, _) = GridIconsAccuracyTestTask.Infer(img125, this.session, this.prototypes);
if (predName == blackboard.selectedBait.GetDescription())
{
resRa.Click();
blackboard.Sleep(700);
// 可能重复点击,所以固定界面点击下
imageRegion.ClickTo((int)(imageRegion.Width * 0.675), (int)(imageRegion.Height / 3d));
blackboard.Sleep(200);
// 点击确定
using var ra = imageRegion.Find(new RecognitionObject
{
Name = "BtnWhiteConfirm",
RecognitionType = RecognitionTypes.TemplateMatch,
TemplateImageMat = GameTaskManager.LoadAssetImage(@"Common\Element", "btn_white_confirm.png", systemInfo),
Use3Channels = true
}.InitTemplate());
if (ra.IsExist())
{
ra.Click();
}
blackboard.chooseBaitUIOpening = false;
logger.LogInformation("退出换饵界面");
blackboard.Sleep(500); // 等待界面切换
blackboard.chooseBaitFailures.Add(blackboard.selectedBaitName);
if (blackboard.chooseBaitFailures.Count(f => f == blackboard.selectedBaitName) >= MAX_FAILED_TIMES)
{
logger.LogWarning($"本次将忽略{BaitType.FromName(blackboard.selectedBaitName).ChineseName}");
}
blackboard.selectedBaitName = string.Empty;
return BehaviourStatus.Failed;
return BehaviourStatus.Succeeded;
}
else
}
if (timeProvider.GetLocalNow() >= chooseBaitUIOpenWaitEndTime)
{
logger.LogWarning("没有找到目标鱼饵");
input.Keyboard.KeyPress(VK.VK_ESCAPE);
blackboard.chooseBaitUIOpening = false;
logger.LogInformation("退出换饵界面");
blackboard.chooseBaitFailures.Add(blackboard.selectedBait.Value);
if (blackboard.chooseBaitFailures.Count(f => f == blackboard.selectedBait) >= MAX_FAILED_TIMES)
{
return BehaviourStatus.Running;
logger.LogWarning($"本次将忽略{blackboard.selectedBait.GetDescription()}");
}
blackboard.selectedBait = null;
return BehaviourStatus.Failed;
}
else
{
resRa.Click();
blackboard.Sleep(700);
// 可能重复点击,所以固定界面点击下
imageRegion.ClickTo((int)(imageRegion.Width * 0.675), (int)(imageRegion.Height / 3d));
blackboard.Sleep(200);
// 点击确定
using var ra = imageRegion.Find(new RecognitionObject
{
Name = "BtnWhiteConfirm",
RecognitionType = RecognitionTypes.TemplateMatch,
TemplateImageMat = GameTaskManager.LoadAssetImage(@"Common\Element", "btn_white_confirm.png", systemInfo),
Use3Channels = true
}.InitTemplate());
if (ra.IsExist())
{
ra.Click();
}
blackboard.chooseBaitUIOpening = false;
logger.LogInformation("退出换饵界面");
blackboard.Sleep(500); // 等待界面切换
return BehaviourStatus.Running;
}
return BehaviourStatus.Succeeded;
}
}
@@ -374,10 +396,10 @@ namespace BetterGenshinImpact.GameTask.AutoFishing
// 找到落点最近的鱼
currentFish = null;
string[] ignoredBaits = blackboard.throwRodNoBaitFishFailures.GroupBy(f => f).Where(g => g.Count() >= MAX_NO_BAIT_FISH_TIMES).Select(g => g.Key).ToArray();
BaitType[] ignoredBaits = blackboard.throwRodNoBaitFishFailures.GroupBy(f => f).Where(g => g.Count() >= MAX_NO_BAIT_FISH_TIMES).Select(g => g.Key).ToArray();
var list = fishpond.Fishes
.Where(f => !ignoredBaits.Contains(f.FishType.BaitName)) // 不能是已经失败两次的饵;
.Where(f => f.FishType.BaitName == blackboard.selectedBaitName).OrderByDescending(f => f.Confidence)
.Where(f => !ignoredBaits.Contains(f.FishType.BaitType)) // 不能是已经失败两次的饵;
.Where(f => f.FishType.BaitType == blackboard.selectedBait).OrderByDescending(f => f.Confidence)
.ToList();
if (list.Count > 0)
{
@@ -393,13 +415,17 @@ namespace BetterGenshinImpact.GameTask.AutoFishing
{
// 没有找到鱼饵适用鱼,重新选择鱼饵
blackboard.throwRodNoBaitFish = true;
blackboard.throwRodNoBaitFishFailures.Add(blackboard.selectedBaitName);
if (blackboard.throwRodNoBaitFishFailures.Count(f => f == blackboard.selectedBaitName) >= MAX_NO_BAIT_FISH_TIMES)
if (blackboard.selectedBait == null)
{
logger.LogWarning($"本次将忽略{BaitType.FromName(blackboard.selectedBaitName).ChineseName}");
throw new NullReferenceException();
}
blackboard.throwRodNoBaitFishFailures.Add(blackboard.selectedBait.Value);
if (blackboard.throwRodNoBaitFishFailures.Count(f => f == blackboard.selectedBait) >= MAX_NO_BAIT_FISH_TIMES)
{
logger.LogWarning("本次将忽略{bait}", blackboard.selectedBait.GetDescription());
}
blackboard.selectedBaitName = string.Empty;
blackboard.selectedBait = null;
logger.LogInformation("没有找到鱼饵适用鱼");
input.Mouse.LeftButtonUp();
blackboard.Sleep(2000);
@@ -684,7 +710,7 @@ namespace BetterGenshinImpact.GameTask.AutoFishing
private readonly DrawContent drawContent;
private readonly IOcrService ocrService;
private readonly string getABiteLocalizedString;
public FishBite(string name, Blackboard blackboard, ILogger logger, bool saveScreenshotOnTerminat, IInputSimulator input, IOcrService ocrService, DrawContent? drawContent = null, CultureInfo? cultureInfo = null, IStringLocalizer<AutoFishingImageRecognition>? stringLocalizer = null) : base(name, logger, saveScreenshotOnTerminat)
public FishBite(string name, Blackboard blackboard, ILogger logger, bool saveScreenshotOnTerminat, IInputSimulator input, IOcrService ocrService, DrawContent? drawContent = null, CultureInfo? cultureInfo = null, IStringLocalizer? stringLocalizer = null) : base(name, logger, saveScreenshotOnTerminat)
{
this.blackboard = blackboard;
this.input = input;

View File

@@ -1,4 +1,4 @@
using System;
using System;
using System.Collections.Generic;
using BetterGenshinImpact.Core.Recognition.ONNX;
using BetterGenshinImpact.GameTask.AutoFishing.Assets;
@@ -18,9 +18,9 @@ namespace BetterGenshinImpact.GameTask.AutoFishing
public bool abort = false;
/// <summary>
/// 已选择的鱼饵
/// 已选择的鱼饵类型
/// </summary>
public string selectedBaitName = string.Empty;
public BaitType? selectedBait = null;
/// <summary>
/// 鱼塘
@@ -44,9 +44,9 @@ namespace BetterGenshinImpact.GameTask.AutoFishing
/// <summary>
/// 抛竿无目标鱼失败列表
/// 失败一次就加入一次鱼饵,列表中同名鱼饵的数量代表该种失败了几次
/// 失败一次就加入一次鱼饵类型,列表中同名鱼饵的数量代表该种失败了几次
/// </summary>
public List<string> throwRodNoBaitFishFailures = new List<string>();
public List<BaitType> throwRodNoBaitFishFailures = new List<BaitType>();
/// <summary>
/// 拉条位置的识别框
@@ -61,9 +61,9 @@ namespace BetterGenshinImpact.GameTask.AutoFishing
/// <summary>
/// 选鱼饵失败列表
/// 失败一次就加入一次鱼饵,列表中同名鱼饵的数量代表该种失败了几次
/// 失败一次就加入一次鱼饵类型,列表中同名鱼饵的数量代表该种失败了几次
/// </summary>
public List<string> chooseBaitFailures = new List<string>();
public List<BaitType> chooseBaitFailures = new List<BaitType>();
/// <summary>
/// 镜头俯仰是否被行为重置
@@ -104,12 +104,12 @@ namespace BetterGenshinImpact.GameTask.AutoFishing
{
abort = false;
throwRodNoTargetTimes = 0;
throwRodNoBaitFishFailures = new List<string>();
throwRodNoBaitFishFailures = new List<BaitType>();
fishBoxRect = default;
chooseBaitUIOpening = false;
chooseBaitFailures = new List<string>();
chooseBaitFailures = new List<BaitType>();
pitchReset = true;
selectedBaitName = string.Empty;
selectedBait = null;
}
}
}

View File

@@ -1,54 +1,25 @@
using System.Collections.Generic;
using System.ComponentModel;
namespace BetterGenshinImpact.GameTask.AutoFishing.Model;
public class BaitType
public enum BaitType
{
public static readonly BaitType FruitPasteBait = new("fruit paste bait", "果酿饵");
public static readonly BaitType RedrotBait = new("redrot bait", "赤糜饵");
public static readonly BaitType FalseWormBait = new("false worm bait", "蠕虫假饵");
public static readonly BaitType FakeFlyBait = new("fake fly bait", "飞蝇假饵");
public static readonly BaitType SugardewBait = new("sugardew bait", "甘露饵");
public static readonly BaitType SourBait = new("sour bait", "酸桔饵");
public static readonly BaitType FlashingMaintenanceMekBait = new("flashing maintenance mek bait", "维护机关频闪诱饵");
public static readonly BaitType SpinelgrainBait = new("spinelgrain bait", "澄晶果粒饵");
public static readonly BaitType EmberglowBait = new("emberglow bait", "温火饵");
public static IEnumerable<BaitType> Values
{
get
{
yield return FruitPasteBait;
yield return RedrotBait;
yield return FalseWormBait;
yield return FakeFlyBait;
yield return SugardewBait;
yield return SourBait;
yield return FlashingMaintenanceMekBait;
yield return SpinelgrainBait;
yield return EmberglowBait;
}
}
public string Name { get; private set; }
public string ChineseName { get; private set; }
private BaitType(string name, string chineseName)
{
Name = name;
ChineseName = chineseName;
}
public static BaitType FromName(string name)
{
foreach (var type in Values)
{
if (type.Name == name)
{
return type;
}
}
throw new KeyNotFoundException($"BaitType {name} not found");
}
[Description("果酿饵")]
FruitPasteBait,
[Description("赤糜饵")]
RedrotBait,
[Description("蠕虫假饵")]
FalseWormBait,
[Description("飞蝇假饵")]
FakeFlyBait,
[Description("甘露饵")]
SugardewBait,
[Description("酸桔饵")]
SourBait,
[Description("维护机关频闪诱饵")]
FlashingMaintenanceMekBait,
[Description("澄晶果粒饵")]
SpinelgrainBait,
[Description("温火饵")]
EmberglowBait
}

View File

@@ -1,4 +1,4 @@
using System.Collections.Generic;
using System.Collections.Generic;
using System.Linq;
namespace BetterGenshinImpact.GameTask.AutoFishing.Model;
@@ -9,27 +9,27 @@ namespace BetterGenshinImpact.GameTask.AutoFishing.Model;
/// </summary>
public class BigFishType
{
public static readonly BigFishType Medaka = new("medaka", "fruit paste bait", "花鳉", 0);
public static readonly BigFishType LargeMedaka = new("large medaka", "fruit paste bait", "大花鳉", 1);
public static readonly BigFishType Stickleback = new("stickleback", "redrot bait", "棘鱼", 2);
public static readonly BigFishType Koi = new("koi", "fake fly bait", "假龙", 3);
public static readonly BigFishType KoiHead = new("koi head", "fake fly bait", "假龙头", 3);
public static readonly BigFishType Butterflyfish = new("butterflyfish", "false worm bait", "蝶鱼", 4);
public static readonly BigFishType Pufferfish = new("pufferfish", "fake fly bait", "炮鲀", 5);
public static readonly BigFishType Medaka = new("medaka", BaitType.FruitPasteBait, "花鳉", 0);
public static readonly BigFishType LargeMedaka = new("large medaka", BaitType.FruitPasteBait, "大花鳉", 1);
public static readonly BigFishType Stickleback = new("stickleback", BaitType.RedrotBait, "棘鱼", 2);
public static readonly BigFishType Koi = new("koi", BaitType.FakeFlyBait, "假龙", 3);
public static readonly BigFishType KoiHead = new("koi head", BaitType.FakeFlyBait, "假龙头", 3);
public static readonly BigFishType Butterflyfish = new("butterflyfish", BaitType.FalseWormBait, "蝶鱼", 4);
public static readonly BigFishType Pufferfish = new("pufferfish", BaitType.FakeFlyBait, "炮鲀", 5);
public static readonly BigFishType Ray = new("ray", "fake fly bait", "鳐", 6);
public static readonly BigFishType Ray = new("ray", BaitType.FakeFlyBait, "鳐", 6);
// public static readonly BigFishType FormaloRay = new("formalo ray", "fake fly bait", "佛玛洛鳐");
// public static readonly BigFishType DivdaRay = new("divda ray", "fake fly bait", "迪芙妲鳐");
public static readonly BigFishType Angler = new("angler", "sugardew bait", "角鲀", 7);
public static readonly BigFishType AxeMarlin = new("axe marlin", "sugardew bait", "斧枪鱼", 8);
public static readonly BigFishType HeartfeatherBass = new("heartfeather bass", "sour bait", "心羽鲈", 9);
public static readonly BigFishType MaintenanceMek = new("maintenance mek", "flashing maintenance mek bait", "维护机关", 10);
public static readonly BigFishType Unihornfish = new("unihornfish", "spinelgrain bait", "独角鱼", 10);
public static readonly BigFishType Sunfish = new("sunfish", "spinelgrain bait", "翻车鲀", 7);
public static readonly BigFishType Rapidfish = new("rapidfish", "spinelgrain bait", "斗士急流鱼", 9);
public static readonly BigFishType PhonyUnihornfish = new("phony unihornfish", "emberglow bait", "燃素独角鱼", 10);
public static readonly BigFishType MagmaRapidfish = new("magma rapidfish", "emberglow bait", "炽岩斗士急流鱼", 9);
// public static readonly BigFishType FormaloRay = new("formalo ray", "飞蝇假饵", "佛玛洛鳐");
// public static readonly BigFishType DivdaRay = new("divda ray", "飞蝇假饵", "迪芙妲鳐");
public static readonly BigFishType Angler = new("angler", BaitType.SugardewBait, "角鲀", 7);
public static readonly BigFishType AxeMarlin = new("axe marlin", BaitType.SugardewBait, "斧枪鱼", 8);
public static readonly BigFishType HeartfeatherBass = new("heartfeather bass", BaitType.SourBait, "心羽鲈", 9);
public static readonly BigFishType MaintenanceMek = new("maintenance mek", BaitType.FlashingMaintenanceMekBait, "维护机关", 10);
public static readonly BigFishType Unihornfish = new("unihornfish", BaitType.SpinelgrainBait, "独角鱼", 10);
public static readonly BigFishType Sunfish = new("sunfish", BaitType.SpinelgrainBait, "翻车鲀", 7);
public static readonly BigFishType Rapidfish = new("rapidfish", BaitType.SpinelgrainBait, "斗士急流鱼", 9);
public static readonly BigFishType PhonyUnihornfish = new("phony unihornfish", BaitType.EmberglowBait, "燃素独角鱼", 10);
public static readonly BigFishType MagmaRapidfish = new("magma rapidfish", BaitType.EmberglowBait, "炽岩斗士急流鱼", 9);
public static IEnumerable<BigFishType> Values
@@ -59,15 +59,15 @@ public class BigFishType
}
public string Name { get; private set; }
public string BaitName { get; private set; }
public BaitType BaitType { get; private set; }
public string ChineseName { get; private set; }
public int NetIndex { get; private set; }
private BigFishType(string name, string baitName, string chineseName, int netIndex)
private BigFishType(string name, BaitType baitType, string chineseName, int netIndex)
{
Name = name;
BaitName = baitName;
BaitType = baitType;
ChineseName = chineseName;
NetIndex = netIndex;
}

View File

@@ -1,9 +1,14 @@
using BetterGenshinImpact.GameTask.Model;
using BetterGenshinImpact.GameTask.Model;
using System.Threading;
namespace BetterGenshinImpact.GameTask.AutoGeniusInvokation;
public class GeniusInvokationTaskParam(string strategyContent) : BaseTaskParam
public class GeniusInvokationTaskParam : BaseTaskParam<AutoGeniusInvokationTask>
{
public string StrategyContent { get; set; } = strategyContent;
public GeniusInvokationTaskParam(string strategyContent) : base(null, null)
{
this.StrategyContent = strategyContent;
}
public string StrategyContent { get; set; }
}

View File

@@ -1,6 +1,11 @@
using BetterGenshinImpact.GameTask.Model;
using BetterGenshinImpact.GameTask.Model;
using System.Threading;
namespace BetterGenshinImpact.GameTask.AutoMusicGame;
public class AutoMusicGameParam : BaseTaskParam;
public class AutoMusicGameParam : BaseTaskParam<AutoMusicGameTask>
{
public AutoMusicGameParam() : base(null, null)
{
}
}

View File

@@ -1,8 +1,11 @@
using BetterGenshinImpact.GameTask.Model;
using BetterGenshinImpact.GameTask.Model;
using System.Threading;
namespace BetterGenshinImpact.GameTask.AutoSkip.Model;
public class AutoTrackParam : BaseTaskParam
public class AutoTrackParam : BaseTaskParam<AutoTrackTask>
{
public AutoTrackParam() : base(null, null)
{
}
}

View File

@@ -1,24 +1,14 @@
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Text.RegularExpressions;
using System.Threading;
using System.Threading.Tasks;
using BetterGenshinImpact.Core.BgiVision;
using BetterGenshinImpact.Core.Recognition;
using BetterGenshinImpact.Core.Recognition.OCR;
using BetterGenshinImpact.Core.Simulator;
using BetterGenshinImpact.Core.Simulator.Extensions;
using BetterGenshinImpact.GameTask.AutoArtifactSalvage;
using BetterGenshinImpact.GameTask.AutoDomain;
using BetterGenshinImpact.GameTask.AutoDomain.Model;
using BetterGenshinImpact.GameTask.AutoFight.Assets;
using BetterGenshinImpact.GameTask.AutoFight.Model;
using BetterGenshinImpact.GameTask.AutoFight.Script;
using BetterGenshinImpact.GameTask.AutoGeniusInvokation.Exception;
using BetterGenshinImpact.GameTask.AutoPick.Assets;
using BetterGenshinImpact.GameTask.AutoTrackPath;
using BetterGenshinImpact.GameTask.Common;
using BetterGenshinImpact.GameTask.Common.BgiVision;
using BetterGenshinImpact.GameTask.Common.Element.Assets;
@@ -29,6 +19,12 @@ using BetterGenshinImpact.Helpers.Extensions;
using BetterGenshinImpact.Service.Notification;
using BetterGenshinImpact.Service.Notification.Model.Enum;
using Microsoft.Extensions.Logging;
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using static BetterGenshinImpact.GameTask.Common.TaskControl;
using static Vanara.PInvoke.User32;
@@ -95,7 +91,7 @@ public class AutoStygianOnslaughtTask : ISoloTask
{
throw new Exception("幽境危战进入秘境失败!");
}
await Delay(1500, _ct); // 开始的三秒计时
// 队伍没初始化成功则重试
@@ -203,7 +199,7 @@ public class AutoStygianOnslaughtTask : ISoloTask
// F5 打开活动
Simulation.SendInput.SimulateAction(GIActions.OpenTheEventsMenu);
await page.GetByText("活动一览").WithRoi(r => r.CutLeftTop(0.3,0.2)).WaitFor();
await page.GetByText("活动一览").WithRoi(r => r.CutLeftTop(0.3, 0.2)).WaitFor();
await Delay(500, _ct);
if (page.GetByText("幽境危战").WithRoi(r => r.CutRight(0.5)).IsExist())
@@ -613,7 +609,7 @@ public class AutoStygianOnslaughtTask : ISoloTask
star = 4;
}
await new AutoArtifactSalvageTask(star).Start(_ct);
await new AutoArtifactSalvageTask(new AutoArtifactSalvageTaskParam(star, javaScript: null, artifactSetFilter: null, maxNumToCheck: null, recognitionFailurePolicy: null)).Start(_ct);
}

View File

@@ -1,8 +1,10 @@
using BetterGenshinImpact.GameTask.Model;
using BetterGenshinImpact.GameTask.Model;
using System;
using System.Threading;
namespace BetterGenshinImpact.GameTask.AutoTrackPath;
public class AutoTrackPathParam : BaseTaskParam
[Obsolete]
public class AutoTrackPathParam
{
}

View File

@@ -529,7 +529,7 @@ public partial class AutoWoodTask : ISoloTask
if (!ra.IsEmpty())
{
clickCnt++;
GameCaptureRegion.GameRegion1080PPosClick(955, 666);
GameCaptureRegion.GameRegion1080PPosClick(960, 630);
Debug.WriteLine("[AutoWood] Click entry");
}
else

View File

@@ -1,5 +1,3 @@
using BetterGenshinImpact.Core.Recognition.OpenCv;
using BetterGenshinImpact.Helpers.Extensions;
using System;
using System.Diagnostics;
using System.IO;
@@ -8,13 +6,8 @@ using System.Runtime.InteropServices;
using System.Text;
using System.Threading;
using BetterGenshinImpact.GameTask.Model.Area;
using Microsoft.Extensions.Logging;
using Vanara.PInvoke;
using static BetterGenshinImpact.GameTask.Common.TaskControl;
using BetterGenshinImpact.Core.Recognition;
using Fischless.GameCapture;
using OpenCvSharp;
using System.Collections.Generic;
namespace BetterGenshinImpact.GameTask.AutoWood.Utils;
@@ -110,64 +103,28 @@ internal sealed class Login3rdParty
{
if (Process.GetProcessesByName("YuanShen").FirstOrDefault() is Process process)
{
// 使用新的B服登录逻辑
// B服登录
var (loginWindow, windowType) = GetBiliLoginWindow(process);
if (loginWindow != IntPtr.Zero)
{
if (windowType.Contains("协议"))
{
ImageRegion screen;
try
{
screen = CaptureWindowToRectArea(loginWindow);
}
catch
{
screen = CaptureToRectArea();
}
var ocrList = screen.FindMulti(RecognitionObject.OcrThis);
var agreeRegion = ocrList.FirstOrDefault(r =>
r.Text.Contains("同意") && !r.Text.Contains("不同意"));
if (agreeRegion != null)
{
ClickRegionCenterBy1080(agreeRegion);
// 记录协议窗口点击位置
var (centerDesktopX, centerDesktopY) = agreeRegion.ConvertPositionToDesktopRegion(agreeRegion.Width / 2, agreeRegion.Height / 2);
var captureRect = TaskContext.Instance().SystemInfo.CaptureAreaRect;
var inCaptureX = centerDesktopX - captureRect.X;
var inCaptureY = centerDesktopY - captureRect.Y;
var scale = TaskContext.Instance().SystemInfo.ScaleTo1080PRatio;
lastAgreementClickPos = (inCaptureX / scale, inCaptureY / scale);
Debug.WriteLine("[AutoWood] Click protocol window for Bilibili using OCR");
}
Thread.Sleep(2000);
GameCaptureRegion.GameRegion1080PPosClick(1030, 615);
// 检查窗口是否还存在
var (remainingWindow, remainingType) = GetBiliLoginWindow(process);
if (remainingWindow == IntPtr.Zero || !remainingType.Contains("协议"))
{
// 协议窗口已消失,继续等待登录窗口
return false; // 继续循环等待登录窗口
return false;
}
return false; // 协议窗口仍然存在,继续尝试
// 协议窗口仍然存在,继续尝试
return false;
}
if (windowType.Contains("登录"))
{
Thread.Sleep(2000);
// 使用协议窗口坐标或默认坐标点击登录
if (lastAgreementClickPos.HasValue)
{
GameCaptureRegion.GameRegion1080PPosClick(lastAgreementClickPos.Value.x1080, lastAgreementClickPos.Value.y1080);
Debug.WriteLine("[AutoWood] Click login window for Bilibili");
}
else
{
GameCaptureRegion.GameRegion1080PPosClick(960, 620);
Debug.WriteLine("[AutoWood] Click login window for Bilibili");
}
GameCaptureRegion.GameRegion1080PPosClick(960, 630);
Thread.Sleep(2000);
// 检查窗口是否还存在
@@ -262,99 +219,4 @@ internal sealed class Login3rdParty
return (bHWnd, windowType);
}
private ImageRegion CaptureWindowToRectArea(IntPtr hWnd)
{
if (hWnd == IntPtr.Zero)
{
throw new ArgumentException("无效的窗口句柄", nameof(hWnd));
}
// BitBlt 方式
try
{
using var bitblt = GameCaptureFactory.Create(CaptureModes.BitBlt);
bitblt.Start(hWnd, new Dictionary<string, object> { { "autoFixWin11BitBlt", true } });
var img = GrabOneFrame(bitblt);
if (img == null) throw new Exception("BitBlt 无帧");
Debug.WriteLine($"[AutoWood] BitBlt 捕获窗口成功,尺寸 {img.Width}x{img.Height}");
return BuildWindowClientRegion(hWnd, img);
}
catch (Exception e)
{
Debug.WriteLine($"[AutoWood] BitBlt 捕获失败: {e.Message}");
}
// DwmSharedSurface方式
try
{
using var dwm = GameCaptureFactory.Create(CaptureModes.DwmGetDxSharedSurface);
dwm.Start(hWnd);
var img = GrabOneFrame(dwm);
if (img == null) throw new Exception("Dwm 无帧");
Debug.WriteLine($"[AutoWood] DwmSharedSurface 捕获窗口成功,尺寸 {img.Width}x{img.Height}");
return BuildWindowClientRegion(hWnd, img);
}
catch (Exception e)
{
Debug.WriteLine($"[AutoWood] DwmSharedSurface 捕获失败,准备回退: {e.Message}");
}
// 全部失败,抛出异常由调用方回退到游戏截图
throw new Exception("针对句柄的窗口截图失败");
}
private ImageRegion BuildWindowClientRegion(IntPtr hWnd, Mat img)
{
// 以窗口客户区的屏幕坐标为锚点,构造相对桌面的区域,使 Region.Click 映射到正确位置
if (!User32.GetClientRect(hWnd, out var clientRect))
{
// 回退:用窗口矩形
User32.GetWindowRect(hWnd, out var wr);
return TaskContext.Instance().SystemInfo.DesktopRectArea.Derive(img, wr.left, wr.top);
}
POINT pt = default;
User32.ClientToScreen(hWnd, ref pt);
return TaskContext.Instance().SystemInfo.DesktopRectArea.Derive(img, pt.X, pt.Y);
}
private Mat? GrabOneFrame(IGameCapture capture, int retry = 8, int delayMs = 40)
{
for (int i = 0; i < retry; i++)
{
var img = capture.Capture();
if (img != null)
{
return img;
}
Thread.Sleep(delayMs);
}
return null;
}
/// <summary>
/// 将 Region 中心点映射到 1080P 坐标系并点击。
/// </summary>
private void ClickRegionCenterBy1080(Region region)
{
// 计算区域中心的桌面坐标
var (centerDesktopX, centerDesktopY) = region.ConvertPositionToDesktopRegion(region.Width / 2, region.Height / 2);
// 转换到游戏捕获区域坐标
var captureRect = TaskContext.Instance().SystemInfo.CaptureAreaRect;
var inCaptureX = centerDesktopX - captureRect.X;
var inCaptureY = centerDesktopY - captureRect.Y;
if (inCaptureX < 0 || inCaptureY < 0)
{
// 不在游戏捕获区域内,直接回退为桌面点击
DesktopRegion.DesktopRegionClick(centerDesktopX, centerDesktopY);
return;
}
// 映射为 1080P 坐标
var scale = TaskContext.Instance().SystemInfo.ScaleTo1080PRatio;
var x1080 = inCaptureX / scale;
var y1080 = inCaptureY / scale;
GameCaptureRegion.GameRegion1080PPosClick(x1080, y1080);
}
}

View File

@@ -1,14 +1,14 @@
using BetterGenshinImpact.GameTask.Model;
using BetterGenshinImpact.GameTask.Model;
using System.Threading;
namespace BetterGenshinImpact.GameTask.AutoWood;
public class WoodTaskParam : BaseTaskParam
public class WoodTaskParam : BaseTaskParam<AutoWoodTask>
{
public int WoodRoundNum { get; set; }
public int WoodDailyMaxCount { get; set; }
public WoodTaskParam(int woodRoundNum, int woodDailyMaxCount)
public WoodTaskParam(int woodRoundNum, int woodDailyMaxCount) : base(null, null)
{
WoodRoundNum = woodRoundNum;
if (woodRoundNum == 0)

View File

@@ -59,6 +59,45 @@ public static partial class Bv
return false;
}
/// <summary>
/// 是否在秘境中
/// </summary>
/// <param name="captureRa"></param>
/// <returns></returns>
public static bool IsInDomain(ImageRegion captureRa)
{
using var matchRegion = captureRa.Find(ElementAssets.Instance.InDomainRo);
if (matchRegion.IsEmpty())
{
return false;
}
bool IsWhite(int r, int g, int b)
{
return (r >= 240 && r <= 255) &&
(g >= 240 && g <= 255) &&
(b >= 240 && b <= 255);
}
// 若全部为白色则视为不在秘境中
var samplePoints = new[]
{
new Point(matchRegion.X + matchRegion.Width / 2, matchRegion.Y + matchRegion.Height / 2),
new Point(matchRegion.X + matchRegion.Width / 4, matchRegion.Y + matchRegion.Height / 4),
new Point(matchRegion.X + matchRegion.Width * 3 / 4, matchRegion.Y + matchRegion.Height / 4),
new Point(matchRegion.X + matchRegion.Width / 4, matchRegion.Y + matchRegion.Height * 3 / 4),
new Point(matchRegion.X + matchRegion.Width * 3 / 4, matchRegion.Y + matchRegion.Height * 3 / 4)
};
bool allWhite = samplePoints.All(pt =>
{
var v = captureRa.SrcMat.At<Vec3b>(pt.Y, pt.X);
return IsWhite(v.Item2, v.Item1, v.Item0);
});
return !allWhite && !IsInRevivePrompt(captureRa);
}
/// <summary>
/// 在任意可以关闭的UI界面识别关闭按钮

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.8 KiB

After

Width:  |  Height:  |  Size: 5.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.5 KiB

View File

@@ -15,6 +15,7 @@ public class ElementAssets : BaseAssets<ElementAssets>
public RecognitionObject BtnOnlineYes;
public RecognitionObject BtnOnlineNo;
public Lazy<RecognitionObject> BtnExitDoor;
public RecognitionObject InDomainRo;
public RecognitionObject PaimonMenuRo;
public RecognitionObject BlueTrackPoint;
@@ -136,6 +137,16 @@ public class ElementAssets : BaseAssets<ElementAssets>
TemplateImageMat = GameTaskManager.LoadAssetImage(@"Common\Element", "btn_exit_door.png"),
DrawOnWindow = false
}.InitTemplate());
// 秘境退出图标
InDomainRo = new RecognitionObject
{
Name = "InDomain",
RecognitionType = RecognitionTypes.TemplateMatch,
TemplateImageMat = GameTaskManager.LoadAssetImage(@"Common\Element", "in_domain.png"),
RegionOfInterest = new Rect(0, 0, CaptureRect.Width / 4, CaptureRect.Height / 4),
DrawOnWindow = false
}.InitTemplate();
// 派蒙菜单
// 此图38x40 小地图210x210 小地图左上角位置 24,-15

View File

@@ -0,0 +1,84 @@
using BetterGenshinImpact.Core.Recognition.OCR;
using BetterGenshinImpact.Core.Simulator;
using BetterGenshinImpact.GameTask.AutoArtifactSalvage;
using BetterGenshinImpact.GameTask.GetGridIcons;
using BetterGenshinImpact.GameTask.Model.Area;
using BetterGenshinImpact.GameTask.Model.GameUI;
using Fischless.WindowsInput;
using Microsoft.Extensions.Logging;
using Microsoft.ML.OnnxRuntime;
using OpenCvSharp;
using System;
using System.Collections.Generic;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
namespace BetterGenshinImpact.GameTask.Common.Job
{
internal class CountInventoryItem : ISoloTask<int>
{
public string Name => "背包数物品";
private readonly ILogger logger = App.GetLogger<CountInventoryItem>();
private readonly InputSimulator input = Simulation.SendInput;
private CancellationToken ct;
private readonly GridScreenName gridScreenName;
private readonly string foodName;
public CountInventoryItem(GridScreenName gridScreenName, string foodName)
{
this.gridScreenName = gridScreenName;
this.foodName = foodName;
}
public async Task<int> Start(CancellationToken ct)
{
this.ct = ct;
logger.LogInformation("打开背包并在{grid}寻找{name}……", this.gridScreenName, this.foodName);
await new ReturnMainUiTask().Start(ct);
await AutoArtifactSalvageTask.OpenInventory(this.gridScreenName, input, logger, this.ct);
using InferenceSession session = GridIconsAccuracyTestTask.LoadModel(out Dictionary<string, float[]> prototypes);
using var ra = TaskControl.CaptureToRectArea();
GridScreen gridScreen = new GridScreen(GridParams.Templates[this.gridScreenName], logger, ct);
int? count = null;
await foreach (ImageRegion itemRegion in gridScreen)
{
using Mat icon = itemRegion.SrcMat.GetGridIcon();
var result = GridIconsAccuracyTestTask.Infer(icon, session, prototypes);
string predName = result.Item1;
if (predName == this.foodName)
{
string numStr = itemRegion.SrcMat.GetGridItemIconText(OcrFactory.Paddle);
if (int.TryParse(numStr, out int num))
{
count = num;
}
else
{
count = -2;
logger.LogWarning("无法识别数量:{text}", numStr);
}
break;
}
}
if (count == null)
{
count = -1;
logger.LogInformation("没有找到{name}", this.foodName);
}
await new ReturnMainUiTask().Start(ct);
return count.Value;
}
async Task ISoloTask.Start(CancellationToken ct)
{
await Start(ct);
}
}
}

View File

@@ -102,9 +102,16 @@ public class GoToCraftingBenchTask
{
// 图像下方就是脆弱树脂数量
var countArea = ra.DeriveCrop(fragileResinCountRa.X, fragileResinCountRa.Y + fragileResinCountRa.Height,
fragileResinCountRa.Width, fragileResinCountRa.Height/3);
var count = OcrFactory.Paddle.Ocr(countArea.CacheGreyMat);
fragileResinCount = StringUtils.TryParseInt(count);
fragileResinCountRa.Width, fragileResinCountRa.Height);
var count = OcrFactory.Paddle.OcrWithoutDetector(countArea.SrcMat);
// Logger.LogInformation("识别原粹树脂数量:{Count}", count);
var match = System.Text.RegularExpressions.Regex.Match(count, @"(\d+)\s*[/17]\s*(4|40)");
if (match.Success)
{
var numericPart = match.Groups[1].Value;
fragileResinCount = StringUtils.TryParseInt(numericPart);
Logger.LogInformation("提取到的原粹树脂数量:{fragileResinCount}", fragileResinCount);
}
}
var condensedResinCountRa = ra.Find(ElementAssets.Instance.CondensedResinCount);
if (!condensedResinCountRa.IsEmpty())

View File

@@ -1,29 +1,19 @@
using BetterGenshinImpact.Core.Config;
using BetterGenshinImpact.GameTask.GameLoading.Assets;
using System;
using System.Collections.Generic;
using System.Diagnostics;
using BetterGenshinImpact.GameTask.Common;
using BetterGenshinImpact.GameTask.Common.BgiVision;
using BetterGenshinImpact.GameTask.Common.Element.Assets;
using BetterGenshinImpact.GameTask.Model.Area;
using Microsoft.Extensions.Logging;
using BetterGenshinImpact.Genshin.Paths;
using System.IO;
using System.Text.RegularExpressions;
using System.Threading.Channels;
using Microsoft.Win32;
using System.Windows.Documents;
using System.Linq;
using System.Threading;
using System.Runtime.InteropServices;
using System.Text;
using BetterGenshinImpact.Core.Recognition.OpenCv;
using BetterGenshinImpact.Helpers.Extensions;
using Vanara.PInvoke;
using BetterGenshinImpact.Core.Recognition;
using Fischless.GameCapture;
using OpenCvSharp;
namespace BetterGenshinImpact.GameTask.GameLoading;
@@ -37,6 +27,9 @@ public class GameLoadingTrigger : ITaskTrigger
public bool IsExclusive => false;
public bool IsBiliJudged = false;
public bool IsBili = false;
public bool IsBackgroundRunning => true;
private readonly GameLoadingAssets _assets;
@@ -244,43 +237,47 @@ public class GameLoadingTrigger : ITaskTrigger
IsEnabled = false;
return;
}
if (Bv.IsInMainUi(content.CaptureRectArea) || Bv.IsInAnyClosableUi(content.CaptureRectArea))
// 成功进入游戏判断
if (Bv.IsInMainUi(content.CaptureRectArea) || Bv.IsInAnyClosableUi(content.CaptureRectArea) || Bv.IsInDomain(content.CaptureRectArea))
{
_logger.LogInformation("已进入游戏");
IsEnabled = false;
return;
}
// B服判断逻辑
bool isBili = false;
try
// B服判断
if (!IsBiliJudged)
{
var exePath = _config.InstallPath;
if (!string.IsNullOrEmpty(exePath))
try
{
var configIni = Path.Combine(Path.GetDirectoryName(exePath)!, "config.ini");
if (File.Exists(configIni))
var exePath = _config.InstallPath;
if (!string.IsNullOrEmpty(exePath))
{
var lines = File.ReadAllLines(configIni);
foreach (var line in lines)
var configIni = Path.Combine(Path.GetDirectoryName(exePath)!, "config.ini");
if (File.Exists(configIni))
{
var kv = line.Trim();
if (kv.StartsWith("channel=") && kv.EndsWith("14"))
var lines = File.ReadAllLines(configIni);
foreach (var line in lines)
{
isBili = true;
break;
var kv = line.Trim();
if (kv.StartsWith("channel=") && kv.EndsWith("14"))
{
IsBili = true;
break;
}
}
}
}
}
}
catch (Exception ex)
{
TaskControl.Logger.LogWarning("B服判断异常: " + ex.Message);
catch (Exception ex)
{
TaskControl.Logger.LogWarning("B服判断异常: " + ex.Message);
}
IsBiliJudged = true;
}
// 官服流程:先识别并点击顶号或切号的后一次“进入游戏”弹窗按钮
if (!isBili)
if (!IsBili)
{
var extraEnterGameBtn = content.CaptureRectArea.Find(_assets.ChooseEnterGameRo);
if (!extraEnterGameBtn.IsEmpty())
@@ -290,7 +287,7 @@ public class GameLoadingTrigger : ITaskTrigger
}
}
// 官服流程:点击进入游戏按钮(作为外层包装)
// 点击进入游戏按钮
var ra = content.CaptureRectArea.Find(_assets.EnterGameRo);
if (!ra.IsEmpty())
@@ -301,7 +298,7 @@ public class GameLoadingTrigger : ITaskTrigger
}
// 只有在"进入游戏"按钮未出现时才进行B服登录处理
if (isBili && !biliLoginClicked)
if (IsBili && !biliLoginClicked)
{
// B服流程处理登录窗口
var process = Process.GetProcessesByName("YuanShen").FirstOrDefault();
@@ -311,89 +308,28 @@ public class GameLoadingTrigger : ITaskTrigger
{
if (windowType.Contains("协议"))
{
ImageRegion screen;
try
{
screen = CaptureWindowToRectArea(loginWindow);
}
catch
{
screen = TaskControl.CaptureToRectArea();
}
var ocrList = screen.FindMulti(RecognitionObject.OcrThis);
var agreeRegion = ocrList.FirstOrDefault(r =>
r.Text.Contains("同意") && !r.Text.Contains("不同意"));
if (agreeRegion != null)
{
ClickRegionCenterBy1080(agreeRegion);
// 记录协议窗口点击位置
var (centerDesktopX, centerDesktopY) =
agreeRegion.ConvertPositionToDesktopRegion(agreeRegion.Width / 2,
agreeRegion.Height / 2);
var captureRect = TaskContext.Instance().SystemInfo.CaptureAreaRect;
var inCaptureX = centerDesktopX - captureRect.X;
var inCaptureY = centerDesktopY - captureRect.Y;
var scale = TaskContext.Instance().SystemInfo.ScaleTo1080PRatio;
lastAgreementClickPos = (inCaptureX / scale, inCaptureY / scale);
SystemControl.FocusWindow(TaskContext.Instance().GameHandle);
}
Thread.Sleep(2000);
GameCaptureRegion.GameRegion1080PPosClick(1030, 615);
}
if (windowType.Contains("登录"))
{
Thread.Sleep(2000);
// 使用协议窗口坐标或默认坐标点击登录
if (lastAgreementClickPos.HasValue)
{
GameCaptureRegion.GameRegion1080PPosClick(lastAgreementClickPos.Value.x1080,
lastAgreementClickPos.Value.y1080);
}
else
{
GameCaptureRegion.GameRegion1080PPosClick(960, 620);
}
GameCaptureRegion.GameRegion1080PPosClick(960, 630);
Thread.Sleep(2000);
// 检查窗口是否还存在
var (remainingWindow, remainingType) = GetBiliLoginWindow(process);
if (remainingWindow == IntPtr.Zero)
{
_logger.LogInformation("B服登录完成准备进入游戏");
// 添加延时确保窗口完全消失
Thread.Sleep(2000);
// 点击屏幕尝试找回焦点
TaskContext.Instance().PostMessageSimulator.LeftButtonClickBackground();
biliLoginClicked = true;
}
}
}
// 在B服登录过程中每次循环都检查是否出现"进入游戏"按钮
ra = content.CaptureRectArea.Find(_assets.EnterGameRo);
if (!ra.IsEmpty())
{
_logger.LogInformation("检测到进入游戏按钮,直接点击");
TaskContext.Instance().PostMessageSimulator.LeftButtonClickBackground();
biliLoginClicked = true;
return;
}
Thread.Sleep(1000);
// 检查是否成功登录
if (biliLoginClicked)
{
_logger.LogInformation("B服登录完成等待后尝试点击进入游戏");
Thread.Sleep(5000);
ClickEnterGameButton();
return;
}
}
else if (!isBili)
{
// 官服流程:直接点击进入游戏按钮
ClickEnterGameButton();
return;
}
var wmRa = content.CaptureRectArea.Find(_assets.WelkinMoonRo);
@@ -485,108 +421,4 @@ public class GameLoadingTrigger : ITaskTrigger
return (bHWnd, windowType);
}
private void ClickEnterGameButton()
{
TaskContext.Instance().PostMessageSimulator.LeftButtonClickBackground();
}
private ImageRegion CaptureWindowToRectArea(IntPtr hWnd)
{
if (hWnd == IntPtr.Zero)
{
throw new ArgumentException("无效的窗口句柄", nameof(hWnd));
}
// BitBlt 方式
try
{
using var bitblt = GameCaptureFactory.Create(CaptureModes.BitBlt);
bitblt.Start(hWnd, new Dictionary<string, object> { { "autoFixWin11BitBlt", true } });
var img = GrabOneFrame(bitblt);
if (img == null) throw new Exception("BitBlt 无帧");
_logger.LogDebug("BitBlt 捕获窗口成功,尺寸 {W}x{H}", img.Width, img.Height);
return BuildWindowClientRegion(hWnd, img);
}
catch (Exception e)
{
_logger.LogWarning("BitBlt 捕获失败: {Msg}", e.Message);
}
// DwmSharedSurface方式
try
{
using var dwm = GameCaptureFactory.Create(CaptureModes.DwmGetDxSharedSurface);
dwm.Start(hWnd);
var img = GrabOneFrame(dwm);
if (img == null) throw new Exception("Dwm 无帧");
_logger.LogDebug("DwmSharedSurface 捕获窗口成功,尺寸 {W}x{H}", img.Width, img.Height);
return BuildWindowClientRegion(hWnd, img);
}
catch (Exception e)
{
_logger.LogDebug("DwmSharedSurface 捕获失败,准备回退: {Msg}", e.Message);
}
// 全部失败,抛出异常由调用方回退到游戏截图
throw new Exception("针对句柄的窗口截图失败");
}
private ImageRegion BuildWindowClientRegion(IntPtr hWnd, Mat img)
{
// 以窗口客户区的屏幕坐标为锚点,构造相对桌面的区域,使 Region.Click 映射到正确位置
if (!User32.GetClientRect(hWnd, out var clientRect))
{
// 回退:用窗口矩形
User32.GetWindowRect(hWnd, out var wr);
return TaskContext.Instance().SystemInfo.DesktopRectArea.Derive(img, wr.left, wr.top);
}
POINT pt = default;
User32.ClientToScreen(hWnd, ref pt);
return TaskContext.Instance().SystemInfo.DesktopRectArea.Derive(img, pt.X, pt.Y);
}
private Mat? GrabOneFrame(IGameCapture capture, int retry = 8, int delayMs = 40)
{
for (int i = 0; i < retry; i++)
{
var img = capture.Capture();
if (img != null)
{
return img;
}
Thread.Sleep(delayMs);
}
return null;
}
/// <summary>
/// 将 Region 中心点映射到 1080P 坐标系并点击。
/// </summary>
private void ClickRegionCenterBy1080(Region region)
{
// 计算区域中心的桌面坐标
var (centerDesktopX, centerDesktopY) =
region.ConvertPositionToDesktopRegion(region.Width / 2, region.Height / 2);
// 转换到游戏捕获区域坐标
var captureRect = TaskContext.Instance().SystemInfo.CaptureAreaRect;
var inCaptureX = centerDesktopX - captureRect.X;
var inCaptureY = centerDesktopY - captureRect.Y;
if (inCaptureX < 0 || inCaptureY < 0)
{
// 不在游戏捕获区域内,直接回退为桌面点击
DesktopRegion.DesktopRegionClick(centerDesktopX, centerDesktopY);
return;
}
// 映射为 1080P 坐标
var scale = TaskContext.Instance().SystemInfo.ScaleTo1080PRatio;
var x1080 = inCaptureX / scale;
var y1080 = inCaptureY / scale;
GameCaptureRegion.GameRegion1080PPosClick(x1080, y1080);
}
};

View File

@@ -1,7 +1,9 @@
using BetterGenshinImpact.Core.Recognition.OCR;
using BetterGenshinImpact.Core.Simulator;
using BetterGenshinImpact.GameTask.AutoArtifactSalvage;
using BetterGenshinImpact.GameTask.Common;
using BetterGenshinImpact.GameTask.Common.Job;
using BetterGenshinImpact.GameTask.Model;
using BetterGenshinImpact.GameTask.Model.Area;
using BetterGenshinImpact.GameTask.Model.GameUI;
using BetterGenshinImpact.Helpers.Extensions;
@@ -48,6 +50,10 @@ public class GetGridIconsTask : ISoloTask
{
this.ct = ct;
int count = this.maxNumToGet ?? int.MaxValue;
string directory = Path.Combine(AppContext.BaseDirectory, "log/gridIcons", $"{this.gridScreenName}{DateTime.Now:yyyyMMddHHmmss}");
Directory.CreateDirectory(directory);
switch (this.gridScreenName)
{
case GridScreenName.Weapons:
@@ -60,23 +66,23 @@ public class GetGridIconsTask : ISoloTask
case GridScreenName.PreciousItems:
case GridScreenName.Furnishings:
await new ReturnMainUiTask().Start(ct);
await AutoArtifactSalvageTask.OpenBag(this.gridScreenName, this.input, this.logger, this.ct);
await AutoArtifactSalvageTask.OpenInventory(this.gridScreenName, this.input, this.logger, this.ct);
break;
case GridScreenName.ArtifactSetFilter:
logger.LogInformation("{name}暂不支持自动打开,请提前手动打开界面", gridScreenName.GetDescription());
await GetArtifactSetFilterGridIcons(count, directory);
return;
default:
logger.LogInformation("{name}暂不支持自动打开,请提前手动打开界面", gridScreenName.GetDescription());
break;
}
using var ra0 = CaptureToRectArea();
GridScreenParams gridParams = GridScreenParams.Templates[this.gridScreenName];
Rect gridRoi = gridParams.GetRect(ra0);
await GetInventoryGridIcons(count, directory);
}
int count = this.maxNumToGet ?? int.MaxValue;
string directory = Path.Combine(AppContext.BaseDirectory, "log/gridIcons", DateTime.Now.ToString("yyyyMMddHHmmss"));
Directory.CreateDirectory(directory);
GridScreen gridScreen = new GridScreen(gridRoi, gridParams, this.logger, this.ct);
private async Task GetInventoryGridIcons(int count, string directory)
{
GridScreen gridScreen = new GridScreen(GridParams.Templates[this.gridScreenName], this.logger, this.ct);
HashSet<string> fileNames = new HashSet<string>();
await foreach (ImageRegion itemRegion in gridScreen)
{
@@ -130,6 +136,106 @@ public class GetGridIconsTask : ISoloTask
}
}
private async Task GetArtifactSetFilterGridIcons(int count, string directory)
{
ArtifactSetFilterScreen gridScreen = new ArtifactSetFilterScreen(new GridParams(new Rect(40, 100, 1300, 852), 2, 3, 40, 40, 0.024), this.logger, this.ct);
HashSet<string> fileNames = new HashSet<string>();
await foreach (ImageRegion itemRegion in gridScreen)
{
itemRegion.Click();
await Delay(300, ct);
static bool tryGetFlower(out string flowerName)
{
using var ra1 = CaptureToRectArea();
using ImageRegion nameRegion = ra1.DeriveCrop(new Rect((int)(ra1.Width * 0.714), (int)(ra1.Width * 0.284), (int)(ra1.Width * 0.256), (int)(ra1.Width * 0.208)));
var ocrResult = OcrFactory.Paddle.OcrResult(nameRegion.SrcMat);
var flowerWithGlyph = ocrResult.Regions.OrderBy(r => r.Rect.Center.Y).SkipWhile(r => !r.Text.Contains("套装包含")).Skip(1).FirstOrDefault();
if (flowerWithGlyph == default)
{
nameRegion.Move();
flowerName = string.Empty;
return false;
}
// 可能带有花形符号
Rect flowerWithGlyphRect = flowerWithGlyph.Rect.BoundingRect();
// 费解的是,原图识别没问题,但为了排除名称前的花形符号,无论裁切还是不裁切只是将符号涂白,都会把一些花名识别出旧体字
// 花形符号往往还被识别为空格,导致无法用识别框位置来区分
// 截取没有符号的区域再识别一次
Rect flowerWithoutGlyph = new Rect((int)(ra1.Width * 0.028), (int)(flowerWithGlyphRect.Y - flowerWithGlyphRect.Height * 0), (int)(ra1.Width * 0.228), (int)(flowerWithGlyphRect.Height * 1));
Mat roi = nameRegion.SrcMat.SubMat(flowerWithoutGlyph);
var whiteOcrResult = OcrFactory.Paddle.OcrResult(roi);
flowerName = whiteOcrResult.Text;
// 所以只好识别两次Trim后根据字数取原截图OCR的结果……
flowerName = flowerWithGlyph.Text.Trim().Substring(flowerWithGlyph.Text.Trim().Length - flowerName.Trim().Length);
return true;
}
if (!tryGetFlower(out string flowerName))
{
await TaskControl.Delay(100, this.ct);
for (int i = 0; i < 5; i++)
{
this.input.Mouse.VerticalScroll(-2);
await TaskControl.Delay(40, this.ct);
}
await TaskControl.Delay(300, this.ct);
if (!tryGetFlower(out flowerName))
{
throw new Exception("尝试获取生之花失败");
//flowerName = $"识别失败{nameRegion.GetHashCode()}";
}
}
string fileName = flowerName;
if (fileNames.Add(fileName))
{
string filePath = Path.Combine(directory, $"{fileName}.png");
Thread saveThread = new Thread(() =>
{
try
{
using (FileStream fs = new FileStream(filePath, FileMode.Create, FileAccess.Write, FileShare.None))
{
using Mat img125 = CropResizeArtifactSetFilterGridIcon(itemRegion);
img125.ToBitmap().Save(fs, System.Drawing.Imaging.ImageFormat.Png);
}
logger.LogInformation("图片保存成功:{Text}", fileName);
}
catch (Exception e)
{
logger.LogError(e, "图片保存失败:{Text}", fileName);
}
});
saveThread.IsBackground = true; // 设置为后台线程
saveThread.Start();
}
else
{
logger.LogInformation("重复的物品:{Text}", fileName);
}
count--;
if (count <= 0)
{
logger.LogInformation("检查次数已耗尽");
break;
}
}
}
internal static Mat CropResizeArtifactSetFilterGridIcon(ImageRegion itemRegion, ISystemInfo? systemInfo = null)
{
double scale = (systemInfo ?? TaskContext.Instance().SystemInfo).AssetScale;
double width = 60;
double height = 60; // 宽高缩放似乎不一致似乎在2.05:2.15之间,但不知道怎么测定
Rect iconRect = new Rect((int)(itemRegion.Width / 2 - 237 * scale - width / 2), (int)(itemRegion.Height / 2 - height / 2), (int)width, (int)height);
Mat crop = itemRegion.SrcMat.SubMat(iconRect);
return crop.Resize(new Size(125, 125));
}
/// <summary>
/// OCR检测★字符很不稳定因此用cv
/// 非常简陋的色彩检测,请传入聚焦的图像,勿带入可能的干扰

View File

@@ -51,7 +51,7 @@ public class GridIconsAccuracyTestTask : ISoloTask
public static InferenceSession LoadModel(out Dictionary<string, float[]> prototypes)
{
#region model
var session = new InferenceSession(@".\GameTask\GetGridIcons\gridIcon.onnx"); // todo 所有数据炼好后放到onnx统一存放的位置去
var session = new InferenceSession(@".\Assets\Model\Item\gridIcon.onnx");
var metadata = session.ModelMetadata;
@@ -62,7 +62,7 @@ public class GridIconsAccuracyTestTask : ISoloTask
List<string> prefixList = System.Text.Json.JsonSerializer.Deserialize<List<string>>(prefixListJson) ?? throw new Exception(); // 不预测前缀
#endregion
#region
var allLines = File.ReadLines(@".\GameTask\GetGridIcons\训练集原型特征.csv").Skip(1); // 跳过首行列名
var allLines = File.ReadLines(@".\Assets\Model\Item\items.csv").Skip(1); // 跳过首行列名
prototypes = new Dictionary<string, float[]>();
foreach (string line in allLines)
{
@@ -93,7 +93,7 @@ public class GridIconsAccuracyTestTask : ISoloTask
case GridScreenName.PreciousItems:
case GridScreenName.Furnishings:
await new ReturnMainUiTask().Start(ct);
await AutoArtifactSalvageTask.OpenBag(this.gridScreenName, this.input, this.logger, this.ct);
await AutoArtifactSalvageTask.OpenInventory(this.gridScreenName, this.input, this.logger, this.ct);
break;
default:
logger.LogInformation("{name}暂不支持自动打开,请提前手动打开界面", gridScreenName.GetDescription());
@@ -102,25 +102,21 @@ public class GridIconsAccuracyTestTask : ISoloTask
using InferenceSession session = LoadModel(out Dictionary<string, float[]> prototypes);
using var ra0 = CaptureToRectArea();
GridScreenParams gridParams = GridScreenParams.Templates[this.gridScreenName];
Rect gridRoi = gridParams.GetRect(ra0);
int count = this.maxNumToTest ?? int.MaxValue;
double total_acc = 0.0;
double total_count = 0;
GridScreen gridScreen = new GridScreen(gridRoi, gridParams, this.logger, this.ct);
GridScreen gridScreen = new GridScreen(GridParams.Templates[this.gridScreenName], this.logger, this.ct);
await foreach (ImageRegion itemRegion in gridScreen)
{
itemRegion.Click();
Task task1 = Delay(300, ct);
var sadf = task1.Status;
// 用模型推理得到的结果
Task<(string, int)> task2 = Task.Run(() =>
{
return Infer(itemRegion.SrcMat, session, prototypes);
using Mat icon = itemRegion.SrcMat.GetGridIcon();
return Infer(icon, session, prototypes);
}, ct);
await Task.WhenAll(task1, task2);
@@ -158,11 +154,21 @@ public class GridIconsAccuracyTestTask : ISoloTask
}
}
// todo: 单元测试
/// <summary>
/// 请自行裁剪缩放到125*125尺寸
/// </summary>
/// <param name="mat"></param>
/// <param name="session"></param>
/// <param name="prototypes"></param>
/// <returns>(预测名称, 预测星级)</returns>
/// <exception cref="Exception"></exception>
public static (string, int) Infer(Mat mat, InferenceSession session, Dictionary<string, float[]> prototypes)
{
using Mat resized = mat.Resize(new Size(125, 153));
using Mat rgb = resized.CvtColor(ColorConversionCodes.BGR2RGB);
if (mat.Size().Width != 125 || mat.Size().Height != 125)
{
throw new ArgumentOutOfRangeException(nameof(mat), "输入图像尺寸应为125*125");
}
using Mat rgb = mat.CvtColor(ColorConversionCodes.BGR2RGB);
var tensor = new DenseTensor<float>(new[] { 1, 3, rgb.Height, rgb.Width }); // todo 放到BgiOnnxFactory那边去做个Mat->NamedOnnxValue的通用方法
for (int y = 0; y < rgb.Height; y++)
{
@@ -173,7 +179,7 @@ public class GridIconsAccuracyTestTask : ISoloTask
tensor[0, 2, y, x] = rgb.At<Vec3b>(y, x)[2] / 255f;
}
}
var inputs = new List<NamedOnnxValue> { NamedOnnxValue.CreateFromTensor("input", tensor) };
var inputs = new List<NamedOnnxValue> { NamedOnnxValue.CreateFromTensor("input_image", tensor) };
using var results = session.Run(inputs);
float[] feature_matrix = results[0].AsEnumerable<float>().ToArray();
string? pred_name = null;

View File

@@ -1,130 +0,0 @@
标注名称,特征向量
方块戏法,XpMYQGBCksAkDUjAlNVxwMnysECojt0+9ZluwJ7Ij8AYE5xAYquCwDWfB8GCpbi/XSGRwMH6T8AuQQFBf6APwNEFiED4ipVAUxQLQYjiocCwv4xAmhUoQH5yJ79LQPS/LdEVQa6MlsDeL81AFhihQGfS8z8Si1g+VTnCwEPgGsBisMZABH6NwE9Uj0Cb0gNBRdaUwGjldMBTvBRAFt5QQGhV3sDa1RXBzIAQvg/X0j9GAh/AT+2iwM3bvT8kRklAm+QOwRSFaD9T+K8/H0aFQKNGT0B7ENpAFEJMP9WAlEAEsEbA+cQDwNzimr1OkMNAo0WlQJTfBD6G+nRA6sNrwA==
绝对不是下酒菜,FKe5wH/MFMBcTJo9sNj2v+qiwkB8rK1AkGRYPBYI/r+H2hNAKP37v199v8AibxHACp4HwPIAjj+04TVBcG2ewPTAB0CRfkJAS9LdQGUYSD8gv7+8Cpq5QFmMe0CaIy6+ZApCQfoPCcDRCjFA7b0dQLyMBUH14ls/ZENrwOtpSb8iRpBAxeIgwAPOUED4661A8NgDwBZcecCsQRS/pCkfPYKGCUBD/xPACEcdQCtAkEAVVdM+050VQPRYtL8kEErAW206wcVWikAhyUFAcVHjQCuIi0BXfr5Av/qYPPAfiT02avXAD7wxwSbHrsANOINAz68gQDVpiEAlm0DA6J8mvw==
黄金乐曲的变奏,DzorP+aKxsCKTQDAQnjpP3603z9ikVPANFFmwBF9uMCDVKRAAA3dwLNPDcHoWtrAZnWqwB74dcDgm61A6qsOwd0OtMCdnDxARNUSQRSz0cDcmKtA+Vo0QK6LXUADMC8/f3iYQRi3ycDkZfZAd9CYQEZs5T/RfI5AXqi8wAPZoMDHgsZAVDK2wDhEd0D8UwZBx1KawHqN0sBnlIdAsrS/P1g650A0mmTBVJ2EwYiFA0Ecvxo/uUaGwDjoob9WB6m/0rs0wfzuAcDOv71AmgQHQbr5XkFTpjtButdPQGDrqj/ObSjB3ARswQhsn8A+aOdAnGWwQDqNJkB4GexA2Dl8wA==
黄金飞鸟的落羽,8DumPB1Dp8CSdTvAn9E9QJApAUBY+1s/KmdRwIvjlsDoVaxA7F+ZwIYQDsHTovq/aXyTwADPZsDLrLdA52WfwIbSXMD4oHJAWNoOQTJymMAKAIpAWC46QEsMFkDgtZq/0ts9QS39ksCTAuFADpqQQFg8gD9doKA/iLG2wO9xoMDLPspAHBmzwKAee0AeVwRB6wWNwCxXvb82dZtAykwrQCRACr+vqTDBNvNIwa+/iEDNpAHAZLvowC+Vdz988spAfv75wH3s/j9BOwJAoEWUQMjvy0CoNpFArHCOwD3S8z/UiIfA7J3YwFQcEsCJNMxAhPqcQIYCQcB3OoJAUKN6wA==
枫达,pC2IwFdWkr97ffM/ZdvAP218/T/CbaBAOx+EP+/ZTr9sKOE/1W2iv+OlwcDBrA8/8pViwLw6zj122/w+g/68wJ5dZ7/XFhBAyMrVQOZeK0D6iba/tyYMP0Bc8T/qMSC/ULE0QZdXn7/c6SlAoSj1P1dyy0BFLV8/uR10wJvVs0CYOXlAkS4wwGNrlj8cAKtASrgMwFkblr8m3FvAKP/hP1rXNcB+UtU+j3LIwF6csT5EGRq/SG6RwG5tF75AlAk/HywuwSwgNT/6iPA/VPugQDVoX7/4zxVBE4hQv36fHj8jk3PAjq+nwGzGAsBFl05A2dX6P78tJEBQxE/AwuQXwA==
常胜传说,4EacwK2s9r/srus/k8Ssvzc5iD/IzmdAlsQmP2LcAMD2S6U/L1YiwH2htcCvPWDAnHxIwIRiUD+6jVJAixUDwUN1EMBza7o/hJnJQFHjMT/Wg7E+nG0yQGmVXEA6Pi2/2J6QQdHG8L/wCCxA5XzOP8Bi/0Dpg2hAh3VdwDCmXcCRvHtAv0ANwEjYcj9CKaBAKHkCwHSJ0MAh7c++IOtXv8b+C0BluMzAQLyUwGD3X0CJd98/kPexwBoq/7/wr128D+BZwc+o8D/m6aBAbVH3QFYvbEAU1lhBbjQxv/IRFEB+sw7BQos0wShEf8CTMm1A4CEEQMENIkCxLWY/zHBRvw==
乾坤摩拉肉,TFJ/wIOPKsCqSKE/bjrdP0kAzb6OYYJAJIMKPYm7U8A7MxtAM95owO6DzcBi2izAgA0QwN66Bj4v0HhAPrjSwA/n1sAvSY0/XsvaQL0oxb7O7Ys++B1TQCm/iUBeZCS/YJ11QQeiAsCboTdAe+D9P0cGtEBeFNU/S2ZmwCuHrz+GZpFAVExlwA5IkD+eKrNApQUvwGoVXcAzIOW/UM3SPkYwpb+g4PnAzMJ2wJKClkAeODE/fOW+P6LTEsBId1k/zI8uwWZkoT9fTHVAG1MZQTAL0EBiqhFBIGRovjS0+r66J/7AMllmwdvvocC57IlABBoZQAYdwr7y7am+L2Kcvw==
魔女的炎之花,qPIjwNTZhMAA5Ly/2g1MPywenUAoZfU/kyBBwLvsLsBsk5BA7W5QwLZUEsEFRe0965e0wIY+asAYceE/u2XCwIpyY7+odqpAhtURQfExmL+QgUJAq7zLP7y2Ij+nawS9e60uQR3Me8CMMs9A83mTQEBEbUCdJaw+L2W6wPh7/r7AOdZASnyKwA6zhkAvtgZBvlKFwDQSBcAu3F1AvY9mQLpU879x5mjAGr//wDTUcz7w/TTAYyIFwW7gKkB6dohAdcEmwfp0GkDSx4s/A8zXP5hRAz/II15A1u+RwCbrIEA489q/KCJVwPTZ479j67tAN42fQN7Cirz2woC+y6eNwA==
蒜香面包棍,wNUNwFM+mL9V4gBAuUCSP+zBWkDySUBAlg8cPx94Zb+Yn50/50kFwG99psDU7dO/2Ms5wLFY974XfRdADubfwJ1gWsBhNaQ/AUO6QKfsrD+CCJq+zwLLP4NKJ0D2+RY+thV2QRcI4b89KUdAsECuP0PV2ECGGi1Ag2tgwJF8v7/1UVxA5MX7v/cBDD+Zyp1AeEgHwCIgycAg8z7AoCRYP62Wkz/VKWjAgPwJwW7QB0CE/T8/oS0HwchJZr8YLkI/aWs9wfAPVj+y0WRAAiuwQLtVUUAcND1BXhGWv08M5D93qtHARk6ewH5GMsC3S0JAYVnwP621AUAVl8c/6Pnnvw==
双果清露,tsvRv+xdlMD2hhc+gCz/Pzc/40BmHBtAju4EwDsjfcCkRWVAcNaPwIgT1sDYK7zAqwO5wIaeqb839yJBQsYLwe456L8WmkFAg3fnQBCTYMCOCFBA05XtQN7VaECGMn6/srWXQe5GocDlQ6pAE76HQFq60kDM94JAyjmbwN56k79mupxASeYIwBZW+z/mo9JAoKx2wDKQDMEoxhNAcRIPP6ZxIUEodfDAPPqXwQY+8UAaR70/2KyWwK5a6r+wee8/A+ZEwQraEL+VxqNA1+f8QOBwLUEcBRRB0oYlQAOzjUBw6RDBNqY0wchdwcD0krdA4rKOQCO3AkBs3U9ALozYvw==
磐陀裂生之花,84DgwA0QPsCcSfE9APuAwPaHUUDW2gFAMU+Vvzqz5L+ijUNA8p0MwNQYCcEYH/m/ywOkwL/p979m6oa9gL0mwYiYlr/eFYRAkbcKQS/SXz9AGqU/83PVP49pA0C9qpE/hyciQUgiUcC0waBAtNp0QIkcBEHMH0hAGBGawEqtQb/aS7dA69s3wEyMV0DMsPBAm/dbwEaLKcCRiANATiukP/4LEsBjwbnAgdEbwZf6B0AAUES/tU+jwNqmkT++5p69rQokwdxmNkAIE1ZAETqkQJDeLEAM7No/dTGTPzwtBkAGh0XAoarUwFtwkcB7FqhA0U5wQGMcl0DbTC5ASw5DwA==
炸肉排三明治,w4/Mv29cxb/7uoo/vdgnvg/uuUCEm/8/wkdFPx5+tb8QYFY/a3UQwPz2YsDDCJzApV3ov8DrgDuOkBRBVT2owCVxh79AN7u7SpiIQOMqBL/byhQ/9EWyQNj1hkAtHIG+C9GFQe6s3b9rsCFA5kosP5vZAUFf9nZA4Gjfv5KiNsEk9Pc/L2STv2bjDz8YE0VA/k4wv88QDcFQcJk/hTyTvyq2sEAjJRTBlJ0swIkfpEBIA/Q/ZRy+wOaY4780Bbi/rQ49wUiciUC19JFAK77QQA/OGkE7kShBUHMMwFLDVkCYZwbBM6sOwTGpZcCnsBpAHgB4P33BAEBtZKdAMkglvg==
教官的胸花,Jkxsv3IYaMATN4+/egkMQAM29T+qAPo/B1zZvyWibMAdzY9ACK+FwP5XB8E0GlDAkAebwMSnQ8CkJcxALJ8FwXtk4L+RVFBAXEEQQS/XY8CbeSxA7GRsQCZ5vz8fLOK/4q5kQRjhZMDkv7tAcOeAQN7/bEBIAT5AHw2twGJ/pcBOFL9AnjOOwDGwPkB3AgBB1KCAwND2csBhhj1AVDobQPLmcD8uOj/B3XBjwVHw40Ck6pm/eNHuv07LT77UcUpAZR0xwfjUgr8FvGJAQ4XgQFPDt0B72etAxuipQPnthkBWsb7AQIbswKb5SMDIcsNA6uyDQCumX7+Dq7VAJeJnwA==
守护束带,uOb7QDJNX8HukdfATEZHvn6MYcBwcD/B5yhAwWRmNsHmFz5BiutMwXvYj8FWkWpAPDdAwYzrTMGABxvAk1udwI0kwsA3BytBJaeEQSQaFsHG5FlBHBziwHoGh8C0aiRAMBufQXJrPsEkOHFBeHk3QYRGkcBxA6XASVpXwUxaXT86EWNBpsI0wZyPE0FoLohBkuwiwfV3bz8bCVpAjMMaQWSkH8GLvPbAhKI9QB710MC0YuHAU4X6wHfkGkH4Kw3ApvRkwcr3oj8gHFDABnXMv2JlQEEl0ypBvtpcwQYVlcBuEzPARoIuwCoRuD8Cj1hB6lNKQXvFtz80F7hAS2E/wQ==
沉重号角,auRPwOuUs78J/em+xC/XvqsLa0Ci2R5A0mm6PRerO7/mWPs/KbmDv6B2zMD/JMi+EBQJwGRw0b8+Z8E//n2VwNYYGz/HuTZAhFzYQDdpgD+QumG9BEFpvjRulz8gs4k6l6rRQHzfxr+FL3FA1UYfQKRynEDGB58/ELZ4wENlh8APt4JAX1g9wErKNkCeT7xArFgPwBJ14L/czUw/J3f8PyhS7L9fZYLA/BqcwNk0MkBOv8S/KuXKwPi+Mz9EnoA/OUvlwCsBJ0CuHsY/DmsAQGadAcBRSJdA1zSlv/QoH0C6sA7AJoT6v/wJ+r2cSmxAKkYYQAzLOEAwh+I/SpAzwA==
超级至尊披萨,qPQ4v/4iIcAJYEY/IAn7vtgeQkDqFjZAmKWNv7KWK8BNUw1APs8wwGiEqsBODwXAMDCFwBTVvb64ycFARsmcwI5q3D9UFdI/eie3QKY8o78Wcs8/Xup6QPF4xT+XdOO/DXluQTZjPcDIEFRALEIZQIxBxUC2Ng9AFg9xwJxSJ8CWEHlAmev4vyjyHz8g0Z5A0T4ywD/Qr8Aroq0+iM/6Pqz6YcCGsvXAYDanv5H2Cz1+3bI+sxTfwGgZib7HIClAEy1BwfpDID+OPENAQW+6QL52uUAoJUVBVFnCv7R9mECsPr3AFoqYwAVtF8CDlWxAyA4pQBsJ7r+YKiZAip2kvw==
烤肉排,7FqywGu9NsBIwX9AbvXevxmCnMB0VTBA9uJtv5uDB8D0Is0/pvcswP6QrcB1zXA/N/mtwNJ5KT9pe7bAdHbTwFmL4MCaCVk/kBepQG2TE0AgxoQ85gFmv1By4T8OeFm/1r6KQcl39b/YvKY/klfKP7jf0UBNoAY/uGhiwMoRMkHDaYRAsowOwIgycb/zLYxAmuFCwEgi3r428FXAX3o8v7RpgsECTdHA+t7gQK1DuMAS+ZE/XsUIQEyMnr3mLQxAt/BbwSgUYT4O2zJA5AsPQTSj8EByOi1BlOmtwOBZZ771l43A8Pk1wbPBT8Bk80dASMAXQBIxQ8BE6VrA+GQOPQ==
鸟蛋烧,1DaFwAnQJr88JQFAb+AEQMZhsUDOMs1A9FsHQGpFl7/S5ZU/noHXv2zmocBUi1HAgTnvv9nJvr4aATxBeBELwR3R/sBjJfk+YcXMQNZunLwEEq6/JmbpQL+YZUBWQkq/kOl/QTjyBb5D2RZAzyedP3Rx4UAm7Z0/yMLkvxHRKj4P5jpAYM0IwDgMFT+qynhA6lFOv2m32cA6htC/HtKFPxD+oT5OewjB4Dguwd0SwEABX+E+a2nGvvfMA8ALjcpAd5k1wd13O0CJcFBADpsfQbxw2kCYXrBAXtcUPzfZpj8eob/AlUE2wQhGzsC8xBBADi0FP2MYHsBUDx5AnMKBvw==
异国之盏,ltWSQDJZ08B91LXAMLAEQK6YsECvpf+/XvWXwD2Qz8Cpp+FADDbVwNI5JcGaKWTAVY5/wChXs8CIOjlBbNtYwAmNGj8QeadAGN8rQaz8CsFKb81AuFeIQOKT6D9OLnO/5PUxQU6ptsDRmBJB5ZW4QDDg7r+RGdg+vwTUwPCCKcFOv+lAjpHnwPqPykBCkR5BLpybwFcWQsBomOBAP8iBQDMYVkBSVmrBjAz2wLhJ/UBJ9VvAdtNXwIadsj8G2tc/7y8JwYZsT0ADd9s/orqGQAK36EAbe4BA4kgcP6fvIUDItYnAdlTSwOVsgr/NuvpAhmXBQJh68r00qfZAMYatwA==
教官的茶杯,gbqLv6+ct8AUogLAwCxjQLJ3X0Aw2R6++clwwGD5mcCnM6lAc8i2wDy/EcHFMIXA6Uu5wJJmgcDWNshAePoDwTUBz8AD1IBAmK8WQXgqncDHp5VAsad3QPXEIEDO5EA+fH2IQZb2q8DSheNAHy+cQKTSPkAUajpAYr68wF5nJsDB3NJASKyewDTHd0DEoAhBITabwN9HocC3d2ZAG+4nQKRcXEAVYzPBkCp/wTQq3UBUIGe/gllYwDi5Er2payhAUysywbyE/L+zfXxAF/XDQA/PGUFOc+9An8sBQDAW5z9j/ePAlRM4wXobnsBJjuJAh8SxQC8QR72LJ5ZAV1SAwA==
历经风雪的思念,jvCbQSsNksGyqlLByBvsPhrObb9+er7BQzSDwQMXicFQG4RBqYaWwcipsMHKqFY9rn47wUJRmsGW6d5AthkUwCqNUMA8Sk5B0XWsQblqp8E3VJ1BqoTzwC1JscDEHINAZw/HQTLbfcG7nrBB4RpWQeAUYsEm8AHBRml2wSRP5cBPE4dBLtKNwQLfckFvX6lBDNkywbcM/z/xNyhBBaNcQVviIL96Lo7BQGo4P3+KGj/h7zLBB19OwN3pPEFGdO3AOzGAwfxWsj8wcIPAKy2fP93FtEECHBhB/bK5wPMf0MA7abDAyMkmwXJwE0BxO4hBHwZ7QaYglECr5FhB0OiIwQ==
日落果,JfXavz7THMBrNaa/fgYOwE0TcUA6giBATH/5vkkhZsAHuGNAkVJIwPlI7MDLzEPAqaH6v9Xjqb9APAZBCVOSwI/OFUBb4yRAqh0JQXPKMcBmPIY/QCppQAjjxj+maiQ+e24iQTN9LsCbKYpAJM0+QOiqiEBPbdk/w42RwDdhQcAWZ6RAiMSIwPmGeUD/l9xAdPJKwMo/icDFMpu/yjUGQDyf7TyYJxHB7bQtwEu2/UB3mJy/AseFQDamdL98VXzAB6EewftkE7/pNl9AWF8MQabPaUChaJRAB9YuQQJqGUCaQt3APv4XwcPiWMD9H6lArq9OQJXIl0CFHRtA5vofwA==
守护之花,hDAfQaDTAcGiTgDBPg7Zv9VsJEGE2czA/HfUwOmv7sAaHAdBohgFwehQQ8HSFILAdZKGwMjyDcEy83ZBTSXWv3ibKj8rvstAJT1TQQJhOsFS9BJBax9AQOBcmr9ND50/8NJfQUHK0cBQgTVBYL3hQMJBsMCLkQ3AGBL4wBfXJcECUwJBr/sQwT5PDkHkwTpBFwKawJ+X4MAkCKlA/JnWQEQfhj8sDnjBqGFpP+Qq1UAdbqzASp+lwNkaYUBq/Na/xWMrwaBHhUC2quk+MbEwQIO4G0GkEwFB8nekv7Qfiz8GVJjAvmaVwEJtpD8KcwxBSoXsQJ5y8T+aiyJBwKABwQ==
流放者怀表,Zqy9QCMgBcGZyMjANOjTvljEBT8qLsTAqpfjwEDg9MBsAwNBUer6wFzSRcGirHg+dBy0wNJW9MBWWLRAIj4LwIbazLymtNlACX9FQSa4C8HYrQNBzXWwvx72jL/Cgi8/SnRBQUrr58AozSVBP0jQQLG5gsAHngPAA1gIwbhPq79EihJBSswOwZBa8kBQWz5BltLFwE60xD/Nf3VA5uPIQNIG78Bz1C/BS3kCQPkpdb8ZjqnAw/adwIfjjkBswTg/digZwRgFrT7vKKC+478bQHRiBkFlWgpBaYihwPeW1b/ZSmXAc7yUwEERkj7FGBBBG4D2QGcyTL9ktppA/UX8wA==
混沌装置,1+SxwAckZr881i8/ceyQwESKFUCyWIRACqEZPzxaF75UeK8/EOQBvleryMCpU98+pt0awEhWID+LUg8/nJ+ewIU6l0AmijhA2TfVQIXWLkB+FIe/ITgqv8TuxT9ZLA4920zHQB7plb+uICdAX2sIQEp42UD8WII/RxyBwDajmT9fRnxAedIbwAC9JEB+obRAaAgMwIaPMb9MiR3AMqSHP/TJq8C6qCG/0w5DQE5Mpj7k2T2/lMC3wCQKjj30Aue/JoMOwRI0OkCTtgRARX94QO3el8DsbfJAzNYVvxs1+T9pgmjAJtXwv7Q8hr/3ylJAcgUPQGdBtUBjbyTAhOcDwA==
祝圣油膏,oKVHPv7T0sCNtWjAQX8EQAfwzr721EHAx2+jwN6sucAJcMxALiO9wKarKMEuQG++ONnCwCIksMC3Qis/WQqMwOebXcDa4alASV8kQaZAmsAiKbFAOUwvv4CQIz8cjTc/YuMpQQa8y8B/YQRBhhiwQLYrET6q9Gk9W/vfwJpWBECWVvlA7hDRwMhkpUDscCBBReq2wBn4qD+allxAjCqUQCqA1MBI0AbBfBsjwVJ1nT6Krl/AUu+YwLZWQUA4az5ArkbrwAke0L9dz0M/ktVJQEJ940Aw1GlAD3ElwGCVHb3yZU7AumPlwKErE8CuCPZATFHQQHuyX79t0TlAh426wA==
炸萝卜丸子,q4dUwHC/ir8wNtQ+fMQ2wIJV2UBuxI5Arr9hPzjDYL8ed+I/9HCNv8ymqMAQvSbAw8wEwBsayT7e1RRBuACswIeJhEBlPAtALwzDQHbLHj867lO+WMZ+QPxVMUC4wDu/09QdQasou7/DIjlAaMsIQBnCAEHhwBhAlJdMwBeFDcE7sllAw8S8v7hLCkDGl59AjO7Nv6/RssBomN09NsGFPfiaM0AkyqrARhwVwMOE1UAL69k+ytYawEB7rL8wnR/AVnYdwRXOnUCIHVNAI3a6QH8YxT8nWKtAQAcxQEhehkAzybfAAU6FwHD4EMC/Z11AgHjzP+6Gn0CAyjFAerx3vw==
故人之心,BDpaQQZlWMHPMETBGP5aPpEtRUFUCn/BqpA1wUbgL8FN7zZBu9hIwUpNgcE2SaDAv9O+wDHCXMFSXnZB/d6Rv+7oAMFNghFB3qqAQS7ngsFQ82xBL421P3BHCL+EzatA+rqQQSqlOsEgnYVBa5scQXTTFcGSWHnAD8YmwXlx5MBonC1BupRFwUxZSUHg43hB9jz0wA32ysBxDvBABF8hQRSWUEDZG1fB6LM4wBWTfUCSB93AElbswNAav0AA3C3ApAkJwYKrKEByLJ2/kvzCv+u1lEHyLP9A5jAuwS8fl8D8NMDA7LA3wUdKhr5WnkRBIQI4QVwNWUCWET9BmkQ7wQ==
奇迹之杯,/+keQE8m2sD6FnvAS1HgPzZZ3z9O1GrAx5SRwMAHzsCkRMZAcMHTwAxyGMERlDLAAmWMwPIKmcAiLtdATnRiwKzWRr4cLoNAKqcXQVILzcCb+cFAmXWhP5jFEUAN+hI/44lIQRxA0sAo3ANBmL+oQKLVrT44wpQ/LGHTwPRZC8HUAt5AWCbOwO4On0CR7xZB/N2jwJgsx78Sv45APOxHQGrDQUA0rDTBaOTuwBixxUAUNArAWCtrwGZFpj96mxvA5hb+wGa4rD9qxRRAjoCLQGJlK0GALsFA6ZcLvz5fQj/0DOPA6g0HwdMsB8CZZPtAwyjJQKXOA0CFF85Aov6fwA==
苹果,BIuQvzaGEsB0kvS/+m5cwDrgTj9Shx9Azauavzf2KsBbgFpA7sHgv+MG7sA96hhAdE4FwPAyub++LyhAmmmyv3HibkCxi2xAkaABQdsbSb5L9HE/Tbusv8Oylb+tcvm+3eGnQAvF5r9D429AVzkxQEpWNECAzAHA+hOYwMotUz/NE7FAEiSOwEXggEBn49ZACyo8wDsTpj81cz3AQxlNQIpTN8GdpUbAC3YRQWbF0L/CBk7AFlniv16S+z9Uusc/LVzfwLMJCkCPXMK+Azl3QJJfScCT/a1AbxzPv5pDRz+v+fG/BAZBv/XfvD7evIhA8JtJQAQ7YT5LwUK/obxhwA==
学士的时钟,A0+EPzMdtMA4sYDABPgTQC4/70Ax+ay/gYRhwCcKlMAhcLZAuUKswDyVGcHiaoHAo4WRwJvipsBI9gxBDPa7wGZwh8DL7pZAClwhQeyvqsDOS5xARZ50QE2EC0DqZSE/phRNQVihosBPzgRBO7arQAOzHT8FTZc/bC+8wGZk7MDFZc1AM3fDwKK6skAynBBBRIOIwPbWsMA4lKNAvP12QMVdyEAqLhXBiCCJwerHAEH8MB7AU/i9wMx3kj/UbxBArZEAwU68M0CmoAdA77VyQCgY4UD60jdAEuhqv8aOmT+Q+YvAhKgKwRZcLMAAVtxAC8KzQC32yT/PYNJAtImewA==
鎏金殿堂,3NyfwIdJi7+v8rU/OLBzP6xlrUCheKFAlGX/PnFXGb8o+kc/jHSbv4ZmfcDwHlXAfL8UwI5RpD8ujJRAlfy/wLqVnjwbN4Y/caWNQCHc6D/ZMAG/sMmeQIMji0ASsD6/noBLQcdMz7+O8gJAquZjP9vEEkEbhmZAzbwYwLjxg8C3uSBA9Ih4v/thED83ymRAJ6CVv9Bn1MBEkDA/PBt+v+Kg1z/HylfAnyjhwCi5wD9a5d8/50MYwRpD3L+2GkhAWb8fwXgXY0Bp9IJAWXG8QKU7W0DmexJBsWWSwMLyU0D3U8DAnnPdwJWBeMDa5hRAH9CtP6z2Cr42eZ++UtFIvg==
提瓦特焦蛋,JqE9wHwNU8DO2RU+n8kjPiaVi0B+enJAt8Zdvv/SSMBZflNAkSWDwKMQBsEWtDbAHgwewEcd6b8FnyxB4HAEwWd3qcCzWThAQPgPQdP8Hr+quYo/kIiGQOv6fEALwrk+w/R3QUcGCcDf4ZRAXIdlQDSS00B1SUI/Dw2XwHJ7BsFL+L1Aqnx4wJwgQEDQSfhAINRCwHXIvMCI9pC/8ujQP+gZsUAEvADB1WJgwINvGkFQckS/ciGQPwxxm7/XxAjABtgvwcAnxEBSBTtAS8UBQcnuzECvcNRAalEPP9jiMr6l/vHAg6A4wYLBncCoobRAG4FUQMp1hkDl5n1AyiIlwA==
冒险家蛋堡,dStFwMCqQMA20CFA4Xfnv9aGFkC6ACBAdxDDv57nDMAs+Oo/vyhKwD2hpMAM1j3AnHejwBErrr5kQJc/xNnawNre6zzkbtE/HIOnQHPTH76cJ/M/vqpnQN/tQUDwPb2/9vWiQbqLV8Dfg0hAKs4OQFn2B0HhyYBAURN6wAyJx8AgeH5AJxeIv84Blb3q4qBAsOA2wJzS8MCyMNg/TRaEv5i5ZT+cjvfAcMktPXsmXz+FUdc/cRbCwPxZE7/yCVO/NOx4wWxDOUBEoJlAMKHiQLoiBEHAa01Bf/5FwLClmEDkUObAVswQwb+qOsCw8oJApMtAQFUGuD9cKsU/MGSuPA==
祝圣交响乐,4jJxQB0FvMCz2WvA1M/Kv9xRg0CO4ce/3LmZwMkgr8CLkLRAHSiowKnFGMG3Fv6+FciawHWVjMDaMs5Ajvjqv4ghAkB2iZdAU3QXQRMopMDq6q9AX+nUP2hMN7+GTmq/oVozQTzyqsDRz9xAzEGkQDp1vD9uwI+/B5vUwCz5ML/YAd5AcSGpwEd1jEBQfRRBqrOawBJNI8BEL+U/as+CQMZzH8EgCBLBZLuuQHit9r8MCVbAGf2qwPBCMEB2QhxAh68SwYJLpT9luMA96Oc2QMNutUCqhBlBc1upwAieU0DpGjrAqm4swEL9Mj6k1tpAIF+5QPVI7L+mg3BA3+KbwA==
祭火礼冠,zhVpPzqOucCNfQDAtFoJP4cmTcDKwuS/3XOPwC40qsDnz75AcF64wPr8JMFCLvs/OJzLwHiymMA4Hy7A4C+DwKZcOMDYVJdAxsIgQa44UcBFtJRARPRXwEwIh7+KknW+zjIpQdYBo8DRKd1AzZaoQLKcRb9a2Ha//A7pwIsXo0AER/FAKsjbwIRngkBNGxlBD761wFmp5z+SfBM/eHCUQHF5VcGQGvjApoxXPXI5VMCK7mfAjtWZwKY8VUD+rpY/H+AZwTiuCMDd3Qg/TpRdQGnXXkDsGBxBT+JgwKPpTL/MLhrAiBFawLhzSr3gbuBAIcvBQKsbA8Cw7pQ/H5a9wA==
摩拉急速来,6nYhv1apTcDeiw+/IFKZu6n/tUDLxj9Aa3fIv/1iUsBORVFArwhVwPrY2cDXaj/A+mtZwKJWab86sA9BWMmewGwdOUAS5jpAAufrQK8REMAYlwVAsJuiQGkzCkA5Cb2/ovxnQTDFWcDCdpBAyJZGQMG2n0DWswRAlX+ZwCcsbMBkDp9AX+NEwPjUIUBRktVA6/NMwBD9usDQr+E/cRitP5FSRkAKa8LAYp/RwEi6cUCORQC/r5iXwDQABb9gwTU/D2BEwVBYyz+KrFFAuxPJQBDuiUBW/CVBN56hP3mIh0CCw9/AowziwKoHMsCU159AInlrQDkRbT8wc8k/NksawA==
蒙德烤鱼,XIePwLT1Q7wu8jlAXxlAwOGgeUAclKdARN8HQGYEk74o7Es//BpNvw58s8B3LIu+Ra/2vzkB0r7mGOZAHNUCwe9Ik8AuBZ4/6fXgQIZUA0DmS9W/oGY/QGQu2j9CPyc/6piFQXYiED/JYPw/AikjP1DW/EAP7aW+a3j+v5hklD+TUVRAln3yv/YUGD+R6oxA0M7XvbgVs8AsFnHAfKthP5TN9cCxFfnAivUWQPbyuz4/06K+XJ+NvyT3qj2AZwdARbxWwXZqrkBvSBJAFOASQR/zr0AJ+9dAQXkLwNNH3T+Nl5TAgrr1wH9miMDC6+c/k7qZPtgDnb3lD/0//G2kvw==
「美梦」,OTEuvy/XhcDHHPk+VLIoQIIciUBcnwS/h1ucv2FUWMCuDitAwW+fwLppy8AbN67ApmxiwKqwB8BP7OxAhsUCwcgB5cBfrJI/bcXbQNXJKMCghDJAJDWAQBcNh0AyhLE/ILKpQZ4qgcDVMJxACks2QH+RyUDKWoFAYbyLwJoKrsDu/YNA/Xs5wFMpwT8YvshAOJQ9wJoJEsH5Gde+xGEtP3p5DkGi1xjBBRxGwZ518EAtpcM/gRuSwLqY7L/glzzADVlNwRBmpr63LqpAMfTmQHiVVEFdw1NBsLxUP04KYj+XjS/BSq5WwUjsqcD4A6tAQfVrQDPbXUAkfqxAArMAwA==
守护座钟,UzrlP/cM/8BE42fAJOdNvZHxf0BohdXAjoSkwMI7yMBjp9pACzX0wFGfMsFw0WTANGPkwFtc48B+selAqITmwK4ANsBYZMBAyRYvQVv0x8CzVelAj4yLP24x2z/hLQxAHUaAQRBx88BpbSJBieXXQLHDmz/1TU4/1zzmwL1jMMGo+fxA1RnNwKIMy0AfOC5Bv72zwBpySMDrELZAZqeOQFhzFkHIqxvBIhkXwbE+DEElbRrAEucLwGfbLUA5vLDA+zEbwYk1P0BwQ+s/LNYqQMHvYkHZVHtAIP+Dv9VPhL8sot3A7AUVwe8xa8CGBg5Bc7/oQF4y2UAoRwRBO//GwA==
翡玉什锦袋,GtKOwPBlIsB2kHVApRLDv9P/6z+Wqn1AhnJrv/GgBMBuqdA/O2xIwFA5vsAa4HzA3eq5wAAFFL9zmw0/kPcwwQA7O7/fcNI//tvPQMsGnz7ce5U/fhSTQPN6KkDTrvO/jVvIQSBmRMDnzVhA8mcIQHfDIUFshp9AAyKBwCweiMDJhotAnE+fv/ra2r5YzadABBIewKJoGMGqCxZAOLCRv/oDxj9LfB/BqPr5wNsgST8O098/v0bjwLysCb+AYa4/vzWdwQBW/z/5V8JAYMMgQfsOB0H8W3FBAClpvoD21kAkBgDB14g3wSa8c8DLNIBALLsqQNoAnz6V3xJAYZPbvg==
轻策家常菜,LLZnv8WOc8A6Ovc/lG+Bv2lW9r94yhzAw8i4vxWkasBSu0dAb0KZwKXhBcGPC7G+lFKfwM1WScDCdPC/2mL2wFEWnL/MyStAC5wAQQPWIL/L+w5AWjgAwGxToj+LV9E+eiOKQRxzlsDLJL9Af3BkQGNXsEBmHt8/RQ+4wHUkoMBsT71A4ZyPwOBd8j8O1gFBhKR8wLZAM8BPX8U+FaIRQDxi5b5M+/PA78CbwMwgAED2BVi/4qVXwPoW0T+Q65rAh0BWwdY2Yz/9JDJAWtq9QAKzEEGxVR1BZOdvPzqSbD/2Ju7AI73nwChpAcB+47lAwX+JQHGulkClQFxAR3d5wA==
雾虚草囊,mId3PtTUkcCojE3ATumSPdwqiEB4VLY+T89iwKmmd8Di0JlACgtmwPWMEMHZB5I+nwGJwC26aMBU22lAu+0wwINXFkD29qpACe8QQVFSLcBw4WVAzlvMPnBpFr76wiG/okP7QH1ijcBmYtBAE9eaQPN2oj+rASm/CZPNwGiSyT6INdJAzPKnwCVhnUCupgtBDJ+NwO4NJ78fQwNAJzaEQF2mpcC77E7AQwEjwObIjb+llFjAGhkJwb9mJkDc8V5AJDX3wHChnT/SDVs+wQOHPyB1I77bk/RAYse2wLgFyD+nWwXAEfnGv4ILWD7Jz8JAlUSlQHZmkL7el7w+VdOdwA==
宝石闪闪,Otp9v9kEO8BmOEy/3fYKPzoWtUB9LIVAibzFv1aIOMCUyoFAGVgzwCYA6MBfMg2/61SvwNqiF8BIfQ9ByNOMwP7VDb/BPHNAWX3/QKddAcCY8v4//7aUQJwcLT/MFAnATu4/QRxFLMAqIptAi0VxQC98h0Cw8dq73vOCwE9tKsAhnqVAIgVUwBCwHEASfMtA7kZPwEz5VsCk5xZAX/EnQLUQtsC7iv3AkEiBwOYc0j99vQPAavmAwOTuuD9bsKVAqUkmwVC6R0CN+XE/xlifQAVdkkBlnodA5GwCwM06gkAT9My/oA6AwG6KBMDS0opAcetOQNOLLMBnJkZADzMtwA==
提瓦特煎蛋,VnC6wPYuTsCYfUhAfvFqP04M4T54PHVAaF4Tv2PpHMDr1ck/sKdxwFM3q8CbBHfADheGwGtz7z5U2QNABGASwV9fIMFsVwo/zs62QKNqez9npjc/pCCJQD/Sl0DU3vK9euO8QRp/DcAywANA1jreP2ayFUEH50NAp9tNwPfyDD8WoH9AGB7RvySxAr8oXY1APA0awIgp+cDAqcu/c0zZv6pr3L9j5RHBKhBhv5sf9T87CTJAu3oMQDt5FcAOEho/a2V6wRp7CkCES6ZAdrYmQRryN0HYRVFBZiuKwP9tmD8QPBPB38CQwesFwsD0VnlAeEYZQE6JG79cv3c+Y+YwPw==
唯一的真相,9OEiv8zrLcAIIwE/7behP4zjrEBmUcU/ZH/DvppmL8C1AhZAkvVdwMxCtcBF3Y/Am0g2wPwwmL8N9hBB+3TBwIv9TcD+BmI/hwbIQBEeB8DYBOk/5QqeQLWfbEDSVDQ/SjCJQf16QsCA/YJAL2gEQOfFvUAWgF1AtwFowM39mMBtumRAl8MbwN7fpD+C5q5AeCUUwDXGCMGMxU+/6kM/P/BpoUB+UAbB2ucDwX3vnEBkSoU/LtyywKRLwL/8d4O/Dzk8wSZbVD/eDpRAXUreQB/uKUGLIkBBIh88v+J5BkDvBhPBn44SwTDQh8B554RADgcjQOus6D80HoZAzvrVvw==
炝炒肉片,wvF1wAiRMsAH+aI/ErFPv0o1+b9f3gVAFI/XvkK3K8DZEC1AIU1NwCYa7sDsG4o/5BBhwB4vuL/4PdA/hdrKwOWN0MBuSgBAIfD0QO0RUD8ubM4+yYZMv/TrrD+cJhM/3oo6QRNRA8ABP1NAO2MwQKzlm0Bbsnq/TTWLwM6W10D4e6RAYR2BwP4t3z+SMMxA2pBFwFTHuz6uXYbAFSEfQMgiM8EEfKjAJhFtQIC3ob/n2Yi/1WuVPvn+zz5w+mk/12AUwda8ED+6a5Y/uRvTQMeopkBt6gtB+CGHwF6SPcDzoZjAPP4BwUZYccChrIhAsR01QMskFb9BF2m/pNtDwA==
四喜圆满,xF5sPiItVsB4S1g9sitXwGBXZUDYyOE/rrAGwD0ENsDvindAQ3RJwHYmEcEh2G4/At6dwHjqW8B3cVtA6KXLwL/n4D94ooRAtAMaQbvHlr9llRpAPiyaPht8yr9uHlS/PrpqQRfiUMCYCrRAB0qCQHuXlUB36fC+aEHFwOjgKz+lFM9AcOeCwBrAUECyyQlBpuFowPkogcA+qp6/I8V0QKKq0cCjgKXAHsDev5I8vL9GXD7A6w7CwAeEMEAENApAC+9bweSFAEBOAIY/C9WkQHP/9z+grxxBSdSsv+DwUUB7e0bAFMOPv/CsubyAnbFAr6aJQOWKVT+HmQ9AnSyTwA==
学士的书签,vBAJQHtsxcA9+mHAsntNPwtO+D8mcSzAdt2OwGW/ucAjWMZAhurCwJQKG8HrxDDAm1+qwFdkp8DcDJxA4EKWwO//hcAUO3dAdYwfQZ274MAqUrtAS8uOP8gVUD+D6Iw+qwNRQYtatcDgb/9AY3SkQGJql79un5E/oBXHwNJkpL9ondBAdlvQwI87lUD8dhBBswaewP6+JsBxroFAFUdrQK9pncCfoGzBpMgqwQK/ckBxTRjA91tFwL7YpD+KuhlAAMQKwdtv3b/2OiJAvZO1QJbkE0E6+J1AOdh9PzhACkBESp/AgJn+wOJ15r/OL+VAZYu6QFsIZb9tP9hA8AOfwA==
天枢肉,5lFVwM5dVMCScINAALg6wDBqxsBkWoA/5nAcwPUlCsAAVyJAgWpYwLcjAMHsRVdA6zf3wHZt1L/sAjnBwpkEwU+nB8AxLT1Avk/qQCW1M0BArLM/BKyowDU73r8dgJm/vBaHQY4eX8AO6GtAI0NnQHmjxEAlxGo/soTLwNjxA0HWhcVA9JxNwASu6bziO+ZA+6ydwHZsAb9bc2bAvnnjP2nHgsENfmHAImhbQADOAsGmBVq/HVDewJ0QNEBQe1c/Ln6AwVRTH8Da1AZAIm2eQG2InD9Gf4BB/qZbwIOM1j8lUUHAoKvPvzyag71Er5lATeeLQON3bL8FIwzA1YBIwA==
蒙德土豆饼,VOmpwAzhPsChlxZA5jMswFE4RcCYEx5A4TqLvwEvHsCkIEhA1YEzwN/cAsGAYpU/f5W1wNLhlL9xS3TACQT5wNgBML9Vm09AsX4JQcXG2j83mUA/9TrWv8heGD82Dia/6dKCQRzVNMDl7nxAN7stQCoK1UDSLog/LeGuwGjNc0AQlcFAkaqHwA7qBkDrD+1AIJdjwKireb4EjUq/GamhP987EMHTQZTAjwB1v3rmp79YrXS/5gMLvxoKuj/8WdK/h3N8wcY2ET/FmjtAszUDQaXjD0DIfxRBlO+dPz5TTz8p85rA6YgGwUDR5r9yu59AfBZtQBuI9z8b8CrAcYgqwA==
特工祭刀,/yGFv9M1jMCPVre/DCKiP5mWML8UrGw/uZdTwPkMfsBeSZBAkrh3wDT/B8EBnTw/M2uowB96NsDyGbq+7eSUwOlL078I0ohAamwFQaYAAsD/clJAcfMIv0K6Ar16u1G/ApIeQYLEhMCD7qhAQFeBQCqIHkCCS3g+Fgy5wHQQyUDRlMlAmV+JwOqUMEDRnvxAa2OUwGrPmD/X1eU+NWZLQOZGLsGDVrHAZlpKwOtgX8C4eRTA+aHEwKjo9z96T6NA1xULwTh9B8BElZA/eCJAQBSlNUBppg9Bi4eLwEtjrD/7Zx/AEwtgwMDkpb/Vy7hAqaeSQOnPRsB4D4I9c8iLwA==
千灵慕斯,zIXCP1DE8cCAlUm/vkyLvwQ8CsB+m33AOcS3wDm5z8DsHKFA3D3jwMO4DMGAuhzAEc/UwJ1eWMCAPuw6gQiYwFqlFMDOBVNA4ucCQXRBo8AMX9BATEtmPgFrjT+wAmu/U2GqQVvl6sDWk8hA7m+iQFM/Z0BVxQNASK3dwOGNUD/iPtpA9CaPwB+6DEBaCQpBWA23wA4fgMArmDBAUoOQP4ycCMHcQkrBlr9SPw+EW8DwO/K8+IbHv9BWtj+/q+69ecpfwR5bu791ZlVAU9jxQPwxdUEasWxBTraQwHevEUBgBgXBCP5GweBWC8BmBupAJvHLQBo1v7+FJ3tAmvFDwA==
感别之冠,6s0rwMqKh8BSl2o/HUOJwL/XVMCIAlXAfXfQv6d2HsBCDC9AfFtwwFJTFsEydzVAfJSfwLpKhsAOuV7AMUUYwe/OuD+0G5BAmI0WQQKFoj9dyg1A+1+8wAW/i7/1QLY/2xZsQY8VkMA8q8tAaq+DQIeP20AWyX+9lBHMwGrtZMDQbNFAz0iRwJZxhUCbCQpBtSJqwDqq2T8IU7m/nRlcQKoee8AIBy3ASromv6ETD8BQIgbAHPzZwJL4gkBeZkHApOpPwUbwMUAJKvg+UvI6QN7HgUCUMwpBfPJuwFKfDMD6Oo7AsmzCv+8ksL46prZATg+OQKi9zkB/H2BAWcO0wA==
盛世太平,efaAwAfmfcAkln8/7wxrPlMftT9cPJ4/FXilv82wTcAH1ElAZ1SWwEFx/MCkh1bAfTahwGgE+r8iwZJAmQYiwWmHz8D9/z9A0KAIQc19i79UChRAfAI1QAEuaEAxq4c/4mawQaqEa8DDpKtA6zhLQCqZ00BdukRAYJKjwLw7GsBEp7ZACRJ8wPAaIkCJvOVATYFbwPAAzMAhf22+lzmLP13fukCJctHA5PgGwWJNr0CqFTg/XEOywHQiP7/w6/a/8al+wZ73QT/nh59Aj+HlQF8c+0CcGU1BIKKMvUww8r44ThzBBQdVwemXvcCtXLVAOUKAQDflgkCyL6k/iYwnwA==
摩拉肉,AmlTwAMw8b+OO4g/RBBdP8JVb0AnXaVAJC+zPkDGD8C4vfg/vYYewAbzssBQhQfAYfUewMRl1b20iQtBB8OtwMmlEMB7Dsc/bIPMQFgVxrvoI/i9hmGtQIIEWUBVbIO/4XpuQb0Xsr9AjCdAThvtP+pa5EDtABA/ITItwDzULsC7UX1AvVAZwHVqjz+wo5dA84nhv/IemMAJYr2+ONn/Pq5C8b/wMu/AKvxuP12CTUCYyRe9NCisPaRwbb+ssAJA98g7wXZvk0BVAiBA4Xf9QMuMvUCmu+tAI3L0v9bqB0CwFLnAQoclwfTPi8ClJ1BAwiLPP+NMYb/t34M/cIxevw==
雾虚花粉,Z4ejv3KxbsCIdwbAO67OP78eR0AczN+/bdTxv6l5IcDhW0lA88pBwAZj+MDP94u+Npc1wEuPZcD7B2Q/wnZxwJUD/L+ThmpAtlP2QCPPP78koAtApYSyvw67mz8FI+I/YloNQV0qccB6J7dAEmtYQAwQP0BsBq28UTaiwJCYB78LDaVA6R6PwE1ZgkCeYOpAuPpIwHR8874kfDA/w3diQA0Th78b8fe/NUmiwNw0Cz6bgQ/A05L+wMzjAUAhXks+ppTYwBp4pj8xUOw+81GRPQadGECQY/JAWcXQwOZLPr+dN1vA/RuPwMgtOb/cy6BAEBJ5QEWCNkBjsIU/cqqRwA==
串串三味,gLzLu3ixk8B8Rpg98rN6PxugAUDATqs/auQ9wJrIhsC6vmdA1hOXwMLf9cAKPzbAxrKgwEg6FMAk478/G3/DwF+IdsCW/jZAr13zQD4NJMCMm11AFGY2QEKswj9cggHAH6+YQUEZkMCD3aRA1KyFQMwBv0DXWSpAZJe2wADQTcAtbbRAoW9jwARE0T8U/+tAjueOwC6e4cBcQ20/rC2sPyUyhcB/nyHBg9i+wC4MkT8gmnE8F1Y5wMCBh7vqjHpAAFRTwXFBJz4KDlBAE2PdQI8/7kCAUj1BbIEowFaMhECbVb/AR6cVwaCXuL9XJL1AlU6RQIUsC8ARPX9A0LoGwA==
老兵的容颜,1oYKwPZ8Z8AKxFa/1X6VPy7lTL+gPa89uakZwI2SSMAg/khAqTJ8wCtKysDl65nAHqpywGV34b+P7DLApzTGwF9TAcHrWJw/PjrPQFpdP8Cc+zJAt19cP25PL0AGUFW/yqFUQZBMZcDB2YpAPiUvQDjHJ0AW3Y9AkdeCwAhIGb9V+n9Ailh9wEuC0j/wArhA0CNzwMYxqMCaJBdASQlvPqIBHcGgj2nBMQgFwT2JAEDC4Io/02eDwGQU4r+QgaJATrIAwe4ZV8BLZqNAHUDvQFgqx0C4jClBhv0yvxaRUkCUQbfAyqYywcI8/b9JvpJA5mBhQI8Cc8Dwno1AkUnJvw==
祭水礼冠,Pu8YQTYRPMH5LtzAVrbrPxxj1sBkrUzBSuQbwVU6MsG4+zVBbgJEwf4sfcGK6RRAqNsPwZ+vM8EIVrq/9gUgwIWYAsG6PO1ABlF4QWMOQsE64TNB/rPrwHzAEsCsOg5Ag2qCQRhMHsF41l5BDc4GQe6BOMG0xn7AJuYywRodnkDuPjlB4FpgwS5zEkHKyGtBQpANwehXl0DcbkhAKBkQQZRNTcGQq13BVV4qP0AoNcCKHN7ADnt4wJjx1EA02nnAmsY9wc1VcMA4t22/RNljQJ7PXkEy2jtB/JejwFjFDcGdE5DAnOP3wE6rTj+u2jZBeBYnQfEtib9YPadAOuM1wQ==
灵光源起之蕊,ND44wKLqYsD0Une/0P66v62H8j8ATTu9GowDwJlOG8CDS1NAb5xMwBBh/MDHQQLA4Gp6wBpGFsCDXkM/RwnYwChq8b8KtlRArsH2QC+qOr+TOiZAtyyMPSiC1D9CnC8/E2I8QTyyV8D4jKRAUYxOQFL+k0D1bBhAn1SlwHiyj8CSkLVA3zpWwMa/REBicvZAQ6FcwC5qT8AyJgJAjhW3P1cUEMDYzvDAEnSZwKDIvj8OtDi/n+MJwTDBWz+isAJAlzwKwRGzEkDKUjhAZpl/QCNvkkCnOwlB3mGewFWd8T8GebDAlJydwDR4DcASGq5AVGCAQJnIzT8dg01AWTJUwA==
地脉的枯叶,9aqUv0xaqMB10knAs31Dv/xHt0CwsS4/FXptwGvjecAZrqBAktOHwNikDsEwefu/4b2TwBWdW8DkGvRA3RKcwG5W3z8yJ7BAT5kRQXcKPMCg7Y5AwmU1QN0t3D+tELm+0idGQSNOn8DPzuBAz0WiQG3ilUA2pow/ch/IwAIOHMHs09tAK+6DwF9XoEBlGQ1BzxWGwGhMe8BCon1Az9wgQADen0APCL3AjP7awFlQr0DZkQHADQPxwFqxyj8o9ZY/Bu4MwQK/j0BuJ90/RLwvQLzUkkB0X5tAcBaGwAANNEDXY6PABxihwJrrB8B0BdxAXP2uQN+XZUAe42dAmRZuwA==
遗忘的容器,OP3Avutwg8BPUU/ADCvBPv6BqUAkOx1Ai384wFUtXsCwPZlA3FBQwMh8BcGoLPW/C6eRwN8cS8DcYMxA1T2VwMTwK7/5eIxA/0sLQUveZ8AVMGBAyMBMQAykKj+PKuq/baQTQWfBcMB/TctA1y2WQGjiDUBBins/EDOqwFSDl8CSn75Aj8iRwOiDikCYIPVAJAGEwDxAUcCGKH9AvVY4QDL0aMDEQhrBr24ewVTZc0C3vgvAEC/SwNRNgz93nvpASxLxwKVP5z+Gbsc/MpZaQENQgD+tuVZAVwzov3nlnUAJFwXAuyxJwJAgDb+8O7VA5/uQQD4gFcDTP5hAm7ZgwA==
丰年有余,rQPmv3W8XsD8Tac/zbrgPyzCXj/Gv6c/v9jQvxbaV8CvASdAW22HwOmq1sCk+f6/8XaVwEfQg78iNy1A/lPtwKJBib9XzBBA647UQNvITb+8jus/upceQGPlEEAiHX2/TTyGQQyzgMCDgoxABG1LQCCo1UCfbjZAcIWlwBd1db4MnKVA4QhDwEJZoj8IZdBAmo10wM7iksAZy62+X8inP2q7AEB7FJ3AC/oGwRAwzD/hPps+HafIwNfPv74A7+8+bSNRwaBk1r/BPmNANTi7QOoJ10B8VmBBQNcFPVMlxT+OZQDBZuf6wIb6bMCKvaFAydtuQMVvQT8HfjE/Q+skwA==
爆炒肉片,QO25wHe2cMCl0IZAWIhywLfKDMGWhwRAd8XGv5ZXBcAn/jhAPXVlwAfsAMETSIdAL37cwH6P2r82kRLBSasHwT2WDMG0nABAPQT4QGT5hEAoCZ4/2wa0wIyfTr8G9jg+lxCOQU3j2L9uIgxAtOBJQIWn6ECB2pe/+DWlwEz/jEDQNb1AnG1WwBY8hL6Wzc5AddaAwNq0VT+zkaHA6NZqP7oYo8FBddvAnKNlQVHS4MCW9zC/R7MuQBNEJUBMF++/RDNywXWoSED2M5g/ozbxQCero0DvFDVBJGr9wP2B8L85SEHAysbBwLi20b/0+5JAWJhrQClhEsAjKHE/VdEQwA==
学士的镜片,rnN7QI9lIsFcA5DAVLEJwEjwdcA0qQHBJCf4wCDaEMHo7RJBEI0jwQqyVsHYB3a8Y6LxwBsr68AgfnNAsBezwGIIo0ASMvJAZhlWQTSQ+sDomhhBmg5dwL6uJT1xmY4/2JyJQWRFHcHOwjdBbyz2QIcqjz/lLqE9OPsfwQjYT8GRvS5Buf8NwbYyBUF0qFZBlovpwAkypj8UtaJAeRmOQJ7sW0BjezXBhwqev6aDtEA3S2bAUHLtP1ffj0BdKzHBCQFuwbqVE0BEGPI/0fu/QN0HV0ERjoRARrmhQJLrHcCOkBjBd3XawFZE078tnjNBEewVQW98+EAL4NtAW37+wA==
猎人的胸花,SH6WwFYtecCZLv2+gImjPrVSYUBQatA/VM4LwH9OLMCsOGhAi9NlwMnBAcGio4LAfVSqwGbqFcApUQxAstkmwbAiQ8AjJnJAXa0GQSU7yr8VWCdA3Hg6QOCWKUCjw8M+PmZoQaaXhMB63rxAiDKEQEIVv0B66YJAiMWqwFIROT8BwLVAJy1wwFsjY0DMkOtA0u1xwGpqqsCZbEpAfe6XPwxvBEB8owLBwOmcwdxvZ0CIDgg9I4y6wI60Bb4c73hALQc9waDamr7XG59A3k/cQOMJtkAw59NAGuzTP+R5LEAiW73ACBkiwa11lcDr1bRAKHyNQFniST/WyDlAoVo7wA==
北地苹果焖肉,E+STwDatM8An8GhADlyFwGgESr9i0jVAM1qcv4TY4L9W1as/V/YswGEPu8BYU5q/I/23wMgQJ79UH9S/hn4AwRBfGMDnGOA/5tvQQOvP3j5mX7I/Fw4RP8xNuz/+7wO/LMG6QWVvJ8DyiSxAVnICQEjt6kDj2RlAZ7x0wI9FOr4s34ZAtxPrv0wQSD5rEptA1KUfwGE11MDBcBm+eCIYvx7e2sBuixTBYIK9QN67TsAIrW8/xxJjwCBgJT/gB5+/zMyawVVgC0BPRZNAszgLQRrC4kB3RWRBQRhCwLDQb0BwL9bAAbgjwYwKFcBlu11AcpskQKV8MT93l0U/qR6uvg==
侦察骑士烤肉!,rFJBwOf7pL/vnpQ+USb8v0QNej8OxqBAdO/pPlu3AsBjfxRAvsixvyrazcCQWIM/zEUPwKSj/72k1ZpA8Mt8wK4RmL9jkRNA7dfnQPfZ8j5/pCu/v9V6P5DDJj9fl4y/l0X/QIBeNL9QvCFAdlERQFBLikD3G5q/VS9lwIBttkDiC45A0vhgwFhNCkCW161ADysewGByaL2AJXDAnHEJQDSuQcFbJpbAJluWQPr/974uCLq/iDYBP17GCL6mbFlA/oUMwcX5vj+FLno/YvnSQDMPGL/bktdAJFojv/gcbj7LajXAtSedwP6m9b/TnlBAGkQAQDm1+L/ur4a/AJcNwA==
杏仁豆腐,WkUMwLq0K8CHBQJAckBaPziBlkA9oog/9wQ7vsHo/L+Rb5U/MGxhwE+XiMDkE7XAaWVOwMBMFb/eVNdA5r7vwCwur8A0z7i9ytSgQEYAdL/c+rs/7SKbQEA/jkBn1XA/DUmxQQbILsBlzz5AQPOjP/Rp+0AZsZZAoGUuwPTk6MAncSVAFNi0vxbihj0ETHtA1CXUv+4KLcH4YUo+F1eFv3T70EDjDxvB7p/BwJAunEDPwh1A9rqmwHqsCsBqS0nAnC9pwYAUqz+aKr9AbsX6QCIqTEHQBWpBVNKPv7gFL0DQOijBzr9VwfJknMCiHVpAG8IEQEQwLUBumYlAMpZPvg==
星蕈,QIppwJ+277/KECu/ShiYv126pUC3MjVATE7ePtxNMsDMuSpALNwrwKxh2cDP35vA9fTSvxS9M79KgwNBJ33rwBv7sb8FTdo/RHf9QE6xw7/GlNg+5kucQCU9RkAaaKo/S9FHQSSQDsA2gIBA504RQOH+xUAQr0BAbPpkwFrHLcDdVYtAvy5ewAhYPkC508BAp/YNwLOh1sBsDxm/+MN7P0iga0BYIgrBVKAnwUgu/kDoARk9+p0BP9hl5L/KRd+/jpAcwZiDCj8YM5NAt/IQQcUrtEB7eqRAu7P7QA6nH0AEtgDBzztGweAYsMCUjo9Anz0gQDKxkUBCJ3NAaNL5vw==
地脉的旧枝,7uiUwGvUPcCmiei/MqaLvWSEZ0Do3aU/ga/Rvw9Zdr8PmC1A2GOWvyfW68BsS4E/74VewHhOEcBtYQG/z1qXwIo2Tb/4BpdA2m/pQIqsD0BDiH4/OX6Hv2ABcT/D9uo+OxjTQHJENcBZU5RAsYRSQGHy1EAyt1o+W6eZwNxd2L8AuKpArMZJwPUihEAaqdtAoKs8wGqlHT8AMgA/25odQDX/k8DsOc4+lw8fwHTUYr5qmADAA2b/wJ+oBkBuGjRALdHNwBLGPEAUNKE8LNxvv0jPHsDtP4pAzY/1wIwqyj7j956/iMKcv9jdEb8trJFA8Y9hQHZjKkCWl7u+D75ZwA==
雾凇秋分,lNcUwL9a8L8E6Bq/Oax7wGzYx0ATXbNAE5+Fvwo03b/JajtAb6y+v8r63sBX4+q+G3GKwKbWvL8nOwlBlGyzwNUHGkA2JIFARFMDQYdVer+WRJg/3s+KQHiEE7/3BBLAP3lAQQlYu78PqXVAzUZMQLSQs0C+/nO+bpR/wIYasDyK7KFAA00uwASETUCyf7xAVMEawL6vh8Co7Qc/jH8ZQK3A88Dx1MzAOhYMv0SfIT5U9fK/DqqHwMn+nj/IoOJAd8JKwb8kaEBKjKM/iW/AQI8IWr8N1bBAttMKv0BAn0CnlOG/u6IWwCikvr+2dmhAgDYtQI3F/7/pQdk/MSwhwA==
鸡豆花,w1yGv5Y/hMBrlb4/xoi0PnxgkEBQ+PY/Yx8UwHZmVMAeWyNAxI+JwGpercAEVZLA5sKlwNzxlr+AoptAE0bPwFivH8DCPMA/dt+1QA7pHcC24ExAw8e1QJPoUkA+jra/hn62QfRdg8Bys4NAmM87QMyo30DQZHpAQviBwM4P9MAk/IBAJ6nKv+BKDz4EL6JAwHVJwKtIIsGrsENAA9RfvwKxNEDIIjDBhgCEwE70/D8DrOE/nmi1wGjYV7//Jo4/kmd8wdjYK0Cer55A9JHtQCiiOkEOoVFBBLdzwF9RuUArfPvA2w41wSR2Q8C4IpFAW95hQN7yJr+j4IJAPiRAvQ==
黄金时代的先声,4pvTPtN+s8DI4HPAyAUuPww/vUAQ2oK/OlRwwLPspMCIXKpAno+zwLXAD8FRRaHAJwp3wB5WbMBmER1BniGzwMCJaL4qYYNA7y8YQdtpvMByOaJAnmaQQPhCNEBxQQU/q1tlQcryssAimPJABvyZQB/lCkD4sCRAfQPMwH04psDmr8tAyKGywML8qkD69AtBdhyIwP4vwcDrnIlA8yghQA/rt0D69SbBfO5EwSR9zkAX952/pkS+wMQV/j3CDqQ9O6IjwQRw8j5523JAEPS6QKz2CkHMIAVBxeAwPydJBkBe6P/AojAnwWYQWsDIO+RAOx2wQGQGJkAAVY9AcHF6wA==
夏祭游鱼,IV6KwLymccCa7j++4oG9vRenQ78aCS5AMAUGwPl0NMCHtXRAJ20/wHHxCcHcpMA/mcW9wAIAzL/IqZq/z97kwLXOf8B6M5ZAi5ELQZEcyz9u7eo/DSS2vzRCGz9+4Qe/O1pKQbjMUMAD4ZNAlg9zQIBIx0AuLSe+f565wAIxjEDlAdxALeB+wETdNEBwgfxA3zGJwHAhNj4PxhHAa2ggQBt/3MDnG6u+1wqhwImCl79Xkr+/f7bGwFLtuD8AIYNAjt4cwSzccD+IZIY/QMV9QIwZx74IugRBuWi0wPjTUr7chmvALkSOwGKBJcCOx69ATEWMQKrzrD4lO6C/OLl1wA==
祝圣精华,WKRLvjCiz8CYMlLAebQVQC+y1D+NzDHAABOYwMg8usCe4bpAIKXFwIgfIMEtBTfAkJaqwM7soMDFTKBAB9zWwBXQS8Dr2ZVADK8hQf9BtMCIw69ATrQFQD8tCUC+mpk/q0FkQfOjy8CWSwRBlz6hQMEQ3T/IItI/DBXQwMzlOb9sb+pAIYO8wEAzn0D9NxhB9jqcwKuv2b/3hYFABupbQKqxBz/85irBkHxtwWkKWkBIsQ7AHzmYwET35z9OMBlAtZIYwRxzSb8jhipAkVqwQNDTNUHfDKJASIDAvb9DIj/bRL/AKGEwwe5TjcB7H/FA3AHCQFStfT5nqpFAJW2jwA==
岩港三鲜,okh4wBzaG8D0I4xAU0ucv3shuL94JYe+bD7Hvka5rb/gS1M/OhlFwI2nj8BM6wXAiHedwGB4Kr+720fAWdgDwcIRxMAls7y+V1GLQIUJDEBvuHg/xKAnvlqWMUCbn4U/bXmuQcmqCsDWXPQ/Ii8sP/3cDUGzvGxAAFMvwIEWksCJ9DZA6QtLv0iJ3L9rT3pAKl8AwMxB0MDUhpa/IILFv9FMh8AR6xXBmAAkQHMeor/h4g9A5B6MwDwGx73TVH7AOSp4wXm8AkCyEZRAv2vrQHCbQEEGbWJBXGqPwPwcHkBes/PAXwUhwfQBVMA5cDtANVn4Pz8xmz8uzmtAYk04vg==
满足沙拉,/GejPrr2g8AH9oU+5XHoP2cHGUBW1am/DbOZv+CpecDUnjNASzmWwBeUwMCDnpLAY3ZfwAKH1r9fhPRAu4rBwIWwOcDeHm4/1J/QQGIjR8C4GSFA+CpKQLo6WkCMjJk9Cp6NQQDEk8BftJZACbdBQIpss0C7/mZANWOLwKRLmcAMHXpAWXhhwIs+2j8SkL9A8udSwMdayMDnMdY+fI8IP9ZwbEDXJibBgWgiwaVLyUD8gp8/dlJDvpaCvb/602XAJx86wUDym79DP5NAJxr+QOmfU0GfrilBkB+CQEZxcj/UwR3BS806wWIke8DWjKNAMKpiQBf2J0Aj+7VAIsL2vw==
四方和平,EuFdwDjYhsCQxYE//SKmP/Qyor+CS7Y/PT/rv1tCYcA2LUlAWYmYwNHu8cBhKznAj3mfwGvixL/WzLQ/7nULwclvu8CDmCFAwnQBQQV+g7+rmhdAHwv2P5PoREDaX2G+mn6tQTZWZMAZY5FAubVBQDJO1EAfF0BAZyykwKFS6r+7I7ZA+Yp4wPVR9D9K391AAJlhwKLTqsAHl9Y+HmwAP5wKy76Lwg3BjO6kwCtxG0CqVS8/IKvQv6TkKL+f8RO+ixeAwcGoJD5r4pVAiV8EQVs7A0Hw6FdBhjs1v74JVz+CMw7BMzNZwf5ChsAWSrBAWgp5QHSZQj4DZfA/VAcBwA==
咚咚,NvkxwA3Ujr9Ylxk9FmBZwJogFUF7uLRAkhi0PdOZRb9TKzNAuMZGv/Fv7cD3xLu+Rb6fwHYKKcCTNgNBm9LjwPObsj9eQYZAQWQLQbuscL6sa2U+s0acQETYFL/LM8W/DCE0QbZdXr8KFpRAkKlUQOeWqkB/c52+MeRbwCgFG78Aq5ZAahAjwEZGPkBc78RAmxXwv3EOkMDyAwJAxzhJQJsBhsANmLTA5vQGwbfNqj9NpDjAFpnlwM2EGkAU9epAf9Q9wQSRvUB9wBE/i6GaQN605b7hCKY/yJAyv+RsrkD/F+A/7rQPwNx1279xZ05ACQoRQEwtF74xVEtAbW9DwA==
三彩团子,Z96twMzTQsCsjPY/G4PMv64vxj/RLXJAuTq1vxqK7L/cTtg/cZ0lwEJlusBqmynAfDWlwL6ALD5nahrAXAjzwBg7o8CNARBAhCbCQLnraj9gQ60/q+/jP2IHLUAXWpC/kCGfQYC/NMCdKzdAQNEiQIYR+UD1wUxAbaaHwELZpD8IDY9A3pHsvzyMOj/WHqdAb/lNwPpE08A9nZo9bEQ1v6yzRsDhYqnAuVYxwLo9l77e9NI/sQakwPDAjL/YAz9AvxJqwSwPqD/v7ZBAgT3SQAMfNkDQvU1BtUObwHzSUED28sPAM2g9wZRCJsC8mohAsl5SQGXINT+YoKu/SJJKvQ==
混沌回路,BgRQwG95JcDCxi2+wQk6QN+AtEDGvWlAbMP8vm90DsDVyUZARA1EwHpi4cA6HnPAq55lwOwdrb/mLbxAyYUFwcWSpr9BGVBAGsv2QH8Vg7+UwIc/gLySQOfyXEAG7Vm/WolfQc1KNMB3Z6BANqtEQHqlmkC3CFJAKfiQwBz2scBLsJtAQxBRwJLoM0AEEthAXkA1wOk1u8BJm0JANK6rP2sXQUFy+orAX/OcwbBWCkG7CJ6+iIfzwMhOhL98Ybg/Fe8nwRzx+j/7vndA1fujQPuRAkAD8NVAzPADQJa/PEAJyM3AVH77wHi5acC5cp5AshhOQBuuZECpKNc/E1ItwA==
连心面,duCjPnh4TMCorak/Ggkiv5timT9UU/w/zk5fv5tlZMCE5TxAjLaKwKPT/8C+Qpm/HzCBwGmwE8Ao16lA4e78wMrhCT9rlCRAujsFQQ2wx79w8QdAe9fFPyZmhT+W4XS/MO+UQRiRRcCRopxAWVxzQMJgvUAI2fI/ity2wMNe+sDhq7ZANzZdwO7iwz8UGfhAX2tawHU438DEwBG/5075PwcJMkDEOwnByzyawIL+VUCecFe/aKGdwOwMEj+gzPK//5FvwUbbLkDq9E1A55/bQL/fs0CKAVFB2bvQP/rAZEChqevAK46jwGiErr8eS7JAGfRuQF/b/z/NFrtAalFSwA==
黄金之夜的喧嚣,V7MFQMhXs8CfTUzAU5OBvxmZmUCYedO/IBOKwG4EocCAJ7VAQNitwNICHMEIZEHAUDm6wOLYm8DibPBAWSWxwBpa473i4ZZAuaMlQfum1sAg96ZAZVw5QPijZD6eqmg+6EdhQf6EpsBFDgFBl56rQLhoCD5yOos/XyPRwBLnqz0CDNJA6Oi/wIwPrUCElBBBkK+fwMrHi8DcADtAVmGCQICoyL9oXDHBjn7dwM9WVkBzaiTAfdUvwOCC1j80G00//DpQwXm+bL84FjVAiSm6QEAS50A8iv9AUZBwQLFGAEBAH5bANU3awAYT7r+vYN5AOXa2QHCYlD+a6I5A+I2dwA==
黑晶号角,Ivn5vo16cMDVrUbA7rzdPeoQ4kBjoSFAu9QrwOsNZsAXq5FALUxVwEK+A8HCWwvAtf5LwOKwGsAIV/hA6h5KwEQpi0CFNZNA1c4JQTYjPsDvvUNAWBFyQOZhxj+S0lq/3yX/QPZGaMD0srhA11aEQOIjDkAHBnU//FezwEWrlcCMG8BAm4iQwMr2lkAIDwBBohppwPDBVMBMunFA7Xw6QJRQGUCJZp3A8fuTwAaXZkDQ9yXA4BL6wC4tcz9YQaA/xooIwSniK0CDWug/5dAhQK+D+D1LOthAKMXTv0BVXkDsM3TAR2p7wMRofL/Uz7lARJePQKMdkT+YXlY/m6B4wA==
督察长祭刀,1/6Ev2ItgsDJmCPAJH8FvX/7OT/EkRpAhWdVwNeKc8CJj55AMxlRwMWtC8EXVLU/b8mWwCejNsCqYuE/32o4wAyYCEC265hAuXYMQU24BMBuvU5AXDwWPmRr177FpM+/mOnHQIFSaMC+da1AxS2LQGE08D+zse6+ZVi/wMqXwT8EUtFApqGdwKLddUBKFAFBSj6MwIC4uT+ACfU/0HleQLbQP8HN7sLAhTZXPwqeQ8COwVXAQrbuwGr4IkANqLZAKvzuwNYrGz/9urU+khIVQN9uy75Df9lAVGOywMg+J0ARXmS/uFD+vsgZZj3G9bRA+U6XQHb5e8DKQJg+03qKwA==
来来菜,YpgJwG5AQcD+bOM/yNvhPm6y1kBe9m5Anbmjv/b7+7+8nw5AsyAnwJANrMD0yGfAtUO5wJZKqr+ADMdAh/bwwGgNSMDf7NM/oPO+QC25k79CDRRA9rq4QPXbCUDSo2G/AU+lQcRqPMBu8mxAB9wwQIO76kDOiE9AsHBZwPJrq8Ci/WpAyGJ4v2XXuL2lLJtAckwlwEkMI8GOL+4/omz7PaEq4T9FTBTB8qD4wPm0DkDVdoM/tWkUwS77Xb5ebGhAnSVowaq4D0AX04dAVCrDQCZ3B0HLuSVBWhJUwGCb4kBXT8LA8ufDwIyJZsB5+HBA8PxAQFULvL8PeqVArKIRvw==
「堆高高」,0Oq1wM9GTb8QQvw+N47RwNhdBEFzd5pABEdAPZ3OU7+SifM/ZkTbvhJRycDCAl/AEqFwwCzIij2AQp1A0kEAweuEz0AgXk9AwqnjQMXjDj70Vny+0kKbQFARrD82Lwu/vnAUQcB/8r8e129AUFcxQAaUBEE4M2JAuA9vwMyvgT6C03xA5rzDv3JbOECp9LJAnwEJwNmRycBN5ck/RcsTP0r7mr+6F6rA9lUowcCxNkCMUKy9YK+dwJ5eWL4Ce+c/Fiw6wdFhNED6xYVAf3TtQJoR2L6u+zVAHp/jQIje1EABQj7A/l2pwPQyT8DIDmVAcjYhQJT+nUD/Cd8/h6KYvw==
大英雄的经验,/bWQPzAhcsDBppDAEkwrwDRCoUBwSOI+aOl7wHYXbsCdIqtAIfArwKIHGsGfofc/ly2XwPYakMCItRlAzDGCv9n7uEAWmcxAZjAjQRofW8BZolRACb2Rv/q+KMAfRZy/1X+ZQHmKcsCFCeRAmLidQGNejb9ivRnAibrJwBgYxT8Gt99AbUjFwOC6zEAB0QtBzb+QwOKI1D/pnClA0vS3QMSkFcEX8CHA21orP6mtHcA/hLHACcELwW/VhUCg2ZFATgv3wBnCuj/S+6K/ar4LP1YIr8Aw/69ABdQ5wDYLJ0DTab0/wx6eP/twK0DAM7RAghqhQGhN5j1U5ig+u0DDwA==
祭冰礼冠,lEBtQNCF+sCqOoTAzATTP9pUNMCsTKHAuvrMwIAk5MCpLvFAeTH+wDwrQMHZO8s/hSHXwODd38CYWJm8eKdwwNhahsCi/MNAZqM9Qd0Uz8DARuBA2IddwC5ng79a6Cs/vHlHQS272sCR0BZB94fPQFL0Z8CLePe/wmUIwVuflUDEsQ5B1RsPwXh+yUC0YTNBYkzOwJzJ9j80/ew/EqDEQP8iMMFNshTBm/t+v1VDQcCzCZfAnneXwG6oiUAIti0/gfwjwXB8G8BKLhs97C5IQHF95kA91CFBJsOdwCScYMC8pWHA8N+twD72bL4rEAdBafHtQJWSxr8zUOQ/YBr4wA==
黄金剧团的奖赏,yFstQBTBmMBp0f6/Gpf3P3xIBb/vsF2/gjJAwDSKrcDYyqJA31aswN5YCMFLNAy/AhZ7wJKQbsCIfp1AgNpEwLfsJD9DWi9AvjQNQZHeqcCEGINAx6wOP47sTj9UpTW/2tJLQahHjsCQO8tAT39rQBhIqz3vagc/hJ21wBywqcC7Y8RADiu+wOOMUUDLzgFB+V6EwPKRiL98hkFAO1NFQFg+ncCYk0rBpMpiwO02tz+hISTAxVOQwGJM3D8UcyQ/oWkrwaal/T51kfI/gg++QC3c9EA0JAlBJjgJP/7+/j8+tb3AmCCbwF47Ur+sI8NANLCTQJTqLcAWnKpAiAqYwA==
「缥雨一滴」,ncPxv8DtcMDoAZ4/HzZGQEKqQ0C6lwJAHVK+v66VVMD4Uz5A/o6VwOhz58ChLpjABZKewCrfxL9axJhAKqkowYNAqMDnsRlAwS71QPUR9b8nTwZA1P6ZQLy2U0DJJYS/TISmQYVkg8CqQ6JAxYZUQGcx30BVJJRAtuyowJCblb84hqtAp0VMwAxq0T+sxeJAOEp0wFstA8GD5ta9JwWOPzC480CMc+/AAQOOwdPh1UDkk44/YPSMwC6877+s8NE/l9BtwY2rH8BAerRABHwGQWy1BEFusFlBypmBQKbDEUCe7RzB2e0ywU7Pq8CcHbNAdqZ8QMvbrz/4BSJAs4IiwA==
昔时应许之梦,hLCYQP59LsH26+nAptQTP5sCqT9VtEXBmQEQwZQAGMFxBBVBYz8cwV+0acEPjBa/DdbiwK85GsEK+j5AmVNowM0UkL+D+QNB7JdXQYROE8GWTCZBiq0nwBC1Rz+lrJZAB0NdQZgML8FQQ1hBJwvsQGJNm7+cvP2//IIaweWRQcAn6C9BA2YQwaX0DUHZHmRBp3jzwK1ohkCe/KtAKcfwQMbLmD9uh8DAhTccwOo9wL9ZtqTAp5AIwczkw0AbGYLAedgFwV+eAUCZOnS/K8mvvrWKg0H3W8FA6OAhwXGlksDesrfAvEITwdwvSsDBwDRBfi8cQb+yu0BUSrVA4jogwQ==
学士的墨杯,uG9xQAA3ysCjGp7AAaFeQBMQv0BYlVe/tqWxwJ8uvcDJmM1A1vG1wOYaIMHaSf+/hL+bwN09rcDRrwJB5LQ0wCOQyr+iU6hAUggjQS712sBK7MJAuKxxQAA/ljlmJCi/9gdIQZiOtMCbmAFBMT+xQFNJ976/nba+XefYwONarT9v7+1AbeHKwCgFq0DIvhlBrN6owGaDQcAN+m1AKCOiQDMwhsBsjg/BHX3lwPVnPj+EnXXAajHUwPAxFEBQvtpA6XIewaAAzL9xZUw/OnshQNpfqEDVCgZB1ytWwNJzOEAHoVfAqpq0wOTTmb9+QepA13bGQFQeoMCj4VxAp6a3wA==
海祗之冠,+JStQJ1G9sCW4WnAeLMQQFCBy8A+kKHAcInFwCAR9MC/+vlA7YUAwXJWPsE/vHNA7eXNwMhk5MBlIc6/FLz/vxBMpj4MHa5AVHA3QUTfv8C61uNAWZ6xwLEp3b9OEf+8OnJDQR6p0MDYzQ9BrTbEQCBXNsB3FDTAw8UFwbmFbMDEwxFBpQQRwQYrq0CkvTJBkMjFwFG/i0AmNC1Aq8m+QEdcPMFAzTLBn/CwQOhWc8BV5LDAJap9wOzyskBMwcu/kkEtwSl/Rj9e+oO/FhM6QGvd1kDXeBNBx5iiwOXWvL97yVLACFbVv7RHxT8GXwZB/6noQBnMWMBDOpdAzqoDwQ==
奇迹耳坠,QvMjPzvZncAlsiXAKoKPQOMsiEDQLV0/Cw9cwDOMecAK6ZlA0nuTwHHVAsEqz2LAaXCxwECQSsBSIphAiYfdwIf+6sDa/n1ApYAKQdC1j8DMZ3xAFD9gQMqcqj+H4YS/RtZuQVg8hMD9Ps9AamWMQJNP5j24XeE/B2uxwKrGwkCprbZA+3qkwKjtbkDOePRADhyKwAH+lMDQvNc/P9VHQCwzn7+SLejAc5uDwdZuJkD8zm6/IXrWwC/1lb6gsRZBH1QmwciQYcDQx0ZAm3aOQLUQhEBCFzVBYNEYwGP4kz8AX5rA0aj0wMr4LcANoLRAxceYQMZGoMCaSvQ/tLuBwA==
教官的怀表,3gpYQGZ90MBFljLAaAlnQPT1Oz998z3A4o+QwNDZzcBqEr1AlCzmwLB9GcHeNX/AakegwDAPncByH8ZAPcC/wFA7hsB9TFJA7x8dQejZAMGmh7tAMMAzQHQl7j8y0s48KC+QQddnu8DuUgFBB52XQGkagr+t2A1A5cLUwJbwrb92HdpAvxXWwK5yhkDu9xFBrhyjwCrvd8DMmG1AUHhNQH+Rmj+lc3bBiNc1wdkqk0At6tq//T4SwOB3BD/Mh48/EyFRwQjnMcDroXpAQTDqQKzhSUGIXzlBTo4rQESKhT/atgPBP/w3wQQgPcA0/PFAKT23QLnpz78ilqBAOeacwA==
守护之血,sgpMvwhE4cA2eoTA+HvFvuTvm0CTDV3AoCK8wGSlgsCLXcVABbiowBghJ8G0d7O/WDD/wGPDw8BojSJAgdfswG2W2MCCw+lAKyglQfI+ccCQochAZPJYvqCqbb5KWHQ/PhFPQYR1zMAV7AxBVx/bQFRcGEBSpXc+JvbcwKcxxL+szfpAEUCnwCd4y0AMiBtBTh2/wKASLMCuCWdAlL+KQC86WMApZ7nAkoUHwfBcFEDroR/Aq+mXwOlEOkCsEGJAQ5EFwcDUgD419mk/Sg5OPjm/nEDHlmtAdxiswC6mtr4EagnA5qihwIT1A8DHhfNAY7reQKPL2j9DOZpAiVmzwA==
逐光之石,4E2sQAQtBcH8j4DAd8pYP6ckF8A4vNTALJnPwCG6+8Cyo+9ARPsEwU4kMMEnrpO/OOjNwIodwsA0/EVAihdzwDJ4KMDa7JVAcsEuQcYSB8Hcv/hAZ16VvzAxtT3QyCU/OJxzQcQp9sC2QhNBkAq8QCK4B8A6Ywg+WEX7wJT0ND/lxABB9nD/wLIuqkBkhylBgiXSwGlN0z5p8OA/T0WQQJLWCsHKqVLBLkKXv9hmDj8kNTXAJDF+vvu5H0CX+/G/+R82wYolWMCooABAKsm4QNUUUkGUfiVByCKNPzsJ67/Gq9PAHuXywGv9zL+oWgpBbFTtQNmWsb/qU8JA4YzPwA==
果果软糖,sSaawCTDgMA08aQ/7WuLv9JFAUDg9IRANwI+wInrLMB9lGNAKWNGwBTj8sBmurK/i5HkwLawjL+1ujvAqhMFweXY1L8kPXtAWPjsQKvruD6CXSJABgAzQNBJyD9Z/vC/7sSRQaXfgsBiPJJA34J3QFRS/0BzlE9AJhK5wDDtIkAS9r9AwKgXwBmJzD+mNOFAxPyOwFYjosCyGZM/QjIuP6BuO8B/rWrATYIIweghY7/lQrM+q9XZwJhSPD/N5JJABftxwVFhND89gXhAJIDAQBu8OkChBBpB0Ad4wPsxkkBMjInAKrLkwMHiNsBwmLFAeiyZQFPpDL+Avvq/8KXGvw==
雷鸟的怜悯,QSPRQCi1FMHpSq/AIo9DQKXS9j4rkAbBxM7iwBq0AMFtqvhAoDMVwUyXSsHjF3O/q1e8wJ2mC8HruSxAZoRPwKnZ+sBQCq1A8AxBQTOUB8FH+RBBCV6+vxgXij6STD1Ar9aBQaziAMELOi5By2fOQKp/asCVnQTAuDcFwY6oPz+7/gpBRb8QwY4syUApxz1BlWLAwLJlnL+byjpAmmvWQJ3kwsA8zjHBoBNDvnuTZMDX+4/Azc0Jwa5blkBIDWc/CwsZwak/SD7mOla+O8mLP5Z5WkGQiklBXnxKwcDOTcC3DazA6ngDwaIHgb+p9hBBOsb8QOeuEsBf+61A2GIIwQ==
唤雷的头冠,GdtJv2/7XcC8oLy/s/JPQJo8ej+00zZAMHsEwKkOW8DpiXlApoJWwL5V98Bsd+O8IiJkwKoCF8Ar7oFA+XOPwESZsD7E91xAiNYCQdV47L8yIRFAUBMEQGpaxT/TaZq/pZswQZFGRsCGS6ZAildCQLIqUkAdNjw/VzmlwLTcz7+8C7dAvG2TwAD/QkAcuOdABthZwPrMWL/0c2w/ffo8QEnXisDKjMfA7kdgwDpxvb7RbRLAsm8gwRxkrz+eV6pAsV0mwVTtNz9YEcE/dsZtQIFIBEADiiRBksOPwNDa1z/e74DAzrIhwKwx2L/u3Z9AaxxnQCuceMCegoI/KEV6wA==
测绘员蛋堡,4Khmvi/CXsDwApq/Jj1zv6I1jkD3SytAm2LvvyDdVsB5koNABExowGQ//sCJWAbAq/mJwAsjGsCGsw5B3IvMwJterz985INArGMIQc3GDsC5NCpAmBh/QBpHpD8EcxXAIqFbQctDWcAGQLRAXGeDQDiWvEAyzOk/O5uqwMwlFMGQt8FAWvpmwF3EVEBH4ftAOLdrwIgPosBgyug/9zAGQE6mAED0+wTBOkv2wEe/4UA64qK/qOVSwLL9GD6KjhdABaswwcfeQkA5kCJAtrLEQBsMfkDNt8BAXFYXQL5vfkDh2KHAwKiPwPJSCsAGFbhA6LeBQC0thD+WP6VAmck8wA==
摇·滚·鸡!,oKdJwB2mA8AAahw/f+F0PmDZeUCm9FNAV2xJvmhl97+NphtAsvoZwCKNw8DIoifAm+RQwJTJTr/UScNAH8PbwL7sWL/x1wJAfR3ZQFNZmD5vEhE/Ru5EQPmmC0DGQku/uMRcQeKIJcB8hm1Ad/EdQLzKBkGf3khAkt+EwMGFr8BCD4xAlWQSwEGE5j+MiL1AFeIywMZtysDZAOS+zio8PxQh8T51j9HA2cUVwebYrUAxUqM+/9URwDTxW79QaMO+doc3wSacHz/D92VA1W/ZQG36kkAlMuxA4iRvQBrxYkD0ddPASwLWwPSBP8C3S4xArrQwQBcbDkAov11A/FzYvw==
冒险家的经验,cqdmwLZECcAQ21W/wPAbwBL83L7gIZNAMfbrv5BX2b8B5FVAahGBvyIF9sDUbUpAJ6WbwLz+mL8aPSrAukt+wL73nkD+qqJAfE/+QMvvnz8X9iw/KG8CwCb4wL9GEDXA5QOJQB87D8C9aX1AB3FwQM7kf0A/rIi+TZStwDpr9z9E0MJA3IVdwG3nVUCTiuNAGgaFwBaRKEDvN4w+F8ssQPb2RcF2IuS/6eOYPyjKO8AGvD3AnabkwAZRHEAuorRAV8UAwU9YTD+Kxzo9zcvtP200/cCHwrFAla3+vwb1VUBwsXI/6NZUQFfNvz9e1ZBAKa1mQLORZ7+J2Nm/uS9cwA==
「蒙德往事」,WD+GwB8av79bS+8+3dDLwLc6nECBAHBADiEyv34ewb9d9ChAvKKsv1mO4sDevsC/E72HwBlF3L6A5x1AP4HTwK5G2UA1hGBAGoH0QDVuGz8Y9/09oDsKQOiqCz93y4+/LoUAQaUZFMCiuXlAhhhTQLFq+UCJQR1Aj7CVwEr7gb9r/plA64YawK4hOEAOg9FADStHwKIncsDDueC9zQiJP9rNpMAVDKXA1GJewJadLUD6w1y/1d8xwFE8sT5kv7C/91UwwXznCkD8a0BAKWzVQEXI078jaI9AZrTwQF7npUCKsxjA2YVRwNSA079OWotAjjRHQGiAtUBPDrM/WkL+vw==
烤肉卷,9GdDv0/gE8CbdbQ/c8olwFLegkBAwOk9kGVpPNrz7L8ojug/8MFPwBwPwcDOA2XAzHcWwO5c6r+ZF5JARD7KwMQC1r4xG4M/t0PVQDVzgr+2zbA/M8nxPzmBGEAJk4A/OCSKQehmGMBlBH9AyTj7P88uukCiyU9ALp9rwF+9HcGs6VtA9+4swJJD4j+IQ7VA7DcDwCC3BMEg+h4/kHfLPhTuAEDDWifBObiGv92kjEBUvwg/0gTCvy8d6b76/8jA67tmwSy/V0ARXJNAUx7mQEuyAUHpBitBEp80QMpiQkCeEPrAXb8IwQi9z79Af4BAeG4jQKG+m0BVUbFAV8zhvw==
气泡酸莓汁,nrg6wACvKcBC5Ya/cOi3wI3nOUCSehpANqEAv5RKI8BbkFFA5cz4vwirAMEB1Yw+5Q5XwA9/2r/9aINALkeDwCSi6r6dcldARY0KQSe4gr9MzoI/ad8OvwjyMT8ytIA8PRLiQN6AIMDDiJJA49trQI27a0CWqI2/fmaPwFF+jEDMKaFAJZyKwB1lc0AWMtpA6DxAwPRjmL7c7Je/afohQPEKL8FVO7jATYfbPWy4HT5MzOe/SmVDwJwKhz+g4Jc/EdnqwPOyhUCIdog/tgW+QMXdPj8h3ghAbwkOwGQrsz7efPG/AkufwOyCrb8dWItAJO9TQE26DUCHlu4/IZA4wA==
华饰之兜,o9Thv3ZAncDjHQLAT1XZvx26oL7gb7M+VPt3wDj2gcBPR6VAgjCDwGOaHcERzvE+svLHwJ09dMCI3ZM/wRPpwG8uBj4W27JAOEseQVHULMCxDoFAAlDPvkBfgr6NsoO/Sd4vQWHlmsBhbNpAPZKgQGxDUECNa1k/du/PwJDpOT/sd/FAJQCowHcHlkCDKhFBvs+gwFgVNz8pJz1ApdpYQB7dCcHBqATBX2/SwA2TAL9uRTHAauu8wIkwJEAWHaZAG2giwR9zTb7XptE/mzGKQM/FEUB9C5tAVFudv/LJAkDSOy/A1CxPwNzDzL++39RA6musQKtlT7/9oz9A3maZwA==
祭雷礼冠,mga+QIWgFcF/fJnAgKRDQAMd18DSVvzAKxvuwMvpE8E2pxJB4P0fwV1wWsEQhwdAg4npwAucCcGO1Ny/G/kxwL3Bv8D9mLdAUzRTQS/YC8FD8AdBcDapwGSPhb+wkYw/w+hfQbtwAcGYxjBBfVDeQPVC28Bx3CXATykZwTCqoECVVB1BQ4M1wWRw1UC+RkxBNLLtwOgbhUDkLBRAGhPpQB3DT8FduEvBrFk7vyB+PsCC0bbA2T1owMCipkAOvcW/vbErwZalbsD3yIO+ucCBQLvzLkE2OC1BsoeMwOnEqsDZbo3ADNLrwJwBmL1tpBtBZxEJQb1BI8ArV0lAsbESwQ==
奇迹之沙,vIFiQLfp4sBZkoXACYn+PyDbPT8G8EbAokquwCyy28DXbOhAp57WwN7HMcEQshHAcYXlwBmszcCpzj5APNaewNd7q8DkqZ9Alzg0QfI3CcFactZAPgWbPpBtAL/GaW6/xfdbQeaU18B0whVBvy3NQAtMTsBJ8sI+P63nwGBPRUBbkvRA1xr2wBUBp0CNeSRBzwvKwG4KXL/3ypBAfH2qQK4X48CUtl/B5BdvwehJQkCCgmDA1JmRwPN8C0BQy9FAlxwRwYJRZ8A2Md4/K4qrQLFE5EB92rlAlEKgP67/EUCuCkPAdoruwGaCt7+QCP9A83nYQFOSV8AfhsFAa9TGwA==
永恒的信仰,Ly6rwENQLsAbFh1AVCJcP9olGb7gHLBANBv1PnlHdcB7fxpA0jF0wO9Z3sDDp6bAVHA8wEBbVj/V5LFAyMkdwZF6uMAGohc/UYD0QKXs0r5MU1U+WiydQBV+okAlOJK/aHSYQUkXMcB5KkFAoacaQMh7D0HqG4VAy5WLwFRggj5lvqFAOyZGwJq7fD445stA1ZRLwCEN4MD5hgLAtlQfv5DSQb4G/CrB+vkcwQxD4kBSTAJAEm3vvxsdbsDA69w+NSpUwdChp75f/slATh1NQbsg/kBwA1hBMDuAQJh4HUAcNzTBk3aDwVUK1sBgqqVAcwA6QLVTKj9GTApAZtMUvw==
黑铜号角,eikSwN7uQsDYcvC/pLGUP8RVKEC6GolABagQwA8uIsDk74NAwP4JwI/LA8HQ8oM/qcyIwJpSAsBAptU/lP59wHTOG0BBkp1AU3sGQYWYl77kv+U/F555P/z7mjwAERDA+SDeQGOcL8BiMZ1AVPl7QFDPcUDlLsk9RyKywLAsob9OPcpAT+F9wM5VakB+4vpAs8Z6wKDjh72oB/M/ULNAQEfiqsCwMDzAXI63wMOUCT/QMj3Aij7mwCyA5D8uf9BA7qj9wHWv4j+JMN4+ilkEQA3AjsCXI5hA1IJKwFzJbUCcjFa/fElbvUSSqj0mDahALzWDQGGu/b+GSJy+RfNzwA==
鲜鱼炖萝卜,6IOvv0gWzcChsK4/iXQHvrOLLMFeiJjA3nlPwOTGvMB7lMpAbgTcwONwLMEu1xZAlU76wGk2kMCekA3BY82+wN+hPMBo3EFAougaQYslSb4gn5NADMgCwQDVXD8zFwdAnJ1qQS6G0cCinN9AX9qbQDK2Y0A9GEI//37ywNlJvMCwe/5AR07XwFs+CkAU1CVBs6jDwA13kEBxeqi+/cpBQHeGvsCuLvfAoC5VO2T0yD8SRvK/zKrBvx1QVkCCMzrBnZApwWRphj5HFQNASTa3QOlPPkGFJu9A4BpQvxKlFcAdH/vATC3kwPg4E8A/YQJB52TSQPJCk0DpL21AqtexwA==
勇士的壮行,eEFnP+RJcsAtihLAR1BKPsA2v0DEU4I/n0AdwB2FWsCfKI9Agfp/wDkqDcEGeRrADdSIwMkngcBs6ttA/Yq4wD1Iw78Cs49AWecZQQMWjMBaYzhASs1EQPwMBz+S9Xq/iPQ9QVN1bcBeZdxA26qSQE7jAj/95Go/1qS3wKter79ky8BAt/GrwBrul0B7cAZBZXaEwLB8lsCtsC9AOsl6QApOF79IUBDBBS0xwUJ5mkBD+iPAN4SbwJGUhz8Eg2lAZs8rwQgTiTygkgRAQI6RQLnf/z8SGMdAXrg8QFRaGUDzpU3A4h+ZwJxil78MVLlA3NqNQLXLPj/X41VAJd6LwA==
地脉的新芽,SIHWv5xrtMD3Ow/ADcaMQGWw0kBEEiRAPtR8wFO4ccAJRZtAdbePwE8VDsGhhhLANly+wCaSbsCtaAFBh3DowHHbUcA6vKpA0L8OQVXlNMBb/Y5AuvSrQBuDF0AmeGC/PmyAQc8oo8AvJN1AX0ynQDxdjEDJioQ/n7DGwGLajb8+RNpAqop/wEb8ekCn2AhBxi2IwHxQlMCj9n1AzYw8QFR5XkCROpDAJOtrwa+LFkB1Pe6/NT4hwfsByT9hTwJBfuQowfB8BECmL+0/K9g6QAY3ykA1CPtASUgMwQYBIkAiLJrAMoviwPB+fMBxOdBAnV+tQI5RNcCUygc+dWB2wA==
纷争的前宴,ytAHQKmy28C0NF3AXaBFwA1Y6r2d0MHAyo2qwPWLpcDEX8RASfzOwE/nO8H+mi2+AUC8wPPM18C6Juu+U2nSwKuRZsBPX7ZA25A+QTWwm8CS285AAkeCwMPQi78TJco/hkp8QVD9s8BL6A9BRcinQEwpYj/bMmW/Y+fswLjpdcBLGQJBoo3cwD2DxUCQ8C9BXMehwKxlmb7ScS5ASIacQAH+/MCeghTB67WVPxAl/b8UqmbAoRXtwH3chUCANrg/7SVDwR74XECtNCo/as9tQFGS0UB5iShBsGriwIcCo78nWEbAEuGlwO5Beb7Or/RAf+jLQNX1GD9Xn89AYMnqwA==
流浪者的经验,xhGewLLUMMB8KpU9sq1hPyKRu0AEr5g/Plssvzyrrr+ojhVA/LI3wG1D18BvL6zA6ASGwAYfDsC4HJRAVWYvwZ1vqcDurjZA/h/rQBsQmL80hK4/DJRKQMkDYkCSuAdA9GNwQZIjSMCynKhAuEhLQOmfzEAUKohAfSVzwJGROD4rSYFAjmMwwK9KQ0BEvrxAZogWwMzE2cCsH9U/AHpUP/WQ9kBGwOPA4hyiwR8S1UAiPk0/FeW8wBiJfr/4WHY/vVowwfIMsj1Sw61AAc+4QJpV3EBwD9tANicVQFW75T/glNDAKbs4wfc/ucB+pI1AkoJGQKamkEBa6UNAx6EXwA==
泡泡桔,U+fjvwGOw78BmIC/dXlswHenBECJLTpAkMRQPNtpEsDPmR5ACufdv4YUwsAfjvq+b7qkv23dvb4Cks5AHPo0wGjGhEDtRyNAFVffQLlKtr6Sppk+ZyOkP9xDLD/GfQa/GvLXQHGAvr9pP0BA8UcRQHxAmkBiwSw+nTJswIOmkcBKBo5A+chOwBgqTUDVRrNAUtEPwCPo0L+2awXAaavDP4Frn8AXS67A9TPaQAAETUBWrq6/qMUGP3jwIL3s/+G/+Wr1wNHNLEC2T9E/0xy0QBQIPD6hN7RAvNVDQPuGtD/tA5PAT0l6wFQIrb8ZyHJA9RgLQH/0MUBVsak/ytgGwA==
混沌炉心,FgUxwILUCsApvIq+yQ6lPwUbXUBS7YhA7vw+v1Az2L81ujRAImAPwHXt4MC4dMi/QiNfwITw6L8jcbhAYTDywMwwcr845VlA8Nv3QGsOVL8W8IE/kB1HQGQPyz/Xa/y/IP81QWi6CcBhZZJAS7NOQIuum0AtUQhAROSLwIS6t8BUzZ5AaT9XwCrQL0CMZ89A00E0wGQcfMAq3xxA9EXjPyxPzL7CjvHAzrwzwat8fEDKE6O/GyXrwHI+Oz5ApMZAM1UhwUz/C0Bg7i1AmXaiQGfpTT/Oos9AxJHQvsQ/S0Aj8mnAuR5mwKQZ879G34ZAKbEwQBWE0b9VEENAr1Y4wA==
凉拌薄荷,7KZRwOPe2L+iDlpAdtQ9v+kEpb50fuo/Dvb/vRNuJL+MaDI/0yEMwFG2jMCgeo6/a1qEwKHDy75aLiHAWQrswC+flMDmV0k+anWWQDE+CkC+7LM+firTPq5V6D+U4K29I9SgQYvvpL/W//I/IisMP/B3/0DiuSxAifoqwJ6TN7/Obi1Avfqhv/MIYr+bemJAIwHEv/AJtcCHSOi/fGkmv+Dk4sA+JuPALVy3P8L6XsAc4LM/bNO+wKTUhz2gqoE/ktJ7wYYK4j+WbHVARYzSQCWR2UDCPmlBiJ6pwOh6KEAciLLALvT1wPwL/r/Rvg1ADVK9PxWM6b8hDdI/zCEkvw==
千层酥酥,zu9zwNAwKsDdHDRA8zbJv+aiQT8S4G5AJ+Oov8IU+r9JcQ1AwyMnwGsRxMDf02a/+gq1wMk+0L7lsE6/ncbuwDdHGD7Njg5Ad1/FQK1znT8Wrrk/8/W1P1Zxkz8V6ri/bPyNQerzNMDMZzVAgIwqQPMZCUGX/kNAn9iTwC/ZMsAbqZlAxnqVv9Ei2726w71A0IVbwHrBuMDBQSG+/CIAvcjBlsCHNcnAT0Nwv4G4Mb+QXDM/ptjAwDn9iz4YYCk/oxZpwUhPjT+mq29AdizNQDPuh0DZC0NB9Y69v83krECjKrrAuMaZwAS/E8Cuq41AJZtZQFFEAT11CaQ/Q6+Kvw==
烛伞蘑菇,6Ufbvwp4T8Dryug+VKFUPzFQMT8jXDtAZ7wAvukyZcBvbVFAfp51wLjx98D4jTXANy0swJDMBcB/K+JAl1LkwJdTjMCljK0/FZoGQS8d1r9KUtA/LPlRQPfpJkC6aQe+Yg13QeNFK8DNF4RAgFpCQI7IuUDciOw/pX2IwAxO+cCIw6dAda1rwM3N1z8mCudA4PRHwJBYoMD5oNC+Ste8P4ImLL9gjzXByiXTwFPT5ECGGxa/HebeP+yzL7+ga+w+oronwUQ+KkAvA0tA0UYPQTCl8UDdVp9APCo3QEjXM0DuAebA2tgjwabSfsCHdqxAguBLQAbRjr4R4MVAGYccwA==
至高的智慧(生活),qOMLwMcHkb/oqy9A3Wy4v1h65D8w9CM/B6H0P7v0fL+wehQ/fukIwICOvMAwYRnAa/G4v8zC1r9MTIhAMJUVwe6W07+mmEg+MaTfQNH1fD8FXUK/HujUPuqQ+z9lP44/tCeMQZGn0b8paFVAA+KmP+8TCEHObkdADZxbwNhv6sAfx0lAIUcywJQQnz9lm6xAlwySvybV08AIaBDAZBN+P6bzJ0CPnwfBnwHgwA0KmkBy8Qo+5TIswCdks76ibajAY6lnwUwZCkDlHYdAyEQHQQBq+EBJiytBpDqdQNOKgz+8hwLBLODqwHxbGsD6LlhANa+3P+bepkBfM8VA1fwlwA==
没有未来菜,9FwLwHTzJMAwNGE/FHgqPxSuqEAwVn5AHz+Ev/y/2b9NHDpACBwZwI2z2MCYL42/miabwNkZC8Dl7J1AC6jgwEz34r8MTTVAl2XjQHvd4T4IRu0/lIZTQPxTvD82YUK/oQp4QSpeDcBoSoZAnfs/QMDC6kA0ueY/GxSHwBsAEMFi0p1Azu7ov2v6hj8gTc1A/kMxwMPU1cBEt1U/op7UP8jg1j//p8DAVcbFwPNQMUAcYWS/j60wwTKNiT9E4GRAMtU3wbq8lUDPwhFAor10QLB3c0D9Qe9AkNemwCXMqEAchojAT+KBvyB5GMAZJI5AJrRMQEMbHL8LgapAq/0WwA==
一捧绿野,7ExgwAT8PsDMqChA0AjtPnrn5j9N6WBAK2fbv0wFBcCaMfU/e2Q4wLvgmcBAX0LA4CzBwMFfo762svs+Fyn3wJPRSsDTcpk/haugQFBtQDsw1us/htSVQH9nJkCRvvK/9DCuQcQoTMAo9D5AbN/4P7VaB0HarXZAPylYwDjH9z4g8WVA1nCNv/fkTb809oNA96kqwEuO48BoJHI/8qZAvzWOhsCbrwbBoNumwGXeDcCIR/c/az/1wFhoDr+/1bpAybB+weS/Gj+noJZAkd/1QHbyEUEC3F1Bt3O7wNOdqEBUj8jAiQ0Xwd8Pb8BrGVVAYcMvQKq9hMC9AIQ/6CoEvQ==
琦色灵彩之羽,qYSZv6O6WsDmIh/AJC75PuBZEECmsARAzdYYwMslLsBxR4VAv7IzwCF4BsG3Gkm/cS6AwEAvL8ALyAVApyqtwEX8EMCcyIFA4hoLQcFQFsArhyNAP+lZP1xzSz9FAa6/oOcDQVFgPsBJMblAKdl5QLmj9j+sdYU/wU+swHb5jr8bWbtANTGRwDVoa0AAKvtARbh2wJFLo790XSJAA4sxQECe2MAu6/TAFsDkwHVQWz9IZw3A26chwYVLmT8UyfVAZHbowM9qnD9WocI/C9cuQMCfSr9tddZAhYuowLx5G0DpKAfA7pa8v1D8Lr8iKKpAsUyAQM7MTsAUQhZAIhqAwA==
饰金胸花,1CcdwH3KMcCFSLi+sa5XvkEvLz+eUitA9zSYv5HGR8BKAVtAX1NMwGRD7cBlEizAvdCCwK3hw7+C6k9AZ27nwISWrr6PCyZAXzYAQedKB8D4Es4/DlQXQMBu1T83ENC/QXJBQd5xSsCNdpRAj7VQQNx2g0AYd1JAk7iZwEy9rb8TbKdAvQqCwObvJUCsm9tA/ohqwM4ZN8DqpMU/CTPFPzZ0HsBz/RbBzroewUFVhkD+FBW/5zB4wJhoKr/GKRZAYfUswWmhxb/3doNAFanpQH2wZUBTYw1BBDeYQAbXREAl47TAmm3ywPqkLMD6xqJA/cVeQItKCb/YwVBAgVAxwA==
1 标注名称 特征向量
2 方块戏法 XpMYQGBCksAkDUjAlNVxwMnysECojt0+9ZluwJ7Ij8AYE5xAYquCwDWfB8GCpbi/XSGRwMH6T8AuQQFBf6APwNEFiED4ipVAUxQLQYjiocCwv4xAmhUoQH5yJ79LQPS/LdEVQa6MlsDeL81AFhihQGfS8z8Si1g+VTnCwEPgGsBisMZABH6NwE9Uj0Cb0gNBRdaUwGjldMBTvBRAFt5QQGhV3sDa1RXBzIAQvg/X0j9GAh/AT+2iwM3bvT8kRklAm+QOwRSFaD9T+K8/H0aFQKNGT0B7ENpAFEJMP9WAlEAEsEbA+cQDwNzimr1OkMNAo0WlQJTfBD6G+nRA6sNrwA==
3 绝对不是下酒菜 FKe5wH/MFMBcTJo9sNj2v+qiwkB8rK1AkGRYPBYI/r+H2hNAKP37v199v8AibxHACp4HwPIAjj+04TVBcG2ewPTAB0CRfkJAS9LdQGUYSD8gv7+8Cpq5QFmMe0CaIy6+ZApCQfoPCcDRCjFA7b0dQLyMBUH14ls/ZENrwOtpSb8iRpBAxeIgwAPOUED4661A8NgDwBZcecCsQRS/pCkfPYKGCUBD/xPACEcdQCtAkEAVVdM+050VQPRYtL8kEErAW206wcVWikAhyUFAcVHjQCuIi0BXfr5Av/qYPPAfiT02avXAD7wxwSbHrsANOINAz68gQDVpiEAlm0DA6J8mvw==
4 黄金乐曲的变奏 DzorP+aKxsCKTQDAQnjpP3603z9ikVPANFFmwBF9uMCDVKRAAA3dwLNPDcHoWtrAZnWqwB74dcDgm61A6qsOwd0OtMCdnDxARNUSQRSz0cDcmKtA+Vo0QK6LXUADMC8/f3iYQRi3ycDkZfZAd9CYQEZs5T/RfI5AXqi8wAPZoMDHgsZAVDK2wDhEd0D8UwZBx1KawHqN0sBnlIdAsrS/P1g650A0mmTBVJ2EwYiFA0Ecvxo/uUaGwDjoob9WB6m/0rs0wfzuAcDOv71AmgQHQbr5XkFTpjtButdPQGDrqj/ObSjB3ARswQhsn8A+aOdAnGWwQDqNJkB4GexA2Dl8wA==
5 黄金飞鸟的落羽 8DumPB1Dp8CSdTvAn9E9QJApAUBY+1s/KmdRwIvjlsDoVaxA7F+ZwIYQDsHTovq/aXyTwADPZsDLrLdA52WfwIbSXMD4oHJAWNoOQTJymMAKAIpAWC46QEsMFkDgtZq/0ts9QS39ksCTAuFADpqQQFg8gD9doKA/iLG2wO9xoMDLPspAHBmzwKAee0AeVwRB6wWNwCxXvb82dZtAykwrQCRACr+vqTDBNvNIwa+/iEDNpAHAZLvowC+Vdz988spAfv75wH3s/j9BOwJAoEWUQMjvy0CoNpFArHCOwD3S8z/UiIfA7J3YwFQcEsCJNMxAhPqcQIYCQcB3OoJAUKN6wA==
6 枫达 pC2IwFdWkr97ffM/ZdvAP218/T/CbaBAOx+EP+/ZTr9sKOE/1W2iv+OlwcDBrA8/8pViwLw6zj122/w+g/68wJ5dZ7/XFhBAyMrVQOZeK0D6iba/tyYMP0Bc8T/qMSC/ULE0QZdXn7/c6SlAoSj1P1dyy0BFLV8/uR10wJvVs0CYOXlAkS4wwGNrlj8cAKtASrgMwFkblr8m3FvAKP/hP1rXNcB+UtU+j3LIwF6csT5EGRq/SG6RwG5tF75AlAk/HywuwSwgNT/6iPA/VPugQDVoX7/4zxVBE4hQv36fHj8jk3PAjq+nwGzGAsBFl05A2dX6P78tJEBQxE/AwuQXwA==
7 常胜传说 4EacwK2s9r/srus/k8Ssvzc5iD/IzmdAlsQmP2LcAMD2S6U/L1YiwH2htcCvPWDAnHxIwIRiUD+6jVJAixUDwUN1EMBza7o/hJnJQFHjMT/Wg7E+nG0yQGmVXEA6Pi2/2J6QQdHG8L/wCCxA5XzOP8Bi/0Dpg2hAh3VdwDCmXcCRvHtAv0ANwEjYcj9CKaBAKHkCwHSJ0MAh7c++IOtXv8b+C0BluMzAQLyUwGD3X0CJd98/kPexwBoq/7/wr128D+BZwc+o8D/m6aBAbVH3QFYvbEAU1lhBbjQxv/IRFEB+sw7BQos0wShEf8CTMm1A4CEEQMENIkCxLWY/zHBRvw==
8 乾坤摩拉肉 TFJ/wIOPKsCqSKE/bjrdP0kAzb6OYYJAJIMKPYm7U8A7MxtAM95owO6DzcBi2izAgA0QwN66Bj4v0HhAPrjSwA/n1sAvSY0/XsvaQL0oxb7O7Ys++B1TQCm/iUBeZCS/YJ11QQeiAsCboTdAe+D9P0cGtEBeFNU/S2ZmwCuHrz+GZpFAVExlwA5IkD+eKrNApQUvwGoVXcAzIOW/UM3SPkYwpb+g4PnAzMJ2wJKClkAeODE/fOW+P6LTEsBId1k/zI8uwWZkoT9fTHVAG1MZQTAL0EBiqhFBIGRovjS0+r66J/7AMllmwdvvocC57IlABBoZQAYdwr7y7am+L2Kcvw==
9 魔女的炎之花 qPIjwNTZhMAA5Ly/2g1MPywenUAoZfU/kyBBwLvsLsBsk5BA7W5QwLZUEsEFRe0965e0wIY+asAYceE/u2XCwIpyY7+odqpAhtURQfExmL+QgUJAq7zLP7y2Ij+nawS9e60uQR3Me8CMMs9A83mTQEBEbUCdJaw+L2W6wPh7/r7AOdZASnyKwA6zhkAvtgZBvlKFwDQSBcAu3F1AvY9mQLpU879x5mjAGr//wDTUcz7w/TTAYyIFwW7gKkB6dohAdcEmwfp0GkDSx4s/A8zXP5hRAz/II15A1u+RwCbrIEA489q/KCJVwPTZ479j67tAN42fQN7Cirz2woC+y6eNwA==
10 蒜香面包棍 wNUNwFM+mL9V4gBAuUCSP+zBWkDySUBAlg8cPx94Zb+Yn50/50kFwG99psDU7dO/2Ms5wLFY974XfRdADubfwJ1gWsBhNaQ/AUO6QKfsrD+CCJq+zwLLP4NKJ0D2+RY+thV2QRcI4b89KUdAsECuP0PV2ECGGi1Ag2tgwJF8v7/1UVxA5MX7v/cBDD+Zyp1AeEgHwCIgycAg8z7AoCRYP62Wkz/VKWjAgPwJwW7QB0CE/T8/oS0HwchJZr8YLkI/aWs9wfAPVj+y0WRAAiuwQLtVUUAcND1BXhGWv08M5D93qtHARk6ewH5GMsC3S0JAYVnwP621AUAVl8c/6Pnnvw==
11 双果清露 tsvRv+xdlMD2hhc+gCz/Pzc/40BmHBtAju4EwDsjfcCkRWVAcNaPwIgT1sDYK7zAqwO5wIaeqb839yJBQsYLwe456L8WmkFAg3fnQBCTYMCOCFBA05XtQN7VaECGMn6/srWXQe5GocDlQ6pAE76HQFq60kDM94JAyjmbwN56k79mupxASeYIwBZW+z/mo9JAoKx2wDKQDMEoxhNAcRIPP6ZxIUEodfDAPPqXwQY+8UAaR70/2KyWwK5a6r+wee8/A+ZEwQraEL+VxqNA1+f8QOBwLUEcBRRB0oYlQAOzjUBw6RDBNqY0wchdwcD0krdA4rKOQCO3AkBs3U9ALozYvw==
12 磐陀裂生之花 84DgwA0QPsCcSfE9APuAwPaHUUDW2gFAMU+Vvzqz5L+ijUNA8p0MwNQYCcEYH/m/ywOkwL/p979m6oa9gL0mwYiYlr/eFYRAkbcKQS/SXz9AGqU/83PVP49pA0C9qpE/hyciQUgiUcC0waBAtNp0QIkcBEHMH0hAGBGawEqtQb/aS7dA69s3wEyMV0DMsPBAm/dbwEaLKcCRiANATiukP/4LEsBjwbnAgdEbwZf6B0AAUES/tU+jwNqmkT++5p69rQokwdxmNkAIE1ZAETqkQJDeLEAM7No/dTGTPzwtBkAGh0XAoarUwFtwkcB7FqhA0U5wQGMcl0DbTC5ASw5DwA==
13 炸肉排三明治 w4/Mv29cxb/7uoo/vdgnvg/uuUCEm/8/wkdFPx5+tb8QYFY/a3UQwPz2YsDDCJzApV3ov8DrgDuOkBRBVT2owCVxh79AN7u7SpiIQOMqBL/byhQ/9EWyQNj1hkAtHIG+C9GFQe6s3b9rsCFA5kosP5vZAUFf9nZA4Gjfv5KiNsEk9Pc/L2STv2bjDz8YE0VA/k4wv88QDcFQcJk/hTyTvyq2sEAjJRTBlJ0swIkfpEBIA/Q/ZRy+wOaY4780Bbi/rQ49wUiciUC19JFAK77QQA/OGkE7kShBUHMMwFLDVkCYZwbBM6sOwTGpZcCnsBpAHgB4P33BAEBtZKdAMkglvg==
14 教官的胸花 Jkxsv3IYaMATN4+/egkMQAM29T+qAPo/B1zZvyWibMAdzY9ACK+FwP5XB8E0GlDAkAebwMSnQ8CkJcxALJ8FwXtk4L+RVFBAXEEQQS/XY8CbeSxA7GRsQCZ5vz8fLOK/4q5kQRjhZMDkv7tAcOeAQN7/bEBIAT5AHw2twGJ/pcBOFL9AnjOOwDGwPkB3AgBB1KCAwND2csBhhj1AVDobQPLmcD8uOj/B3XBjwVHw40Ck6pm/eNHuv07LT77UcUpAZR0xwfjUgr8FvGJAQ4XgQFPDt0B72etAxuipQPnthkBWsb7AQIbswKb5SMDIcsNA6uyDQCumX7+Dq7VAJeJnwA==
15 守护束带 uOb7QDJNX8HukdfATEZHvn6MYcBwcD/B5yhAwWRmNsHmFz5BiutMwXvYj8FWkWpAPDdAwYzrTMGABxvAk1udwI0kwsA3BytBJaeEQSQaFsHG5FlBHBziwHoGh8C0aiRAMBufQXJrPsEkOHFBeHk3QYRGkcBxA6XASVpXwUxaXT86EWNBpsI0wZyPE0FoLohBkuwiwfV3bz8bCVpAjMMaQWSkH8GLvPbAhKI9QB710MC0YuHAU4X6wHfkGkH4Kw3ApvRkwcr3oj8gHFDABnXMv2JlQEEl0ypBvtpcwQYVlcBuEzPARoIuwCoRuD8Cj1hB6lNKQXvFtz80F7hAS2E/wQ==
16 沉重号角 auRPwOuUs78J/em+xC/XvqsLa0Ci2R5A0mm6PRerO7/mWPs/KbmDv6B2zMD/JMi+EBQJwGRw0b8+Z8E//n2VwNYYGz/HuTZAhFzYQDdpgD+QumG9BEFpvjRulz8gs4k6l6rRQHzfxr+FL3FA1UYfQKRynEDGB58/ELZ4wENlh8APt4JAX1g9wErKNkCeT7xArFgPwBJ14L/czUw/J3f8PyhS7L9fZYLA/BqcwNk0MkBOv8S/KuXKwPi+Mz9EnoA/OUvlwCsBJ0CuHsY/DmsAQGadAcBRSJdA1zSlv/QoH0C6sA7AJoT6v/wJ+r2cSmxAKkYYQAzLOEAwh+I/SpAzwA==
17 超级至尊披萨 qPQ4v/4iIcAJYEY/IAn7vtgeQkDqFjZAmKWNv7KWK8BNUw1APs8wwGiEqsBODwXAMDCFwBTVvb64ycFARsmcwI5q3D9UFdI/eie3QKY8o78Wcs8/Xup6QPF4xT+XdOO/DXluQTZjPcDIEFRALEIZQIxBxUC2Ng9AFg9xwJxSJ8CWEHlAmev4vyjyHz8g0Z5A0T4ywD/Qr8Aroq0+iM/6Pqz6YcCGsvXAYDanv5H2Cz1+3bI+sxTfwGgZib7HIClAEy1BwfpDID+OPENAQW+6QL52uUAoJUVBVFnCv7R9mECsPr3AFoqYwAVtF8CDlWxAyA4pQBsJ7r+YKiZAip2kvw==
18 烤肉排 7FqywGu9NsBIwX9AbvXevxmCnMB0VTBA9uJtv5uDB8D0Is0/pvcswP6QrcB1zXA/N/mtwNJ5KT9pe7bAdHbTwFmL4MCaCVk/kBepQG2TE0AgxoQ85gFmv1By4T8OeFm/1r6KQcl39b/YvKY/klfKP7jf0UBNoAY/uGhiwMoRMkHDaYRAsowOwIgycb/zLYxAmuFCwEgi3r428FXAX3o8v7RpgsECTdHA+t7gQK1DuMAS+ZE/XsUIQEyMnr3mLQxAt/BbwSgUYT4O2zJA5AsPQTSj8EByOi1BlOmtwOBZZ771l43A8Pk1wbPBT8Bk80dASMAXQBIxQ8BE6VrA+GQOPQ==
19 鸟蛋烧 1DaFwAnQJr88JQFAb+AEQMZhsUDOMs1A9FsHQGpFl7/S5ZU/noHXv2zmocBUi1HAgTnvv9nJvr4aATxBeBELwR3R/sBjJfk+YcXMQNZunLwEEq6/JmbpQL+YZUBWQkq/kOl/QTjyBb5D2RZAzyedP3Rx4UAm7Z0/yMLkvxHRKj4P5jpAYM0IwDgMFT+qynhA6lFOv2m32cA6htC/HtKFPxD+oT5OewjB4Dguwd0SwEABX+E+a2nGvvfMA8ALjcpAd5k1wd13O0CJcFBADpsfQbxw2kCYXrBAXtcUPzfZpj8eob/AlUE2wQhGzsC8xBBADi0FP2MYHsBUDx5AnMKBvw==
20 异国之盏 ltWSQDJZ08B91LXAMLAEQK6YsECvpf+/XvWXwD2Qz8Cpp+FADDbVwNI5JcGaKWTAVY5/wChXs8CIOjlBbNtYwAmNGj8QeadAGN8rQaz8CsFKb81AuFeIQOKT6D9OLnO/5PUxQU6ptsDRmBJB5ZW4QDDg7r+RGdg+vwTUwPCCKcFOv+lAjpHnwPqPykBCkR5BLpybwFcWQsBomOBAP8iBQDMYVkBSVmrBjAz2wLhJ/UBJ9VvAdtNXwIadsj8G2tc/7y8JwYZsT0ADd9s/orqGQAK36EAbe4BA4kgcP6fvIUDItYnAdlTSwOVsgr/NuvpAhmXBQJh68r00qfZAMYatwA==
21 教官的茶杯 gbqLv6+ct8AUogLAwCxjQLJ3X0Aw2R6++clwwGD5mcCnM6lAc8i2wDy/EcHFMIXA6Uu5wJJmgcDWNshAePoDwTUBz8AD1IBAmK8WQXgqncDHp5VAsad3QPXEIEDO5EA+fH2IQZb2q8DSheNAHy+cQKTSPkAUajpAYr68wF5nJsDB3NJASKyewDTHd0DEoAhBITabwN9HocC3d2ZAG+4nQKRcXEAVYzPBkCp/wTQq3UBUIGe/gllYwDi5Er2payhAUysywbyE/L+zfXxAF/XDQA/PGUFOc+9An8sBQDAW5z9j/ePAlRM4wXobnsBJjuJAh8SxQC8QR72LJ5ZAV1SAwA==
22 历经风雪的思念 jvCbQSsNksGyqlLByBvsPhrObb9+er7BQzSDwQMXicFQG4RBqYaWwcipsMHKqFY9rn47wUJRmsGW6d5AthkUwCqNUMA8Sk5B0XWsQblqp8E3VJ1BqoTzwC1JscDEHINAZw/HQTLbfcG7nrBB4RpWQeAUYsEm8AHBRml2wSRP5cBPE4dBLtKNwQLfckFvX6lBDNkywbcM/z/xNyhBBaNcQVviIL96Lo7BQGo4P3+KGj/h7zLBB19OwN3pPEFGdO3AOzGAwfxWsj8wcIPAKy2fP93FtEECHBhB/bK5wPMf0MA7abDAyMkmwXJwE0BxO4hBHwZ7QaYglECr5FhB0OiIwQ==
23 日落果 JfXavz7THMBrNaa/fgYOwE0TcUA6giBATH/5vkkhZsAHuGNAkVJIwPlI7MDLzEPAqaH6v9Xjqb9APAZBCVOSwI/OFUBb4yRAqh0JQXPKMcBmPIY/QCppQAjjxj+maiQ+e24iQTN9LsCbKYpAJM0+QOiqiEBPbdk/w42RwDdhQcAWZ6RAiMSIwPmGeUD/l9xAdPJKwMo/icDFMpu/yjUGQDyf7TyYJxHB7bQtwEu2/UB3mJy/AseFQDamdL98VXzAB6EewftkE7/pNl9AWF8MQabPaUChaJRAB9YuQQJqGUCaQt3APv4XwcPiWMD9H6lArq9OQJXIl0CFHRtA5vofwA==
24 守护之花 hDAfQaDTAcGiTgDBPg7Zv9VsJEGE2czA/HfUwOmv7sAaHAdBohgFwehQQ8HSFILAdZKGwMjyDcEy83ZBTSXWv3ibKj8rvstAJT1TQQJhOsFS9BJBax9AQOBcmr9ND50/8NJfQUHK0cBQgTVBYL3hQMJBsMCLkQ3AGBL4wBfXJcECUwJBr/sQwT5PDkHkwTpBFwKawJ+X4MAkCKlA/JnWQEQfhj8sDnjBqGFpP+Qq1UAdbqzASp+lwNkaYUBq/Na/xWMrwaBHhUC2quk+MbEwQIO4G0GkEwFB8nekv7Qfiz8GVJjAvmaVwEJtpD8KcwxBSoXsQJ5y8T+aiyJBwKABwQ==
25 流放者怀表 Zqy9QCMgBcGZyMjANOjTvljEBT8qLsTAqpfjwEDg9MBsAwNBUer6wFzSRcGirHg+dBy0wNJW9MBWWLRAIj4LwIbazLymtNlACX9FQSa4C8HYrQNBzXWwvx72jL/Cgi8/SnRBQUrr58AozSVBP0jQQLG5gsAHngPAA1gIwbhPq79EihJBSswOwZBa8kBQWz5BltLFwE60xD/Nf3VA5uPIQNIG78Bz1C/BS3kCQPkpdb8ZjqnAw/adwIfjjkBswTg/digZwRgFrT7vKKC+478bQHRiBkFlWgpBaYihwPeW1b/ZSmXAc7yUwEERkj7FGBBBG4D2QGcyTL9ktppA/UX8wA==
26 混沌装置 1+SxwAckZr881i8/ceyQwESKFUCyWIRACqEZPzxaF75UeK8/EOQBvleryMCpU98+pt0awEhWID+LUg8/nJ+ewIU6l0AmijhA2TfVQIXWLkB+FIe/ITgqv8TuxT9ZLA4920zHQB7plb+uICdAX2sIQEp42UD8WII/RxyBwDajmT9fRnxAedIbwAC9JEB+obRAaAgMwIaPMb9MiR3AMqSHP/TJq8C6qCG/0w5DQE5Mpj7k2T2/lMC3wCQKjj30Aue/JoMOwRI0OkCTtgRARX94QO3el8DsbfJAzNYVvxs1+T9pgmjAJtXwv7Q8hr/3ylJAcgUPQGdBtUBjbyTAhOcDwA==
27 祝圣油膏 oKVHPv7T0sCNtWjAQX8EQAfwzr721EHAx2+jwN6sucAJcMxALiO9wKarKMEuQG++ONnCwCIksMC3Qis/WQqMwOebXcDa4alASV8kQaZAmsAiKbFAOUwvv4CQIz8cjTc/YuMpQQa8y8B/YQRBhhiwQLYrET6q9Gk9W/vfwJpWBECWVvlA7hDRwMhkpUDscCBBReq2wBn4qD+allxAjCqUQCqA1MBI0AbBfBsjwVJ1nT6Krl/AUu+YwLZWQUA4az5ArkbrwAke0L9dz0M/ktVJQEJ940Aw1GlAD3ElwGCVHb3yZU7AumPlwKErE8CuCPZATFHQQHuyX79t0TlAh426wA==
28 炸萝卜丸子 q4dUwHC/ir8wNtQ+fMQ2wIJV2UBuxI5Arr9hPzjDYL8ed+I/9HCNv8ymqMAQvSbAw8wEwBsayT7e1RRBuACswIeJhEBlPAtALwzDQHbLHj867lO+WMZ+QPxVMUC4wDu/09QdQasou7/DIjlAaMsIQBnCAEHhwBhAlJdMwBeFDcE7sllAw8S8v7hLCkDGl59AjO7Nv6/RssBomN09NsGFPfiaM0AkyqrARhwVwMOE1UAL69k+ytYawEB7rL8wnR/AVnYdwRXOnUCIHVNAI3a6QH8YxT8nWKtAQAcxQEhehkAzybfAAU6FwHD4EMC/Z11AgHjzP+6Gn0CAyjFAerx3vw==
29 故人之心 BDpaQQZlWMHPMETBGP5aPpEtRUFUCn/BqpA1wUbgL8FN7zZBu9hIwUpNgcE2SaDAv9O+wDHCXMFSXnZB/d6Rv+7oAMFNghFB3qqAQS7ngsFQ82xBL421P3BHCL+EzatA+rqQQSqlOsEgnYVBa5scQXTTFcGSWHnAD8YmwXlx5MBonC1BupRFwUxZSUHg43hB9jz0wA32ysBxDvBABF8hQRSWUEDZG1fB6LM4wBWTfUCSB93AElbswNAav0AA3C3ApAkJwYKrKEByLJ2/kvzCv+u1lEHyLP9A5jAuwS8fl8D8NMDA7LA3wUdKhr5WnkRBIQI4QVwNWUCWET9BmkQ7wQ==
30 奇迹之杯 /+keQE8m2sD6FnvAS1HgPzZZ3z9O1GrAx5SRwMAHzsCkRMZAcMHTwAxyGMERlDLAAmWMwPIKmcAiLtdATnRiwKzWRr4cLoNAKqcXQVILzcCb+cFAmXWhP5jFEUAN+hI/44lIQRxA0sAo3ANBmL+oQKLVrT44wpQ/LGHTwPRZC8HUAt5AWCbOwO4On0CR7xZB/N2jwJgsx78Sv45APOxHQGrDQUA0rDTBaOTuwBixxUAUNArAWCtrwGZFpj96mxvA5hb+wGa4rD9qxRRAjoCLQGJlK0GALsFA6ZcLvz5fQj/0DOPA6g0HwdMsB8CZZPtAwyjJQKXOA0CFF85Aov6fwA==
31 苹果 BIuQvzaGEsB0kvS/+m5cwDrgTj9Shx9Azauavzf2KsBbgFpA7sHgv+MG7sA96hhAdE4FwPAyub++LyhAmmmyv3HibkCxi2xAkaABQdsbSb5L9HE/Tbusv8Oylb+tcvm+3eGnQAvF5r9D429AVzkxQEpWNECAzAHA+hOYwMotUz/NE7FAEiSOwEXggEBn49ZACyo8wDsTpj81cz3AQxlNQIpTN8GdpUbAC3YRQWbF0L/CBk7AFlniv16S+z9Uusc/LVzfwLMJCkCPXMK+Azl3QJJfScCT/a1AbxzPv5pDRz+v+fG/BAZBv/XfvD7evIhA8JtJQAQ7YT5LwUK/obxhwA==
32 学士的时钟 A0+EPzMdtMA4sYDABPgTQC4/70Ax+ay/gYRhwCcKlMAhcLZAuUKswDyVGcHiaoHAo4WRwJvipsBI9gxBDPa7wGZwh8DL7pZAClwhQeyvqsDOS5xARZ50QE2EC0DqZSE/phRNQVihosBPzgRBO7arQAOzHT8FTZc/bC+8wGZk7MDFZc1AM3fDwKK6skAynBBBRIOIwPbWsMA4lKNAvP12QMVdyEAqLhXBiCCJwerHAEH8MB7AU/i9wMx3kj/UbxBArZEAwU68M0CmoAdA77VyQCgY4UD60jdAEuhqv8aOmT+Q+YvAhKgKwRZcLMAAVtxAC8KzQC32yT/PYNJAtImewA==
33 鎏金殿堂 3NyfwIdJi7+v8rU/OLBzP6xlrUCheKFAlGX/PnFXGb8o+kc/jHSbv4ZmfcDwHlXAfL8UwI5RpD8ujJRAlfy/wLqVnjwbN4Y/caWNQCHc6D/ZMAG/sMmeQIMji0ASsD6/noBLQcdMz7+O8gJAquZjP9vEEkEbhmZAzbwYwLjxg8C3uSBA9Ih4v/thED83ymRAJ6CVv9Bn1MBEkDA/PBt+v+Kg1z/HylfAnyjhwCi5wD9a5d8/50MYwRpD3L+2GkhAWb8fwXgXY0Bp9IJAWXG8QKU7W0DmexJBsWWSwMLyU0D3U8DAnnPdwJWBeMDa5hRAH9CtP6z2Cr42eZ++UtFIvg==
34 提瓦特焦蛋 JqE9wHwNU8DO2RU+n8kjPiaVi0B+enJAt8Zdvv/SSMBZflNAkSWDwKMQBsEWtDbAHgwewEcd6b8FnyxB4HAEwWd3qcCzWThAQPgPQdP8Hr+quYo/kIiGQOv6fEALwrk+w/R3QUcGCcDf4ZRAXIdlQDSS00B1SUI/Dw2XwHJ7BsFL+L1Aqnx4wJwgQEDQSfhAINRCwHXIvMCI9pC/8ujQP+gZsUAEvADB1WJgwINvGkFQckS/ciGQPwxxm7/XxAjABtgvwcAnxEBSBTtAS8UBQcnuzECvcNRAalEPP9jiMr6l/vHAg6A4wYLBncCoobRAG4FUQMp1hkDl5n1AyiIlwA==
35 冒险家蛋堡 dStFwMCqQMA20CFA4Xfnv9aGFkC6ACBAdxDDv57nDMAs+Oo/vyhKwD2hpMAM1j3AnHejwBErrr5kQJc/xNnawNre6zzkbtE/HIOnQHPTH76cJ/M/vqpnQN/tQUDwPb2/9vWiQbqLV8Dfg0hAKs4OQFn2B0HhyYBAURN6wAyJx8AgeH5AJxeIv84Blb3q4qBAsOA2wJzS8MCyMNg/TRaEv5i5ZT+cjvfAcMktPXsmXz+FUdc/cRbCwPxZE7/yCVO/NOx4wWxDOUBEoJlAMKHiQLoiBEHAa01Bf/5FwLClmEDkUObAVswQwb+qOsCw8oJApMtAQFUGuD9cKsU/MGSuPA==
36 祝圣交响乐 4jJxQB0FvMCz2WvA1M/Kv9xRg0CO4ce/3LmZwMkgr8CLkLRAHSiowKnFGMG3Fv6+FciawHWVjMDaMs5Ajvjqv4ghAkB2iZdAU3QXQRMopMDq6q9AX+nUP2hMN7+GTmq/oVozQTzyqsDRz9xAzEGkQDp1vD9uwI+/B5vUwCz5ML/YAd5AcSGpwEd1jEBQfRRBqrOawBJNI8BEL+U/as+CQMZzH8EgCBLBZLuuQHit9r8MCVbAGf2qwPBCMEB2QhxAh68SwYJLpT9luMA96Oc2QMNutUCqhBlBc1upwAieU0DpGjrAqm4swEL9Mj6k1tpAIF+5QPVI7L+mg3BA3+KbwA==
37 祭火礼冠 zhVpPzqOucCNfQDAtFoJP4cmTcDKwuS/3XOPwC40qsDnz75AcF64wPr8JMFCLvs/OJzLwHiymMA4Hy7A4C+DwKZcOMDYVJdAxsIgQa44UcBFtJRARPRXwEwIh7+KknW+zjIpQdYBo8DRKd1AzZaoQLKcRb9a2Ha//A7pwIsXo0AER/FAKsjbwIRngkBNGxlBD761wFmp5z+SfBM/eHCUQHF5VcGQGvjApoxXPXI5VMCK7mfAjtWZwKY8VUD+rpY/H+AZwTiuCMDd3Qg/TpRdQGnXXkDsGBxBT+JgwKPpTL/MLhrAiBFawLhzSr3gbuBAIcvBQKsbA8Cw7pQ/H5a9wA==
38 摩拉急速来 6nYhv1apTcDeiw+/IFKZu6n/tUDLxj9Aa3fIv/1iUsBORVFArwhVwPrY2cDXaj/A+mtZwKJWab86sA9BWMmewGwdOUAS5jpAAufrQK8REMAYlwVAsJuiQGkzCkA5Cb2/ovxnQTDFWcDCdpBAyJZGQMG2n0DWswRAlX+ZwCcsbMBkDp9AX+NEwPjUIUBRktVA6/NMwBD9usDQr+E/cRitP5FSRkAKa8LAYp/RwEi6cUCORQC/r5iXwDQABb9gwTU/D2BEwVBYyz+KrFFAuxPJQBDuiUBW/CVBN56hP3mIh0CCw9/AowziwKoHMsCU159AInlrQDkRbT8wc8k/NksawA==
39 蒙德烤鱼 XIePwLT1Q7wu8jlAXxlAwOGgeUAclKdARN8HQGYEk74o7Es//BpNvw58s8B3LIu+Ra/2vzkB0r7mGOZAHNUCwe9Ik8AuBZ4/6fXgQIZUA0DmS9W/oGY/QGQu2j9CPyc/6piFQXYiED/JYPw/AikjP1DW/EAP7aW+a3j+v5hklD+TUVRAln3yv/YUGD+R6oxA0M7XvbgVs8AsFnHAfKthP5TN9cCxFfnAivUWQPbyuz4/06K+XJ+NvyT3qj2AZwdARbxWwXZqrkBvSBJAFOASQR/zr0AJ+9dAQXkLwNNH3T+Nl5TAgrr1wH9miMDC6+c/k7qZPtgDnb3lD/0//G2kvw==
40 「美梦」 OTEuvy/XhcDHHPk+VLIoQIIciUBcnwS/h1ucv2FUWMCuDitAwW+fwLppy8AbN67ApmxiwKqwB8BP7OxAhsUCwcgB5cBfrJI/bcXbQNXJKMCghDJAJDWAQBcNh0AyhLE/ILKpQZ4qgcDVMJxACks2QH+RyUDKWoFAYbyLwJoKrsDu/YNA/Xs5wFMpwT8YvshAOJQ9wJoJEsH5Gde+xGEtP3p5DkGi1xjBBRxGwZ518EAtpcM/gRuSwLqY7L/glzzADVlNwRBmpr63LqpAMfTmQHiVVEFdw1NBsLxUP04KYj+XjS/BSq5WwUjsqcD4A6tAQfVrQDPbXUAkfqxAArMAwA==
41 守护座钟 UzrlP/cM/8BE42fAJOdNvZHxf0BohdXAjoSkwMI7yMBjp9pACzX0wFGfMsFw0WTANGPkwFtc48B+selAqITmwK4ANsBYZMBAyRYvQVv0x8CzVelAj4yLP24x2z/hLQxAHUaAQRBx88BpbSJBieXXQLHDmz/1TU4/1zzmwL1jMMGo+fxA1RnNwKIMy0AfOC5Bv72zwBpySMDrELZAZqeOQFhzFkHIqxvBIhkXwbE+DEElbRrAEucLwGfbLUA5vLDA+zEbwYk1P0BwQ+s/LNYqQMHvYkHZVHtAIP+Dv9VPhL8sot3A7AUVwe8xa8CGBg5Bc7/oQF4y2UAoRwRBO//GwA==
42 翡玉什锦袋 GtKOwPBlIsB2kHVApRLDv9P/6z+Wqn1AhnJrv/GgBMBuqdA/O2xIwFA5vsAa4HzA3eq5wAAFFL9zmw0/kPcwwQA7O7/fcNI//tvPQMsGnz7ce5U/fhSTQPN6KkDTrvO/jVvIQSBmRMDnzVhA8mcIQHfDIUFshp9AAyKBwCweiMDJhotAnE+fv/ra2r5YzadABBIewKJoGMGqCxZAOLCRv/oDxj9LfB/BqPr5wNsgST8O098/v0bjwLysCb+AYa4/vzWdwQBW/z/5V8JAYMMgQfsOB0H8W3FBAClpvoD21kAkBgDB14g3wSa8c8DLNIBALLsqQNoAnz6V3xJAYZPbvg==
43 轻策家常菜 LLZnv8WOc8A6Ovc/lG+Bv2lW9r94yhzAw8i4vxWkasBSu0dAb0KZwKXhBcGPC7G+lFKfwM1WScDCdPC/2mL2wFEWnL/MyStAC5wAQQPWIL/L+w5AWjgAwGxToj+LV9E+eiOKQRxzlsDLJL9Af3BkQGNXsEBmHt8/RQ+4wHUkoMBsT71A4ZyPwOBd8j8O1gFBhKR8wLZAM8BPX8U+FaIRQDxi5b5M+/PA78CbwMwgAED2BVi/4qVXwPoW0T+Q65rAh0BWwdY2Yz/9JDJAWtq9QAKzEEGxVR1BZOdvPzqSbD/2Ju7AI73nwChpAcB+47lAwX+JQHGulkClQFxAR3d5wA==
44 雾虚草囊 mId3PtTUkcCojE3ATumSPdwqiEB4VLY+T89iwKmmd8Di0JlACgtmwPWMEMHZB5I+nwGJwC26aMBU22lAu+0wwINXFkD29qpACe8QQVFSLcBw4WVAzlvMPnBpFr76wiG/okP7QH1ijcBmYtBAE9eaQPN2oj+rASm/CZPNwGiSyT6INdJAzPKnwCVhnUCupgtBDJ+NwO4NJ78fQwNAJzaEQF2mpcC77E7AQwEjwObIjb+llFjAGhkJwb9mJkDc8V5AJDX3wHChnT/SDVs+wQOHPyB1I77bk/RAYse2wLgFyD+nWwXAEfnGv4ILWD7Jz8JAlUSlQHZmkL7el7w+VdOdwA==
45 宝石闪闪 Otp9v9kEO8BmOEy/3fYKPzoWtUB9LIVAibzFv1aIOMCUyoFAGVgzwCYA6MBfMg2/61SvwNqiF8BIfQ9ByNOMwP7VDb/BPHNAWX3/QKddAcCY8v4//7aUQJwcLT/MFAnATu4/QRxFLMAqIptAi0VxQC98h0Cw8dq73vOCwE9tKsAhnqVAIgVUwBCwHEASfMtA7kZPwEz5VsCk5xZAX/EnQLUQtsC7iv3AkEiBwOYc0j99vQPAavmAwOTuuD9bsKVAqUkmwVC6R0CN+XE/xlifQAVdkkBlnodA5GwCwM06gkAT9My/oA6AwG6KBMDS0opAcetOQNOLLMBnJkZADzMtwA==
46 提瓦特煎蛋 VnC6wPYuTsCYfUhAfvFqP04M4T54PHVAaF4Tv2PpHMDr1ck/sKdxwFM3q8CbBHfADheGwGtz7z5U2QNABGASwV9fIMFsVwo/zs62QKNqez9npjc/pCCJQD/Sl0DU3vK9euO8QRp/DcAywANA1jreP2ayFUEH50NAp9tNwPfyDD8WoH9AGB7RvySxAr8oXY1APA0awIgp+cDAqcu/c0zZv6pr3L9j5RHBKhBhv5sf9T87CTJAu3oMQDt5FcAOEho/a2V6wRp7CkCES6ZAdrYmQRryN0HYRVFBZiuKwP9tmD8QPBPB38CQwesFwsD0VnlAeEYZQE6JG79cv3c+Y+YwPw==
47 唯一的真相 9OEiv8zrLcAIIwE/7behP4zjrEBmUcU/ZH/DvppmL8C1AhZAkvVdwMxCtcBF3Y/Am0g2wPwwmL8N9hBB+3TBwIv9TcD+BmI/hwbIQBEeB8DYBOk/5QqeQLWfbEDSVDQ/SjCJQf16QsCA/YJAL2gEQOfFvUAWgF1AtwFowM39mMBtumRAl8MbwN7fpD+C5q5AeCUUwDXGCMGMxU+/6kM/P/BpoUB+UAbB2ucDwX3vnEBkSoU/LtyywKRLwL/8d4O/Dzk8wSZbVD/eDpRAXUreQB/uKUGLIkBBIh88v+J5BkDvBhPBn44SwTDQh8B554RADgcjQOus6D80HoZAzvrVvw==
48 炝炒肉片 wvF1wAiRMsAH+aI/ErFPv0o1+b9f3gVAFI/XvkK3K8DZEC1AIU1NwCYa7sDsG4o/5BBhwB4vuL/4PdA/hdrKwOWN0MBuSgBAIfD0QO0RUD8ubM4+yYZMv/TrrD+cJhM/3oo6QRNRA8ABP1NAO2MwQKzlm0Bbsnq/TTWLwM6W10D4e6RAYR2BwP4t3z+SMMxA2pBFwFTHuz6uXYbAFSEfQMgiM8EEfKjAJhFtQIC3ob/n2Yi/1WuVPvn+zz5w+mk/12AUwda8ED+6a5Y/uRvTQMeopkBt6gtB+CGHwF6SPcDzoZjAPP4BwUZYccChrIhAsR01QMskFb9BF2m/pNtDwA==
49 四喜圆满 xF5sPiItVsB4S1g9sitXwGBXZUDYyOE/rrAGwD0ENsDvindAQ3RJwHYmEcEh2G4/At6dwHjqW8B3cVtA6KXLwL/n4D94ooRAtAMaQbvHlr9llRpAPiyaPht8yr9uHlS/PrpqQRfiUMCYCrRAB0qCQHuXlUB36fC+aEHFwOjgKz+lFM9AcOeCwBrAUECyyQlBpuFowPkogcA+qp6/I8V0QKKq0cCjgKXAHsDev5I8vL9GXD7A6w7CwAeEMEAENApAC+9bweSFAEBOAIY/C9WkQHP/9z+grxxBSdSsv+DwUUB7e0bAFMOPv/CsubyAnbFAr6aJQOWKVT+HmQ9AnSyTwA==
50 学士的书签 vBAJQHtsxcA9+mHAsntNPwtO+D8mcSzAdt2OwGW/ucAjWMZAhurCwJQKG8HrxDDAm1+qwFdkp8DcDJxA4EKWwO//hcAUO3dAdYwfQZ274MAqUrtAS8uOP8gVUD+D6Iw+qwNRQYtatcDgb/9AY3SkQGJql79un5E/oBXHwNJkpL9ondBAdlvQwI87lUD8dhBBswaewP6+JsBxroFAFUdrQK9pncCfoGzBpMgqwQK/ckBxTRjA91tFwL7YpD+KuhlAAMQKwdtv3b/2OiJAvZO1QJbkE0E6+J1AOdh9PzhACkBESp/AgJn+wOJ15r/OL+VAZYu6QFsIZb9tP9hA8AOfwA==
51 天枢肉 5lFVwM5dVMCScINAALg6wDBqxsBkWoA/5nAcwPUlCsAAVyJAgWpYwLcjAMHsRVdA6zf3wHZt1L/sAjnBwpkEwU+nB8AxLT1Avk/qQCW1M0BArLM/BKyowDU73r8dgJm/vBaHQY4eX8AO6GtAI0NnQHmjxEAlxGo/soTLwNjxA0HWhcVA9JxNwASu6bziO+ZA+6ydwHZsAb9bc2bAvnnjP2nHgsENfmHAImhbQADOAsGmBVq/HVDewJ0QNEBQe1c/Ln6AwVRTH8Da1AZAIm2eQG2InD9Gf4BB/qZbwIOM1j8lUUHAoKvPvzyag71Er5lATeeLQON3bL8FIwzA1YBIwA==
52 蒙德土豆饼 VOmpwAzhPsChlxZA5jMswFE4RcCYEx5A4TqLvwEvHsCkIEhA1YEzwN/cAsGAYpU/f5W1wNLhlL9xS3TACQT5wNgBML9Vm09AsX4JQcXG2j83mUA/9TrWv8heGD82Dia/6dKCQRzVNMDl7nxAN7stQCoK1UDSLog/LeGuwGjNc0AQlcFAkaqHwA7qBkDrD+1AIJdjwKireb4EjUq/GamhP987EMHTQZTAjwB1v3rmp79YrXS/5gMLvxoKuj/8WdK/h3N8wcY2ET/FmjtAszUDQaXjD0DIfxRBlO+dPz5TTz8p85rA6YgGwUDR5r9yu59AfBZtQBuI9z8b8CrAcYgqwA==
53 特工祭刀 /yGFv9M1jMCPVre/DCKiP5mWML8UrGw/uZdTwPkMfsBeSZBAkrh3wDT/B8EBnTw/M2uowB96NsDyGbq+7eSUwOlL078I0ohAamwFQaYAAsD/clJAcfMIv0K6Ar16u1G/ApIeQYLEhMCD7qhAQFeBQCqIHkCCS3g+Fgy5wHQQyUDRlMlAmV+JwOqUMEDRnvxAa2OUwGrPmD/X1eU+NWZLQOZGLsGDVrHAZlpKwOtgX8C4eRTA+aHEwKjo9z96T6NA1xULwTh9B8BElZA/eCJAQBSlNUBppg9Bi4eLwEtjrD/7Zx/AEwtgwMDkpb/Vy7hAqaeSQOnPRsB4D4I9c8iLwA==
54 千灵慕斯 zIXCP1DE8cCAlUm/vkyLvwQ8CsB+m33AOcS3wDm5z8DsHKFA3D3jwMO4DMGAuhzAEc/UwJ1eWMCAPuw6gQiYwFqlFMDOBVNA4ucCQXRBo8AMX9BATEtmPgFrjT+wAmu/U2GqQVvl6sDWk8hA7m+iQFM/Z0BVxQNASK3dwOGNUD/iPtpA9CaPwB+6DEBaCQpBWA23wA4fgMArmDBAUoOQP4ycCMHcQkrBlr9SPw+EW8DwO/K8+IbHv9BWtj+/q+69ecpfwR5bu791ZlVAU9jxQPwxdUEasWxBTraQwHevEUBgBgXBCP5GweBWC8BmBupAJvHLQBo1v7+FJ3tAmvFDwA==
55 感别之冠 6s0rwMqKh8BSl2o/HUOJwL/XVMCIAlXAfXfQv6d2HsBCDC9AfFtwwFJTFsEydzVAfJSfwLpKhsAOuV7AMUUYwe/OuD+0G5BAmI0WQQKFoj9dyg1A+1+8wAW/i7/1QLY/2xZsQY8VkMA8q8tAaq+DQIeP20AWyX+9lBHMwGrtZMDQbNFAz0iRwJZxhUCbCQpBtSJqwDqq2T8IU7m/nRlcQKoee8AIBy3ASromv6ETD8BQIgbAHPzZwJL4gkBeZkHApOpPwUbwMUAJKvg+UvI6QN7HgUCUMwpBfPJuwFKfDMD6Oo7AsmzCv+8ksL46prZATg+OQKi9zkB/H2BAWcO0wA==
56 盛世太平 efaAwAfmfcAkln8/7wxrPlMftT9cPJ4/FXilv82wTcAH1ElAZ1SWwEFx/MCkh1bAfTahwGgE+r8iwZJAmQYiwWmHz8D9/z9A0KAIQc19i79UChRAfAI1QAEuaEAxq4c/4mawQaqEa8DDpKtA6zhLQCqZ00BdukRAYJKjwLw7GsBEp7ZACRJ8wPAaIkCJvOVATYFbwPAAzMAhf22+lzmLP13fukCJctHA5PgGwWJNr0CqFTg/XEOywHQiP7/w6/a/8al+wZ73QT/nh59Aj+HlQF8c+0CcGU1BIKKMvUww8r44ThzBBQdVwemXvcCtXLVAOUKAQDflgkCyL6k/iYwnwA==
57 摩拉肉 AmlTwAMw8b+OO4g/RBBdP8JVb0AnXaVAJC+zPkDGD8C4vfg/vYYewAbzssBQhQfAYfUewMRl1b20iQtBB8OtwMmlEMB7Dsc/bIPMQFgVxrvoI/i9hmGtQIIEWUBVbIO/4XpuQb0Xsr9AjCdAThvtP+pa5EDtABA/ITItwDzULsC7UX1AvVAZwHVqjz+wo5dA84nhv/IemMAJYr2+ONn/Pq5C8b/wMu/AKvxuP12CTUCYyRe9NCisPaRwbb+ssAJA98g7wXZvk0BVAiBA4Xf9QMuMvUCmu+tAI3L0v9bqB0CwFLnAQoclwfTPi8ClJ1BAwiLPP+NMYb/t34M/cIxevw==
58 雾虚花粉 Z4ejv3KxbsCIdwbAO67OP78eR0AczN+/bdTxv6l5IcDhW0lA88pBwAZj+MDP94u+Npc1wEuPZcD7B2Q/wnZxwJUD/L+ThmpAtlP2QCPPP78koAtApYSyvw67mz8FI+I/YloNQV0qccB6J7dAEmtYQAwQP0BsBq28UTaiwJCYB78LDaVA6R6PwE1ZgkCeYOpAuPpIwHR8874kfDA/w3diQA0Th78b8fe/NUmiwNw0Cz6bgQ/A05L+wMzjAUAhXks+ppTYwBp4pj8xUOw+81GRPQadGECQY/JAWcXQwOZLPr+dN1vA/RuPwMgtOb/cy6BAEBJ5QEWCNkBjsIU/cqqRwA==
59 串串三味 gLzLu3ixk8B8Rpg98rN6PxugAUDATqs/auQ9wJrIhsC6vmdA1hOXwMLf9cAKPzbAxrKgwEg6FMAk478/G3/DwF+IdsCW/jZAr13zQD4NJMCMm11AFGY2QEKswj9cggHAH6+YQUEZkMCD3aRA1KyFQMwBv0DXWSpAZJe2wADQTcAtbbRAoW9jwARE0T8U/+tAjueOwC6e4cBcQ20/rC2sPyUyhcB/nyHBg9i+wC4MkT8gmnE8F1Y5wMCBh7vqjHpAAFRTwXFBJz4KDlBAE2PdQI8/7kCAUj1BbIEowFaMhECbVb/AR6cVwaCXuL9XJL1AlU6RQIUsC8ARPX9A0LoGwA==
60 老兵的容颜 1oYKwPZ8Z8AKxFa/1X6VPy7lTL+gPa89uakZwI2SSMAg/khAqTJ8wCtKysDl65nAHqpywGV34b+P7DLApzTGwF9TAcHrWJw/PjrPQFpdP8Cc+zJAt19cP25PL0AGUFW/yqFUQZBMZcDB2YpAPiUvQDjHJ0AW3Y9AkdeCwAhIGb9V+n9Ailh9wEuC0j/wArhA0CNzwMYxqMCaJBdASQlvPqIBHcGgj2nBMQgFwT2JAEDC4Io/02eDwGQU4r+QgaJATrIAwe4ZV8BLZqNAHUDvQFgqx0C4jClBhv0yvxaRUkCUQbfAyqYywcI8/b9JvpJA5mBhQI8Cc8Dwno1AkUnJvw==
61 祭水礼冠 Pu8YQTYRPMH5LtzAVrbrPxxj1sBkrUzBSuQbwVU6MsG4+zVBbgJEwf4sfcGK6RRAqNsPwZ+vM8EIVrq/9gUgwIWYAsG6PO1ABlF4QWMOQsE64TNB/rPrwHzAEsCsOg5Ag2qCQRhMHsF41l5BDc4GQe6BOMG0xn7AJuYywRodnkDuPjlB4FpgwS5zEkHKyGtBQpANwehXl0DcbkhAKBkQQZRNTcGQq13BVV4qP0AoNcCKHN7ADnt4wJjx1EA02nnAmsY9wc1VcMA4t22/RNljQJ7PXkEy2jtB/JejwFjFDcGdE5DAnOP3wE6rTj+u2jZBeBYnQfEtib9YPadAOuM1wQ==
62 灵光源起之蕊 ND44wKLqYsD0Une/0P66v62H8j8ATTu9GowDwJlOG8CDS1NAb5xMwBBh/MDHQQLA4Gp6wBpGFsCDXkM/RwnYwChq8b8KtlRArsH2QC+qOr+TOiZAtyyMPSiC1D9CnC8/E2I8QTyyV8D4jKRAUYxOQFL+k0D1bBhAn1SlwHiyj8CSkLVA3zpWwMa/REBicvZAQ6FcwC5qT8AyJgJAjhW3P1cUEMDYzvDAEnSZwKDIvj8OtDi/n+MJwTDBWz+isAJAlzwKwRGzEkDKUjhAZpl/QCNvkkCnOwlB3mGewFWd8T8GebDAlJydwDR4DcASGq5AVGCAQJnIzT8dg01AWTJUwA==
63 地脉的枯叶 9aqUv0xaqMB10knAs31Dv/xHt0CwsS4/FXptwGvjecAZrqBAktOHwNikDsEwefu/4b2TwBWdW8DkGvRA3RKcwG5W3z8yJ7BAT5kRQXcKPMCg7Y5AwmU1QN0t3D+tELm+0idGQSNOn8DPzuBAz0WiQG3ilUA2pow/ch/IwAIOHMHs09tAK+6DwF9XoEBlGQ1BzxWGwGhMe8BCon1Az9wgQADen0APCL3AjP7awFlQr0DZkQHADQPxwFqxyj8o9ZY/Bu4MwQK/j0BuJ90/RLwvQLzUkkB0X5tAcBaGwAANNEDXY6PABxihwJrrB8B0BdxAXP2uQN+XZUAe42dAmRZuwA==
64 遗忘的容器 OP3Avutwg8BPUU/ADCvBPv6BqUAkOx1Ai384wFUtXsCwPZlA3FBQwMh8BcGoLPW/C6eRwN8cS8DcYMxA1T2VwMTwK7/5eIxA/0sLQUveZ8AVMGBAyMBMQAykKj+PKuq/baQTQWfBcMB/TctA1y2WQGjiDUBBins/EDOqwFSDl8CSn75Aj8iRwOiDikCYIPVAJAGEwDxAUcCGKH9AvVY4QDL0aMDEQhrBr24ewVTZc0C3vgvAEC/SwNRNgz93nvpASxLxwKVP5z+Gbsc/MpZaQENQgD+tuVZAVwzov3nlnUAJFwXAuyxJwJAgDb+8O7VA5/uQQD4gFcDTP5hAm7ZgwA==
65 丰年有余 rQPmv3W8XsD8Tac/zbrgPyzCXj/Gv6c/v9jQvxbaV8CvASdAW22HwOmq1sCk+f6/8XaVwEfQg78iNy1A/lPtwKJBib9XzBBA647UQNvITb+8jus/upceQGPlEEAiHX2/TTyGQQyzgMCDgoxABG1LQCCo1UCfbjZAcIWlwBd1db4MnKVA4QhDwEJZoj8IZdBAmo10wM7iksAZy62+X8inP2q7AEB7FJ3AC/oGwRAwzD/hPps+HafIwNfPv74A7+8+bSNRwaBk1r/BPmNANTi7QOoJ10B8VmBBQNcFPVMlxT+OZQDBZuf6wIb6bMCKvaFAydtuQMVvQT8HfjE/Q+skwA==
66 爆炒肉片 QO25wHe2cMCl0IZAWIhywLfKDMGWhwRAd8XGv5ZXBcAn/jhAPXVlwAfsAMETSIdAL37cwH6P2r82kRLBSasHwT2WDMG0nABAPQT4QGT5hEAoCZ4/2wa0wIyfTr8G9jg+lxCOQU3j2L9uIgxAtOBJQIWn6ECB2pe/+DWlwEz/jEDQNb1AnG1WwBY8hL6Wzc5AddaAwNq0VT+zkaHA6NZqP7oYo8FBddvAnKNlQVHS4MCW9zC/R7MuQBNEJUBMF++/RDNywXWoSED2M5g/ozbxQCero0DvFDVBJGr9wP2B8L85SEHAysbBwLi20b/0+5JAWJhrQClhEsAjKHE/VdEQwA==
67 学士的镜片 rnN7QI9lIsFcA5DAVLEJwEjwdcA0qQHBJCf4wCDaEMHo7RJBEI0jwQqyVsHYB3a8Y6LxwBsr68AgfnNAsBezwGIIo0ASMvJAZhlWQTSQ+sDomhhBmg5dwL6uJT1xmY4/2JyJQWRFHcHOwjdBbyz2QIcqjz/lLqE9OPsfwQjYT8GRvS5Buf8NwbYyBUF0qFZBlovpwAkypj8UtaJAeRmOQJ7sW0BjezXBhwqev6aDtEA3S2bAUHLtP1ffj0BdKzHBCQFuwbqVE0BEGPI/0fu/QN0HV0ERjoRARrmhQJLrHcCOkBjBd3XawFZE078tnjNBEewVQW98+EAL4NtAW37+wA==
68 猎人的胸花 SH6WwFYtecCZLv2+gImjPrVSYUBQatA/VM4LwH9OLMCsOGhAi9NlwMnBAcGio4LAfVSqwGbqFcApUQxAstkmwbAiQ8AjJnJAXa0GQSU7yr8VWCdA3Hg6QOCWKUCjw8M+PmZoQaaXhMB63rxAiDKEQEIVv0B66YJAiMWqwFIROT8BwLVAJy1wwFsjY0DMkOtA0u1xwGpqqsCZbEpAfe6XPwxvBEB8owLBwOmcwdxvZ0CIDgg9I4y6wI60Bb4c73hALQc9waDamr7XG59A3k/cQOMJtkAw59NAGuzTP+R5LEAiW73ACBkiwa11lcDr1bRAKHyNQFniST/WyDlAoVo7wA==
69 北地苹果焖肉 E+STwDatM8An8GhADlyFwGgESr9i0jVAM1qcv4TY4L9W1as/V/YswGEPu8BYU5q/I/23wMgQJ79UH9S/hn4AwRBfGMDnGOA/5tvQQOvP3j5mX7I/Fw4RP8xNuz/+7wO/LMG6QWVvJ8DyiSxAVnICQEjt6kDj2RlAZ7x0wI9FOr4s34ZAtxPrv0wQSD5rEptA1KUfwGE11MDBcBm+eCIYvx7e2sBuixTBYIK9QN67TsAIrW8/xxJjwCBgJT/gB5+/zMyawVVgC0BPRZNAszgLQRrC4kB3RWRBQRhCwLDQb0BwL9bAAbgjwYwKFcBlu11AcpskQKV8MT93l0U/qR6uvg==
70 侦察骑士烤肉! rFJBwOf7pL/vnpQ+USb8v0QNej8OxqBAdO/pPlu3AsBjfxRAvsixvyrazcCQWIM/zEUPwKSj/72k1ZpA8Mt8wK4RmL9jkRNA7dfnQPfZ8j5/pCu/v9V6P5DDJj9fl4y/l0X/QIBeNL9QvCFAdlERQFBLikD3G5q/VS9lwIBttkDiC45A0vhgwFhNCkCW161ADysewGByaL2AJXDAnHEJQDSuQcFbJpbAJluWQPr/974uCLq/iDYBP17GCL6mbFlA/oUMwcX5vj+FLno/YvnSQDMPGL/bktdAJFojv/gcbj7LajXAtSedwP6m9b/TnlBAGkQAQDm1+L/ur4a/AJcNwA==
71 杏仁豆腐 WkUMwLq0K8CHBQJAckBaPziBlkA9oog/9wQ7vsHo/L+Rb5U/MGxhwE+XiMDkE7XAaWVOwMBMFb/eVNdA5r7vwCwur8A0z7i9ytSgQEYAdL/c+rs/7SKbQEA/jkBn1XA/DUmxQQbILsBlzz5AQPOjP/Rp+0AZsZZAoGUuwPTk6MAncSVAFNi0vxbihj0ETHtA1CXUv+4KLcH4YUo+F1eFv3T70EDjDxvB7p/BwJAunEDPwh1A9rqmwHqsCsBqS0nAnC9pwYAUqz+aKr9AbsX6QCIqTEHQBWpBVNKPv7gFL0DQOijBzr9VwfJknMCiHVpAG8IEQEQwLUBumYlAMpZPvg==
72 星蕈 QIppwJ+277/KECu/ShiYv126pUC3MjVATE7ePtxNMsDMuSpALNwrwKxh2cDP35vA9fTSvxS9M79KgwNBJ33rwBv7sb8FTdo/RHf9QE6xw7/GlNg+5kucQCU9RkAaaKo/S9FHQSSQDsA2gIBA504RQOH+xUAQr0BAbPpkwFrHLcDdVYtAvy5ewAhYPkC508BAp/YNwLOh1sBsDxm/+MN7P0iga0BYIgrBVKAnwUgu/kDoARk9+p0BP9hl5L/KRd+/jpAcwZiDCj8YM5NAt/IQQcUrtEB7eqRAu7P7QA6nH0AEtgDBzztGweAYsMCUjo9Anz0gQDKxkUBCJ3NAaNL5vw==
73 地脉的旧枝 7uiUwGvUPcCmiei/MqaLvWSEZ0Do3aU/ga/Rvw9Zdr8PmC1A2GOWvyfW68BsS4E/74VewHhOEcBtYQG/z1qXwIo2Tb/4BpdA2m/pQIqsD0BDiH4/OX6Hv2ABcT/D9uo+OxjTQHJENcBZU5RAsYRSQGHy1EAyt1o+W6eZwNxd2L8AuKpArMZJwPUihEAaqdtAoKs8wGqlHT8AMgA/25odQDX/k8DsOc4+lw8fwHTUYr5qmADAA2b/wJ+oBkBuGjRALdHNwBLGPEAUNKE8LNxvv0jPHsDtP4pAzY/1wIwqyj7j956/iMKcv9jdEb8trJFA8Y9hQHZjKkCWl7u+D75ZwA==
74 雾凇秋分 lNcUwL9a8L8E6Bq/Oax7wGzYx0ATXbNAE5+Fvwo03b/JajtAb6y+v8r63sBX4+q+G3GKwKbWvL8nOwlBlGyzwNUHGkA2JIFARFMDQYdVer+WRJg/3s+KQHiEE7/3BBLAP3lAQQlYu78PqXVAzUZMQLSQs0C+/nO+bpR/wIYasDyK7KFAA00uwASETUCyf7xAVMEawL6vh8Co7Qc/jH8ZQK3A88Dx1MzAOhYMv0SfIT5U9fK/DqqHwMn+nj/IoOJAd8JKwb8kaEBKjKM/iW/AQI8IWr8N1bBAttMKv0BAn0CnlOG/u6IWwCikvr+2dmhAgDYtQI3F/7/pQdk/MSwhwA==
75 鸡豆花 w1yGv5Y/hMBrlb4/xoi0PnxgkEBQ+PY/Yx8UwHZmVMAeWyNAxI+JwGpercAEVZLA5sKlwNzxlr+AoptAE0bPwFivH8DCPMA/dt+1QA7pHcC24ExAw8e1QJPoUkA+jra/hn62QfRdg8Bys4NAmM87QMyo30DQZHpAQviBwM4P9MAk/IBAJ6nKv+BKDz4EL6JAwHVJwKtIIsGrsENAA9RfvwKxNEDIIjDBhgCEwE70/D8DrOE/nmi1wGjYV7//Jo4/kmd8wdjYK0Cer55A9JHtQCiiOkEOoVFBBLdzwF9RuUArfPvA2w41wSR2Q8C4IpFAW95hQN7yJr+j4IJAPiRAvQ==
76 黄金时代的先声 4pvTPtN+s8DI4HPAyAUuPww/vUAQ2oK/OlRwwLPspMCIXKpAno+zwLXAD8FRRaHAJwp3wB5WbMBmER1BniGzwMCJaL4qYYNA7y8YQdtpvMByOaJAnmaQQPhCNEBxQQU/q1tlQcryssAimPJABvyZQB/lCkD4sCRAfQPMwH04psDmr8tAyKGywML8qkD69AtBdhyIwP4vwcDrnIlA8yghQA/rt0D69SbBfO5EwSR9zkAX952/pkS+wMQV/j3CDqQ9O6IjwQRw8j5523JAEPS6QKz2CkHMIAVBxeAwPydJBkBe6P/AojAnwWYQWsDIO+RAOx2wQGQGJkAAVY9AcHF6wA==
77 夏祭游鱼 IV6KwLymccCa7j++4oG9vRenQ78aCS5AMAUGwPl0NMCHtXRAJ20/wHHxCcHcpMA/mcW9wAIAzL/IqZq/z97kwLXOf8B6M5ZAi5ELQZEcyz9u7eo/DSS2vzRCGz9+4Qe/O1pKQbjMUMAD4ZNAlg9zQIBIx0AuLSe+f565wAIxjEDlAdxALeB+wETdNEBwgfxA3zGJwHAhNj4PxhHAa2ggQBt/3MDnG6u+1wqhwImCl79Xkr+/f7bGwFLtuD8AIYNAjt4cwSzccD+IZIY/QMV9QIwZx74IugRBuWi0wPjTUr7chmvALkSOwGKBJcCOx69ATEWMQKrzrD4lO6C/OLl1wA==
78 祝圣精华 WKRLvjCiz8CYMlLAebQVQC+y1D+NzDHAABOYwMg8usCe4bpAIKXFwIgfIMEtBTfAkJaqwM7soMDFTKBAB9zWwBXQS8Dr2ZVADK8hQf9BtMCIw69ATrQFQD8tCUC+mpk/q0FkQfOjy8CWSwRBlz6hQMEQ3T/IItI/DBXQwMzlOb9sb+pAIYO8wEAzn0D9NxhB9jqcwKuv2b/3hYFABupbQKqxBz/85irBkHxtwWkKWkBIsQ7AHzmYwET35z9OMBlAtZIYwRxzSb8jhipAkVqwQNDTNUHfDKJASIDAvb9DIj/bRL/AKGEwwe5TjcB7H/FA3AHCQFStfT5nqpFAJW2jwA==
79 岩港三鲜 okh4wBzaG8D0I4xAU0ucv3shuL94JYe+bD7Hvka5rb/gS1M/OhlFwI2nj8BM6wXAiHedwGB4Kr+720fAWdgDwcIRxMAls7y+V1GLQIUJDEBvuHg/xKAnvlqWMUCbn4U/bXmuQcmqCsDWXPQ/Ii8sP/3cDUGzvGxAAFMvwIEWksCJ9DZA6QtLv0iJ3L9rT3pAKl8AwMxB0MDUhpa/IILFv9FMh8AR6xXBmAAkQHMeor/h4g9A5B6MwDwGx73TVH7AOSp4wXm8AkCyEZRAv2vrQHCbQEEGbWJBXGqPwPwcHkBes/PAXwUhwfQBVMA5cDtANVn4Pz8xmz8uzmtAYk04vg==
80 满足沙拉 /GejPrr2g8AH9oU+5XHoP2cHGUBW1am/DbOZv+CpecDUnjNASzmWwBeUwMCDnpLAY3ZfwAKH1r9fhPRAu4rBwIWwOcDeHm4/1J/QQGIjR8C4GSFA+CpKQLo6WkCMjJk9Cp6NQQDEk8BftJZACbdBQIpss0C7/mZANWOLwKRLmcAMHXpAWXhhwIs+2j8SkL9A8udSwMdayMDnMdY+fI8IP9ZwbEDXJibBgWgiwaVLyUD8gp8/dlJDvpaCvb/602XAJx86wUDym79DP5NAJxr+QOmfU0GfrilBkB+CQEZxcj/UwR3BS806wWIke8DWjKNAMKpiQBf2J0Aj+7VAIsL2vw==
81 四方和平 EuFdwDjYhsCQxYE//SKmP/Qyor+CS7Y/PT/rv1tCYcA2LUlAWYmYwNHu8cBhKznAj3mfwGvixL/WzLQ/7nULwclvu8CDmCFAwnQBQQV+g7+rmhdAHwv2P5PoREDaX2G+mn6tQTZWZMAZY5FAubVBQDJO1EAfF0BAZyykwKFS6r+7I7ZA+Yp4wPVR9D9K391AAJlhwKLTqsAHl9Y+HmwAP5wKy76Lwg3BjO6kwCtxG0CqVS8/IKvQv6TkKL+f8RO+ixeAwcGoJD5r4pVAiV8EQVs7A0Hw6FdBhjs1v74JVz+CMw7BMzNZwf5ChsAWSrBAWgp5QHSZQj4DZfA/VAcBwA==
82 咚咚 NvkxwA3Ujr9Ylxk9FmBZwJogFUF7uLRAkhi0PdOZRb9TKzNAuMZGv/Fv7cD3xLu+Rb6fwHYKKcCTNgNBm9LjwPObsj9eQYZAQWQLQbuscL6sa2U+s0acQETYFL/LM8W/DCE0QbZdXr8KFpRAkKlUQOeWqkB/c52+MeRbwCgFG78Aq5ZAahAjwEZGPkBc78RAmxXwv3EOkMDyAwJAxzhJQJsBhsANmLTA5vQGwbfNqj9NpDjAFpnlwM2EGkAU9epAf9Q9wQSRvUB9wBE/i6GaQN605b7hCKY/yJAyv+RsrkD/F+A/7rQPwNx1279xZ05ACQoRQEwtF74xVEtAbW9DwA==
83 三彩团子 Z96twMzTQsCsjPY/G4PMv64vxj/RLXJAuTq1vxqK7L/cTtg/cZ0lwEJlusBqmynAfDWlwL6ALD5nahrAXAjzwBg7o8CNARBAhCbCQLnraj9gQ60/q+/jP2IHLUAXWpC/kCGfQYC/NMCdKzdAQNEiQIYR+UD1wUxAbaaHwELZpD8IDY9A3pHsvzyMOj/WHqdAb/lNwPpE08A9nZo9bEQ1v6yzRsDhYqnAuVYxwLo9l77e9NI/sQakwPDAjL/YAz9AvxJqwSwPqD/v7ZBAgT3SQAMfNkDQvU1BtUObwHzSUED28sPAM2g9wZRCJsC8mohAsl5SQGXINT+YoKu/SJJKvQ==
84 混沌回路 BgRQwG95JcDCxi2+wQk6QN+AtEDGvWlAbMP8vm90DsDVyUZARA1EwHpi4cA6HnPAq55lwOwdrb/mLbxAyYUFwcWSpr9BGVBAGsv2QH8Vg7+UwIc/gLySQOfyXEAG7Vm/WolfQc1KNMB3Z6BANqtEQHqlmkC3CFJAKfiQwBz2scBLsJtAQxBRwJLoM0AEEthAXkA1wOk1u8BJm0JANK6rP2sXQUFy+orAX/OcwbBWCkG7CJ6+iIfzwMhOhL98Ybg/Fe8nwRzx+j/7vndA1fujQPuRAkAD8NVAzPADQJa/PEAJyM3AVH77wHi5acC5cp5AshhOQBuuZECpKNc/E1ItwA==
85 连心面 duCjPnh4TMCorak/Ggkiv5timT9UU/w/zk5fv5tlZMCE5TxAjLaKwKPT/8C+Qpm/HzCBwGmwE8Ao16lA4e78wMrhCT9rlCRAujsFQQ2wx79w8QdAe9fFPyZmhT+W4XS/MO+UQRiRRcCRopxAWVxzQMJgvUAI2fI/ity2wMNe+sDhq7ZANzZdwO7iwz8UGfhAX2tawHU438DEwBG/5075PwcJMkDEOwnByzyawIL+VUCecFe/aKGdwOwMEj+gzPK//5FvwUbbLkDq9E1A55/bQL/fs0CKAVFB2bvQP/rAZEChqevAK46jwGiErr8eS7JAGfRuQF/b/z/NFrtAalFSwA==
86 黄金之夜的喧嚣 V7MFQMhXs8CfTUzAU5OBvxmZmUCYedO/IBOKwG4EocCAJ7VAQNitwNICHMEIZEHAUDm6wOLYm8DibPBAWSWxwBpa473i4ZZAuaMlQfum1sAg96ZAZVw5QPijZD6eqmg+6EdhQf6EpsBFDgFBl56rQLhoCD5yOos/XyPRwBLnqz0CDNJA6Oi/wIwPrUCElBBBkK+fwMrHi8DcADtAVmGCQICoyL9oXDHBjn7dwM9WVkBzaiTAfdUvwOCC1j80G00//DpQwXm+bL84FjVAiSm6QEAS50A8iv9AUZBwQLFGAEBAH5bANU3awAYT7r+vYN5AOXa2QHCYlD+a6I5A+I2dwA==
87 黑晶号角 Ivn5vo16cMDVrUbA7rzdPeoQ4kBjoSFAu9QrwOsNZsAXq5FALUxVwEK+A8HCWwvAtf5LwOKwGsAIV/hA6h5KwEQpi0CFNZNA1c4JQTYjPsDvvUNAWBFyQOZhxj+S0lq/3yX/QPZGaMD0srhA11aEQOIjDkAHBnU//FezwEWrlcCMG8BAm4iQwMr2lkAIDwBBohppwPDBVMBMunFA7Xw6QJRQGUCJZp3A8fuTwAaXZkDQ9yXA4BL6wC4tcz9YQaA/xooIwSniK0CDWug/5dAhQK+D+D1LOthAKMXTv0BVXkDsM3TAR2p7wMRofL/Uz7lARJePQKMdkT+YXlY/m6B4wA==
88 督察长祭刀 1/6Ev2ItgsDJmCPAJH8FvX/7OT/EkRpAhWdVwNeKc8CJj55AMxlRwMWtC8EXVLU/b8mWwCejNsCqYuE/32o4wAyYCEC265hAuXYMQU24BMBuvU5AXDwWPmRr177FpM+/mOnHQIFSaMC+da1AxS2LQGE08D+zse6+ZVi/wMqXwT8EUtFApqGdwKLddUBKFAFBSj6MwIC4uT+ACfU/0HleQLbQP8HN7sLAhTZXPwqeQ8COwVXAQrbuwGr4IkANqLZAKvzuwNYrGz/9urU+khIVQN9uy75Df9lAVGOywMg+J0ARXmS/uFD+vsgZZj3G9bRA+U6XQHb5e8DKQJg+03qKwA==
89 来来菜 YpgJwG5AQcD+bOM/yNvhPm6y1kBe9m5Anbmjv/b7+7+8nw5AsyAnwJANrMD0yGfAtUO5wJZKqr+ADMdAh/bwwGgNSMDf7NM/oPO+QC25k79CDRRA9rq4QPXbCUDSo2G/AU+lQcRqPMBu8mxAB9wwQIO76kDOiE9AsHBZwPJrq8Ci/WpAyGJ4v2XXuL2lLJtAckwlwEkMI8GOL+4/omz7PaEq4T9FTBTB8qD4wPm0DkDVdoM/tWkUwS77Xb5ebGhAnSVowaq4D0AX04dAVCrDQCZ3B0HLuSVBWhJUwGCb4kBXT8LA8ufDwIyJZsB5+HBA8PxAQFULvL8PeqVArKIRvw==
90 「堆高高」 0Oq1wM9GTb8QQvw+N47RwNhdBEFzd5pABEdAPZ3OU7+SifM/ZkTbvhJRycDCAl/AEqFwwCzIij2AQp1A0kEAweuEz0AgXk9AwqnjQMXjDj70Vny+0kKbQFARrD82Lwu/vnAUQcB/8r8e129AUFcxQAaUBEE4M2JAuA9vwMyvgT6C03xA5rzDv3JbOECp9LJAnwEJwNmRycBN5ck/RcsTP0r7mr+6F6rA9lUowcCxNkCMUKy9YK+dwJ5eWL4Ce+c/Fiw6wdFhNED6xYVAf3TtQJoR2L6u+zVAHp/jQIje1EABQj7A/l2pwPQyT8DIDmVAcjYhQJT+nUD/Cd8/h6KYvw==
91 大英雄的经验 /bWQPzAhcsDBppDAEkwrwDRCoUBwSOI+aOl7wHYXbsCdIqtAIfArwKIHGsGfofc/ly2XwPYakMCItRlAzDGCv9n7uEAWmcxAZjAjQRofW8BZolRACb2Rv/q+KMAfRZy/1X+ZQHmKcsCFCeRAmLidQGNejb9ivRnAibrJwBgYxT8Gt99AbUjFwOC6zEAB0QtBzb+QwOKI1D/pnClA0vS3QMSkFcEX8CHA21orP6mtHcA/hLHACcELwW/VhUCg2ZFATgv3wBnCuj/S+6K/ar4LP1YIr8Aw/69ABdQ5wDYLJ0DTab0/wx6eP/twK0DAM7RAghqhQGhN5j1U5ig+u0DDwA==
92 祭冰礼冠 lEBtQNCF+sCqOoTAzATTP9pUNMCsTKHAuvrMwIAk5MCpLvFAeTH+wDwrQMHZO8s/hSHXwODd38CYWJm8eKdwwNhahsCi/MNAZqM9Qd0Uz8DARuBA2IddwC5ng79a6Cs/vHlHQS272sCR0BZB94fPQFL0Z8CLePe/wmUIwVuflUDEsQ5B1RsPwXh+yUC0YTNBYkzOwJzJ9j80/ew/EqDEQP8iMMFNshTBm/t+v1VDQcCzCZfAnneXwG6oiUAIti0/gfwjwXB8G8BKLhs97C5IQHF95kA91CFBJsOdwCScYMC8pWHA8N+twD72bL4rEAdBafHtQJWSxr8zUOQ/YBr4wA==
93 黄金剧团的奖赏 yFstQBTBmMBp0f6/Gpf3P3xIBb/vsF2/gjJAwDSKrcDYyqJA31aswN5YCMFLNAy/AhZ7wJKQbsCIfp1AgNpEwLfsJD9DWi9AvjQNQZHeqcCEGINAx6wOP47sTj9UpTW/2tJLQahHjsCQO8tAT39rQBhIqz3vagc/hJ21wBywqcC7Y8RADiu+wOOMUUDLzgFB+V6EwPKRiL98hkFAO1NFQFg+ncCYk0rBpMpiwO02tz+hISTAxVOQwGJM3D8UcyQ/oWkrwaal/T51kfI/gg++QC3c9EA0JAlBJjgJP/7+/j8+tb3AmCCbwF47Ur+sI8NANLCTQJTqLcAWnKpAiAqYwA==
94 「缥雨一滴」 ncPxv8DtcMDoAZ4/HzZGQEKqQ0C6lwJAHVK+v66VVMD4Uz5A/o6VwOhz58ChLpjABZKewCrfxL9axJhAKqkowYNAqMDnsRlAwS71QPUR9b8nTwZA1P6ZQLy2U0DJJYS/TISmQYVkg8CqQ6JAxYZUQGcx30BVJJRAtuyowJCblb84hqtAp0VMwAxq0T+sxeJAOEp0wFstA8GD5ta9JwWOPzC480CMc+/AAQOOwdPh1UDkk44/YPSMwC6877+s8NE/l9BtwY2rH8BAerRABHwGQWy1BEFusFlBypmBQKbDEUCe7RzB2e0ywU7Pq8CcHbNAdqZ8QMvbrz/4BSJAs4IiwA==
95 昔时应许之梦 hLCYQP59LsH26+nAptQTP5sCqT9VtEXBmQEQwZQAGMFxBBVBYz8cwV+0acEPjBa/DdbiwK85GsEK+j5AmVNowM0UkL+D+QNB7JdXQYROE8GWTCZBiq0nwBC1Rz+lrJZAB0NdQZgML8FQQ1hBJwvsQGJNm7+cvP2//IIaweWRQcAn6C9BA2YQwaX0DUHZHmRBp3jzwK1ohkCe/KtAKcfwQMbLmD9uh8DAhTccwOo9wL9ZtqTAp5AIwczkw0AbGYLAedgFwV+eAUCZOnS/K8mvvrWKg0H3W8FA6OAhwXGlksDesrfAvEITwdwvSsDBwDRBfi8cQb+yu0BUSrVA4jogwQ==
96 学士的墨杯 uG9xQAA3ysCjGp7AAaFeQBMQv0BYlVe/tqWxwJ8uvcDJmM1A1vG1wOYaIMHaSf+/hL+bwN09rcDRrwJB5LQ0wCOQyr+iU6hAUggjQS712sBK7MJAuKxxQAA/ljlmJCi/9gdIQZiOtMCbmAFBMT+xQFNJ976/nba+XefYwONarT9v7+1AbeHKwCgFq0DIvhlBrN6owGaDQcAN+m1AKCOiQDMwhsBsjg/BHX3lwPVnPj+EnXXAajHUwPAxFEBQvtpA6XIewaAAzL9xZUw/OnshQNpfqEDVCgZB1ytWwNJzOEAHoVfAqpq0wOTTmb9+QepA13bGQFQeoMCj4VxAp6a3wA==
97 海祗之冠 +JStQJ1G9sCW4WnAeLMQQFCBy8A+kKHAcInFwCAR9MC/+vlA7YUAwXJWPsE/vHNA7eXNwMhk5MBlIc6/FLz/vxBMpj4MHa5AVHA3QUTfv8C61uNAWZ6xwLEp3b9OEf+8OnJDQR6p0MDYzQ9BrTbEQCBXNsB3FDTAw8UFwbmFbMDEwxFBpQQRwQYrq0CkvTJBkMjFwFG/i0AmNC1Aq8m+QEdcPMFAzTLBn/CwQOhWc8BV5LDAJap9wOzyskBMwcu/kkEtwSl/Rj9e+oO/FhM6QGvd1kDXeBNBx5iiwOXWvL97yVLACFbVv7RHxT8GXwZB/6noQBnMWMBDOpdAzqoDwQ==
98 奇迹耳坠 QvMjPzvZncAlsiXAKoKPQOMsiEDQLV0/Cw9cwDOMecAK6ZlA0nuTwHHVAsEqz2LAaXCxwECQSsBSIphAiYfdwIf+6sDa/n1ApYAKQdC1j8DMZ3xAFD9gQMqcqj+H4YS/RtZuQVg8hMD9Ps9AamWMQJNP5j24XeE/B2uxwKrGwkCprbZA+3qkwKjtbkDOePRADhyKwAH+lMDQvNc/P9VHQCwzn7+SLejAc5uDwdZuJkD8zm6/IXrWwC/1lb6gsRZBH1QmwciQYcDQx0ZAm3aOQLUQhEBCFzVBYNEYwGP4kz8AX5rA0aj0wMr4LcANoLRAxceYQMZGoMCaSvQ/tLuBwA==
99 教官的怀表 3gpYQGZ90MBFljLAaAlnQPT1Oz998z3A4o+QwNDZzcBqEr1AlCzmwLB9GcHeNX/AakegwDAPncByH8ZAPcC/wFA7hsB9TFJA7x8dQejZAMGmh7tAMMAzQHQl7j8y0s48KC+QQddnu8DuUgFBB52XQGkagr+t2A1A5cLUwJbwrb92HdpAvxXWwK5yhkDu9xFBrhyjwCrvd8DMmG1AUHhNQH+Rmj+lc3bBiNc1wdkqk0At6tq//T4SwOB3BD/Mh48/EyFRwQjnMcDroXpAQTDqQKzhSUGIXzlBTo4rQESKhT/atgPBP/w3wQQgPcA0/PFAKT23QLnpz78ilqBAOeacwA==
100 守护之血 sgpMvwhE4cA2eoTA+HvFvuTvm0CTDV3AoCK8wGSlgsCLXcVABbiowBghJ8G0d7O/WDD/wGPDw8BojSJAgdfswG2W2MCCw+lAKyglQfI+ccCQochAZPJYvqCqbb5KWHQ/PhFPQYR1zMAV7AxBVx/bQFRcGEBSpXc+JvbcwKcxxL+szfpAEUCnwCd4y0AMiBtBTh2/wKASLMCuCWdAlL+KQC86WMApZ7nAkoUHwfBcFEDroR/Aq+mXwOlEOkCsEGJAQ5EFwcDUgD419mk/Sg5OPjm/nEDHlmtAdxiswC6mtr4EagnA5qihwIT1A8DHhfNAY7reQKPL2j9DOZpAiVmzwA==
101 逐光之石 4E2sQAQtBcH8j4DAd8pYP6ckF8A4vNTALJnPwCG6+8Cyo+9ARPsEwU4kMMEnrpO/OOjNwIodwsA0/EVAihdzwDJ4KMDa7JVAcsEuQcYSB8Hcv/hAZ16VvzAxtT3QyCU/OJxzQcQp9sC2QhNBkAq8QCK4B8A6Ywg+WEX7wJT0ND/lxABB9nD/wLIuqkBkhylBgiXSwGlN0z5p8OA/T0WQQJLWCsHKqVLBLkKXv9hmDj8kNTXAJDF+vvu5H0CX+/G/+R82wYolWMCooABAKsm4QNUUUkGUfiVByCKNPzsJ67/Gq9PAHuXywGv9zL+oWgpBbFTtQNmWsb/qU8JA4YzPwA==
102 果果软糖 sSaawCTDgMA08aQ/7WuLv9JFAUDg9IRANwI+wInrLMB9lGNAKWNGwBTj8sBmurK/i5HkwLawjL+1ujvAqhMFweXY1L8kPXtAWPjsQKvruD6CXSJABgAzQNBJyD9Z/vC/7sSRQaXfgsBiPJJA34J3QFRS/0BzlE9AJhK5wDDtIkAS9r9AwKgXwBmJzD+mNOFAxPyOwFYjosCyGZM/QjIuP6BuO8B/rWrATYIIweghY7/lQrM+q9XZwJhSPD/N5JJABftxwVFhND89gXhAJIDAQBu8OkChBBpB0Ad4wPsxkkBMjInAKrLkwMHiNsBwmLFAeiyZQFPpDL+Avvq/8KXGvw==
103 雷鸟的怜悯 QSPRQCi1FMHpSq/AIo9DQKXS9j4rkAbBxM7iwBq0AMFtqvhAoDMVwUyXSsHjF3O/q1e8wJ2mC8HruSxAZoRPwKnZ+sBQCq1A8AxBQTOUB8FH+RBBCV6+vxgXij6STD1Ar9aBQaziAMELOi5By2fOQKp/asCVnQTAuDcFwY6oPz+7/gpBRb8QwY4syUApxz1BlWLAwLJlnL+byjpAmmvWQJ3kwsA8zjHBoBNDvnuTZMDX+4/Azc0Jwa5blkBIDWc/CwsZwak/SD7mOla+O8mLP5Z5WkGQiklBXnxKwcDOTcC3DazA6ngDwaIHgb+p9hBBOsb8QOeuEsBf+61A2GIIwQ==
104 唤雷的头冠 GdtJv2/7XcC8oLy/s/JPQJo8ej+00zZAMHsEwKkOW8DpiXlApoJWwL5V98Bsd+O8IiJkwKoCF8Ar7oFA+XOPwESZsD7E91xAiNYCQdV47L8yIRFAUBMEQGpaxT/TaZq/pZswQZFGRsCGS6ZAildCQLIqUkAdNjw/VzmlwLTcz7+8C7dAvG2TwAD/QkAcuOdABthZwPrMWL/0c2w/ffo8QEnXisDKjMfA7kdgwDpxvb7RbRLAsm8gwRxkrz+eV6pAsV0mwVTtNz9YEcE/dsZtQIFIBEADiiRBksOPwNDa1z/e74DAzrIhwKwx2L/u3Z9AaxxnQCuceMCegoI/KEV6wA==
105 测绘员蛋堡 4Khmvi/CXsDwApq/Jj1zv6I1jkD3SytAm2LvvyDdVsB5koNABExowGQ//sCJWAbAq/mJwAsjGsCGsw5B3IvMwJterz985INArGMIQc3GDsC5NCpAmBh/QBpHpD8EcxXAIqFbQctDWcAGQLRAXGeDQDiWvEAyzOk/O5uqwMwlFMGQt8FAWvpmwF3EVEBH4ftAOLdrwIgPosBgyug/9zAGQE6mAED0+wTBOkv2wEe/4UA64qK/qOVSwLL9GD6KjhdABaswwcfeQkA5kCJAtrLEQBsMfkDNt8BAXFYXQL5vfkDh2KHAwKiPwPJSCsAGFbhA6LeBQC0thD+WP6VAmck8wA==
106 摇·滚·鸡! oKdJwB2mA8AAahw/f+F0PmDZeUCm9FNAV2xJvmhl97+NphtAsvoZwCKNw8DIoifAm+RQwJTJTr/UScNAH8PbwL7sWL/x1wJAfR3ZQFNZmD5vEhE/Ru5EQPmmC0DGQku/uMRcQeKIJcB8hm1Ad/EdQLzKBkGf3khAkt+EwMGFr8BCD4xAlWQSwEGE5j+MiL1AFeIywMZtysDZAOS+zio8PxQh8T51j9HA2cUVwebYrUAxUqM+/9URwDTxW79QaMO+doc3wSacHz/D92VA1W/ZQG36kkAlMuxA4iRvQBrxYkD0ddPASwLWwPSBP8C3S4xArrQwQBcbDkAov11A/FzYvw==
107 冒险家的经验 cqdmwLZECcAQ21W/wPAbwBL83L7gIZNAMfbrv5BX2b8B5FVAahGBvyIF9sDUbUpAJ6WbwLz+mL8aPSrAukt+wL73nkD+qqJAfE/+QMvvnz8X9iw/KG8CwCb4wL9GEDXA5QOJQB87D8C9aX1AB3FwQM7kf0A/rIi+TZStwDpr9z9E0MJA3IVdwG3nVUCTiuNAGgaFwBaRKEDvN4w+F8ssQPb2RcF2IuS/6eOYPyjKO8AGvD3AnabkwAZRHEAuorRAV8UAwU9YTD+Kxzo9zcvtP200/cCHwrFAla3+vwb1VUBwsXI/6NZUQFfNvz9e1ZBAKa1mQLORZ7+J2Nm/uS9cwA==
108 「蒙德往事」 WD+GwB8av79bS+8+3dDLwLc6nECBAHBADiEyv34ewb9d9ChAvKKsv1mO4sDevsC/E72HwBlF3L6A5x1AP4HTwK5G2UA1hGBAGoH0QDVuGz8Y9/09oDsKQOiqCz93y4+/LoUAQaUZFMCiuXlAhhhTQLFq+UCJQR1Aj7CVwEr7gb9r/plA64YawK4hOEAOg9FADStHwKIncsDDueC9zQiJP9rNpMAVDKXA1GJewJadLUD6w1y/1d8xwFE8sT5kv7C/91UwwXznCkD8a0BAKWzVQEXI078jaI9AZrTwQF7npUCKsxjA2YVRwNSA079OWotAjjRHQGiAtUBPDrM/WkL+vw==
109 烤肉卷 9GdDv0/gE8CbdbQ/c8olwFLegkBAwOk9kGVpPNrz7L8ojug/8MFPwBwPwcDOA2XAzHcWwO5c6r+ZF5JARD7KwMQC1r4xG4M/t0PVQDVzgr+2zbA/M8nxPzmBGEAJk4A/OCSKQehmGMBlBH9AyTj7P88uukCiyU9ALp9rwF+9HcGs6VtA9+4swJJD4j+IQ7VA7DcDwCC3BMEg+h4/kHfLPhTuAEDDWifBObiGv92kjEBUvwg/0gTCvy8d6b76/8jA67tmwSy/V0ARXJNAUx7mQEuyAUHpBitBEp80QMpiQkCeEPrAXb8IwQi9z79Af4BAeG4jQKG+m0BVUbFAV8zhvw==
110 气泡酸莓汁 nrg6wACvKcBC5Ya/cOi3wI3nOUCSehpANqEAv5RKI8BbkFFA5cz4vwirAMEB1Yw+5Q5XwA9/2r/9aINALkeDwCSi6r6dcldARY0KQSe4gr9MzoI/ad8OvwjyMT8ytIA8PRLiQN6AIMDDiJJA49trQI27a0CWqI2/fmaPwFF+jEDMKaFAJZyKwB1lc0AWMtpA6DxAwPRjmL7c7Je/afohQPEKL8FVO7jATYfbPWy4HT5MzOe/SmVDwJwKhz+g4Jc/EdnqwPOyhUCIdog/tgW+QMXdPj8h3ghAbwkOwGQrsz7efPG/AkufwOyCrb8dWItAJO9TQE26DUCHlu4/IZA4wA==
111 华饰之兜 o9Thv3ZAncDjHQLAT1XZvx26oL7gb7M+VPt3wDj2gcBPR6VAgjCDwGOaHcERzvE+svLHwJ09dMCI3ZM/wRPpwG8uBj4W27JAOEseQVHULMCxDoFAAlDPvkBfgr6NsoO/Sd4vQWHlmsBhbNpAPZKgQGxDUECNa1k/du/PwJDpOT/sd/FAJQCowHcHlkCDKhFBvs+gwFgVNz8pJz1ApdpYQB7dCcHBqATBX2/SwA2TAL9uRTHAauu8wIkwJEAWHaZAG2giwR9zTb7XptE/mzGKQM/FEUB9C5tAVFudv/LJAkDSOy/A1CxPwNzDzL++39RA6musQKtlT7/9oz9A3maZwA==
112 祭雷礼冠 mga+QIWgFcF/fJnAgKRDQAMd18DSVvzAKxvuwMvpE8E2pxJB4P0fwV1wWsEQhwdAg4npwAucCcGO1Ny/G/kxwL3Bv8D9mLdAUzRTQS/YC8FD8AdBcDapwGSPhb+wkYw/w+hfQbtwAcGYxjBBfVDeQPVC28Bx3CXATykZwTCqoECVVB1BQ4M1wWRw1UC+RkxBNLLtwOgbhUDkLBRAGhPpQB3DT8FduEvBrFk7vyB+PsCC0bbA2T1owMCipkAOvcW/vbErwZalbsD3yIO+ucCBQLvzLkE2OC1BsoeMwOnEqsDZbo3ADNLrwJwBmL1tpBtBZxEJQb1BI8ArV0lAsbESwQ==
113 奇迹之沙 vIFiQLfp4sBZkoXACYn+PyDbPT8G8EbAokquwCyy28DXbOhAp57WwN7HMcEQshHAcYXlwBmszcCpzj5APNaewNd7q8DkqZ9Alzg0QfI3CcFactZAPgWbPpBtAL/GaW6/xfdbQeaU18B0whVBvy3NQAtMTsBJ8sI+P63nwGBPRUBbkvRA1xr2wBUBp0CNeSRBzwvKwG4KXL/3ypBAfH2qQK4X48CUtl/B5BdvwehJQkCCgmDA1JmRwPN8C0BQy9FAlxwRwYJRZ8A2Md4/K4qrQLFE5EB92rlAlEKgP67/EUCuCkPAdoruwGaCt7+QCP9A83nYQFOSV8AfhsFAa9TGwA==
114 永恒的信仰 Ly6rwENQLsAbFh1AVCJcP9olGb7gHLBANBv1PnlHdcB7fxpA0jF0wO9Z3sDDp6bAVHA8wEBbVj/V5LFAyMkdwZF6uMAGohc/UYD0QKXs0r5MU1U+WiydQBV+okAlOJK/aHSYQUkXMcB5KkFAoacaQMh7D0HqG4VAy5WLwFRggj5lvqFAOyZGwJq7fD445stA1ZRLwCEN4MD5hgLAtlQfv5DSQb4G/CrB+vkcwQxD4kBSTAJAEm3vvxsdbsDA69w+NSpUwdChp75f/slATh1NQbsg/kBwA1hBMDuAQJh4HUAcNzTBk3aDwVUK1sBgqqVAcwA6QLVTKj9GTApAZtMUvw==
115 黑铜号角 eikSwN7uQsDYcvC/pLGUP8RVKEC6GolABagQwA8uIsDk74NAwP4JwI/LA8HQ8oM/qcyIwJpSAsBAptU/lP59wHTOG0BBkp1AU3sGQYWYl77kv+U/F555P/z7mjwAERDA+SDeQGOcL8BiMZ1AVPl7QFDPcUDlLsk9RyKywLAsob9OPcpAT+F9wM5VakB+4vpAs8Z6wKDjh72oB/M/ULNAQEfiqsCwMDzAXI63wMOUCT/QMj3Aij7mwCyA5D8uf9BA7qj9wHWv4j+JMN4+ilkEQA3AjsCXI5hA1IJKwFzJbUCcjFa/fElbvUSSqj0mDahALzWDQGGu/b+GSJy+RfNzwA==
116 鲜鱼炖萝卜 6IOvv0gWzcChsK4/iXQHvrOLLMFeiJjA3nlPwOTGvMB7lMpAbgTcwONwLMEu1xZAlU76wGk2kMCekA3BY82+wN+hPMBo3EFAougaQYslSb4gn5NADMgCwQDVXD8zFwdAnJ1qQS6G0cCinN9AX9qbQDK2Y0A9GEI//37ywNlJvMCwe/5AR07XwFs+CkAU1CVBs6jDwA13kEBxeqi+/cpBQHeGvsCuLvfAoC5VO2T0yD8SRvK/zKrBvx1QVkCCMzrBnZApwWRphj5HFQNASTa3QOlPPkGFJu9A4BpQvxKlFcAdH/vATC3kwPg4E8A/YQJB52TSQPJCk0DpL21AqtexwA==
117 勇士的壮行 eEFnP+RJcsAtihLAR1BKPsA2v0DEU4I/n0AdwB2FWsCfKI9Agfp/wDkqDcEGeRrADdSIwMkngcBs6ttA/Yq4wD1Iw78Cs49AWecZQQMWjMBaYzhASs1EQPwMBz+S9Xq/iPQ9QVN1bcBeZdxA26qSQE7jAj/95Go/1qS3wKter79ky8BAt/GrwBrul0B7cAZBZXaEwLB8lsCtsC9AOsl6QApOF79IUBDBBS0xwUJ5mkBD+iPAN4SbwJGUhz8Eg2lAZs8rwQgTiTygkgRAQI6RQLnf/z8SGMdAXrg8QFRaGUDzpU3A4h+ZwJxil78MVLlA3NqNQLXLPj/X41VAJd6LwA==
118 地脉的新芽 SIHWv5xrtMD3Ow/ADcaMQGWw0kBEEiRAPtR8wFO4ccAJRZtAdbePwE8VDsGhhhLANly+wCaSbsCtaAFBh3DowHHbUcA6vKpA0L8OQVXlNMBb/Y5AuvSrQBuDF0AmeGC/PmyAQc8oo8AvJN1AX0ynQDxdjEDJioQ/n7DGwGLajb8+RNpAqop/wEb8ekCn2AhBxi2IwHxQlMCj9n1AzYw8QFR5XkCROpDAJOtrwa+LFkB1Pe6/NT4hwfsByT9hTwJBfuQowfB8BECmL+0/K9g6QAY3ykA1CPtASUgMwQYBIkAiLJrAMoviwPB+fMBxOdBAnV+tQI5RNcCUygc+dWB2wA==
119 纷争的前宴 ytAHQKmy28C0NF3AXaBFwA1Y6r2d0MHAyo2qwPWLpcDEX8RASfzOwE/nO8H+mi2+AUC8wPPM18C6Juu+U2nSwKuRZsBPX7ZA25A+QTWwm8CS285AAkeCwMPQi78TJco/hkp8QVD9s8BL6A9BRcinQEwpYj/bMmW/Y+fswLjpdcBLGQJBoo3cwD2DxUCQ8C9BXMehwKxlmb7ScS5ASIacQAH+/MCeghTB67WVPxAl/b8UqmbAoRXtwH3chUCANrg/7SVDwR74XECtNCo/as9tQFGS0UB5iShBsGriwIcCo78nWEbAEuGlwO5Beb7Or/RAf+jLQNX1GD9Xn89AYMnqwA==
120 流浪者的经验 xhGewLLUMMB8KpU9sq1hPyKRu0AEr5g/Plssvzyrrr+ojhVA/LI3wG1D18BvL6zA6ASGwAYfDsC4HJRAVWYvwZ1vqcDurjZA/h/rQBsQmL80hK4/DJRKQMkDYkCSuAdA9GNwQZIjSMCynKhAuEhLQOmfzEAUKohAfSVzwJGROD4rSYFAjmMwwK9KQ0BEvrxAZogWwMzE2cCsH9U/AHpUP/WQ9kBGwOPA4hyiwR8S1UAiPk0/FeW8wBiJfr/4WHY/vVowwfIMsj1Sw61AAc+4QJpV3EBwD9tANicVQFW75T/glNDAKbs4wfc/ucB+pI1AkoJGQKamkEBa6UNAx6EXwA==
121 泡泡桔 U+fjvwGOw78BmIC/dXlswHenBECJLTpAkMRQPNtpEsDPmR5ACufdv4YUwsAfjvq+b7qkv23dvb4Cks5AHPo0wGjGhEDtRyNAFVffQLlKtr6Sppk+ZyOkP9xDLD/GfQa/GvLXQHGAvr9pP0BA8UcRQHxAmkBiwSw+nTJswIOmkcBKBo5A+chOwBgqTUDVRrNAUtEPwCPo0L+2awXAaavDP4Frn8AXS67A9TPaQAAETUBWrq6/qMUGP3jwIL3s/+G/+Wr1wNHNLEC2T9E/0xy0QBQIPD6hN7RAvNVDQPuGtD/tA5PAT0l6wFQIrb8ZyHJA9RgLQH/0MUBVsak/ytgGwA==
122 混沌炉心 FgUxwILUCsApvIq+yQ6lPwUbXUBS7YhA7vw+v1Az2L81ujRAImAPwHXt4MC4dMi/QiNfwITw6L8jcbhAYTDywMwwcr845VlA8Nv3QGsOVL8W8IE/kB1HQGQPyz/Xa/y/IP81QWi6CcBhZZJAS7NOQIuum0AtUQhAROSLwIS6t8BUzZ5AaT9XwCrQL0CMZ89A00E0wGQcfMAq3xxA9EXjPyxPzL7CjvHAzrwzwat8fEDKE6O/GyXrwHI+Oz5ApMZAM1UhwUz/C0Bg7i1AmXaiQGfpTT/Oos9AxJHQvsQ/S0Aj8mnAuR5mwKQZ879G34ZAKbEwQBWE0b9VEENAr1Y4wA==
123 凉拌薄荷 7KZRwOPe2L+iDlpAdtQ9v+kEpb50fuo/Dvb/vRNuJL+MaDI/0yEMwFG2jMCgeo6/a1qEwKHDy75aLiHAWQrswC+flMDmV0k+anWWQDE+CkC+7LM+firTPq5V6D+U4K29I9SgQYvvpL/W//I/IisMP/B3/0DiuSxAifoqwJ6TN7/Obi1Avfqhv/MIYr+bemJAIwHEv/AJtcCHSOi/fGkmv+Dk4sA+JuPALVy3P8L6XsAc4LM/bNO+wKTUhz2gqoE/ktJ7wYYK4j+WbHVARYzSQCWR2UDCPmlBiJ6pwOh6KEAciLLALvT1wPwL/r/Rvg1ADVK9PxWM6b8hDdI/zCEkvw==
124 千层酥酥 zu9zwNAwKsDdHDRA8zbJv+aiQT8S4G5AJ+Oov8IU+r9JcQ1AwyMnwGsRxMDf02a/+gq1wMk+0L7lsE6/ncbuwDdHGD7Njg5Ad1/FQK1znT8Wrrk/8/W1P1Zxkz8V6ri/bPyNQerzNMDMZzVAgIwqQPMZCUGX/kNAn9iTwC/ZMsAbqZlAxnqVv9Ei2726w71A0IVbwHrBuMDBQSG+/CIAvcjBlsCHNcnAT0Nwv4G4Mb+QXDM/ptjAwDn9iz4YYCk/oxZpwUhPjT+mq29AdizNQDPuh0DZC0NB9Y69v83krECjKrrAuMaZwAS/E8Cuq41AJZtZQFFEAT11CaQ/Q6+Kvw==
125 烛伞蘑菇 6Ufbvwp4T8Dryug+VKFUPzFQMT8jXDtAZ7wAvukyZcBvbVFAfp51wLjx98D4jTXANy0swJDMBcB/K+JAl1LkwJdTjMCljK0/FZoGQS8d1r9KUtA/LPlRQPfpJkC6aQe+Yg13QeNFK8DNF4RAgFpCQI7IuUDciOw/pX2IwAxO+cCIw6dAda1rwM3N1z8mCudA4PRHwJBYoMD5oNC+Ste8P4ImLL9gjzXByiXTwFPT5ECGGxa/HebeP+yzL7+ga+w+oronwUQ+KkAvA0tA0UYPQTCl8UDdVp9APCo3QEjXM0DuAebA2tgjwabSfsCHdqxAguBLQAbRjr4R4MVAGYccwA==
126 至高的智慧(生活) qOMLwMcHkb/oqy9A3Wy4v1h65D8w9CM/B6H0P7v0fL+wehQ/fukIwICOvMAwYRnAa/G4v8zC1r9MTIhAMJUVwe6W07+mmEg+MaTfQNH1fD8FXUK/HujUPuqQ+z9lP44/tCeMQZGn0b8paFVAA+KmP+8TCEHObkdADZxbwNhv6sAfx0lAIUcywJQQnz9lm6xAlwySvybV08AIaBDAZBN+P6bzJ0CPnwfBnwHgwA0KmkBy8Qo+5TIswCdks76ibajAY6lnwUwZCkDlHYdAyEQHQQBq+EBJiytBpDqdQNOKgz+8hwLBLODqwHxbGsD6LlhANa+3P+bepkBfM8VA1fwlwA==
127 没有未来菜 9FwLwHTzJMAwNGE/FHgqPxSuqEAwVn5AHz+Ev/y/2b9NHDpACBwZwI2z2MCYL42/miabwNkZC8Dl7J1AC6jgwEz34r8MTTVAl2XjQHvd4T4IRu0/lIZTQPxTvD82YUK/oQp4QSpeDcBoSoZAnfs/QMDC6kA0ueY/GxSHwBsAEMFi0p1Azu7ov2v6hj8gTc1A/kMxwMPU1cBEt1U/op7UP8jg1j//p8DAVcbFwPNQMUAcYWS/j60wwTKNiT9E4GRAMtU3wbq8lUDPwhFAor10QLB3c0D9Qe9AkNemwCXMqEAchojAT+KBvyB5GMAZJI5AJrRMQEMbHL8LgapAq/0WwA==
128 一捧绿野 7ExgwAT8PsDMqChA0AjtPnrn5j9N6WBAK2fbv0wFBcCaMfU/e2Q4wLvgmcBAX0LA4CzBwMFfo762svs+Fyn3wJPRSsDTcpk/haugQFBtQDsw1us/htSVQH9nJkCRvvK/9DCuQcQoTMAo9D5AbN/4P7VaB0HarXZAPylYwDjH9z4g8WVA1nCNv/fkTb809oNA96kqwEuO48BoJHI/8qZAvzWOhsCbrwbBoNumwGXeDcCIR/c/az/1wFhoDr+/1bpAybB+weS/Gj+noJZAkd/1QHbyEUEC3F1Bt3O7wNOdqEBUj8jAiQ0Xwd8Pb8BrGVVAYcMvQKq9hMC9AIQ/6CoEvQ==
129 琦色灵彩之羽 qYSZv6O6WsDmIh/AJC75PuBZEECmsARAzdYYwMslLsBxR4VAv7IzwCF4BsG3Gkm/cS6AwEAvL8ALyAVApyqtwEX8EMCcyIFA4hoLQcFQFsArhyNAP+lZP1xzSz9FAa6/oOcDQVFgPsBJMblAKdl5QLmj9j+sdYU/wU+swHb5jr8bWbtANTGRwDVoa0AAKvtARbh2wJFLo790XSJAA4sxQECe2MAu6/TAFsDkwHVQWz9IZw3A26chwYVLmT8UyfVAZHbowM9qnD9WocI/C9cuQMCfSr9tddZAhYuowLx5G0DpKAfA7pa8v1D8Lr8iKKpAsUyAQM7MTsAUQhZAIhqAwA==
130 饰金胸花 1CcdwH3KMcCFSLi+sa5XvkEvLz+eUitA9zSYv5HGR8BKAVtAX1NMwGRD7cBlEizAvdCCwK3hw7+C6k9AZ27nwISWrr6PCyZAXzYAQedKB8D4Es4/DlQXQMBu1T83ENC/QXJBQd5xSsCNdpRAj7VQQNx2g0AYd1JAk7iZwEy9rb8TbKdAvQqCwObvJUCsm9tA/ohqwM4ZN8DqpMU/CTPFPzZ0HsBz/RbBzroewUFVhkD+FBW/5zB4wJhoKr/GKRZAYfUswWmhxb/3doNAFanpQH2wZUBTYw1BBDeYQAbXREAl47TAmm3ywPqkLMD6xqJA/cVeQItKCb/YwVBAgVAxwA==

View File

@@ -1,4 +1,4 @@
using System.Threading;
using System.Threading;
using System.Threading.Tasks;
namespace BetterGenshinImpact.GameTask;
@@ -20,3 +20,13 @@ public interface ISoloTask
/// <returns></returns>
Task Start(CancellationToken ct);
}
public interface ISoloTask<T> : ISoloTask
{
/// <summary>
/// 启动独立任务
/// </summary>
/// <param name="ct">取消Token</param>
/// <returns></returns>
new Task<T> Start(CancellationToken ct);
}

View File

@@ -1,21 +1,27 @@
using BetterGenshinImpact.GameTask.AutoFishing;
using Microsoft.Extensions.Localization;
using System;
using System.Globalization;
using System.Threading;
namespace BetterGenshinImpact.GameTask.Model;
/// <summary>
/// 独立任务参数基类
/// </summary>
public class BaseTaskParam
public abstract class BaseTaskParam<T> where T : class
{
public CultureInfo? GameCultureInfo { get; set; }
public BaseTaskParam()
/// <summary>
/// 游戏语言CultureInfo
/// </summary>
public CultureInfo GameCultureInfo { get; private set; }
/// <summary>
/// 多语言StringLocalizer
/// 用于读取与 <typeparamref name="T"/> 同名的.resx文件中的多语言信息
/// </summary>
public IStringLocalizer<T> StringLocalizer { get; private set; }
public BaseTaskParam(CultureInfo? gameCultureInfo, IStringLocalizer<T>? stringLocalizer)
{
}
public BaseTaskParam(CultureInfo? gameCultureInfo)
{
GameCultureInfo = gameCultureInfo;
GameCultureInfo = gameCultureInfo ?? new CultureInfo(TaskContext.Instance().Config.OtherConfig.GameCultureInfoName);
StringLocalizer = stringLocalizer ?? App.GetService<IStringLocalizer<T>>() ?? throw new Exception();
}
}

View File

@@ -0,0 +1,258 @@
using BetterGenshinImpact.Core.Simulator;
using BetterGenshinImpact.GameTask.Common;
using BetterGenshinImpact.GameTask.Model.Area;
using Fischless.WindowsInput;
using Microsoft.Extensions.Logging;
using OpenCvSharp;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
namespace BetterGenshinImpact.GameTask.Model.GameUI
{
public class ArtifactSetFilterScreen : IAsyncEnumerable<ImageRegion>
{
private readonly GridParams @params;
private readonly CancellationToken ct;
private readonly ILogger logger;
private readonly InputSimulator input = Simulation.SendInput;
/// <summary>
/// 对圣遗物套装筛选界面的操作封装类
/// 直接对此类对象进行遍历即可获取所有项
/// 每次的截图是上次滚动后的,如果实时性要求高,应每次迭代自行截图
/// 在末页可能重复返回GridItem须自行处理
/// </summary>
/// <param name="@params"></param>
/// <param name="logger"></param>
/// <param name="ct"></param>
public ArtifactSetFilterScreen(GridParams @params, ILogger logger, CancellationToken ct)
{
this.ct = ct;
this.logger = logger;
this.@params = @params;
}
public IAsyncEnumerator<ImageRegion> GetAsyncEnumerator(CancellationToken cancellationToken = default)
{
return new GridEnumerator(@params.Roi, @params.Columns, new GridScroller(@params, logger, input, ct), ct);
}
public class GridEnumerator : IAsyncEnumerator<ImageRegion>
{
private readonly Rect roi;
private readonly CancellationToken ct;
private readonly int columns;
private readonly GridScroller gridScroller;
private Queue<ImageRegion> imageRegions;
private ImageRegion? current;
ImageRegion IAsyncEnumerator<ImageRegion>.Current => current ?? throw new NullReferenceException();
internal GridEnumerator(Rect roi, int columns, GridScroller gridScroller, CancellationToken ct)
{
this.roi = roi;
this.ct = ct;
this.columns = columns;
this.gridScroller = gridScroller;
this.imageRegions = new Queue<ImageRegion>();
}
public async ValueTask<bool> MoveNextAsync()
{
if (current == null || this.imageRegions.Count < 1)
{
if (current != null)
{
using var ra4 = TaskControl.CaptureToRectArea();
ra4.MoveTo(this.roi.X + this.roi.Width / 2, this.roi.Y + this.roi.Height / 2);
await TaskControl.Delay(300, ct);
if (!await this.gridScroller.TryVerticalScollDown(GetGridItems))
{
return false;
}
}
using ImageRegion ra = TaskControl.CaptureToRectArea();
using ImageRegion imageRegion = ra.DeriveCrop(this.roi);
IEnumerable<ImageRegion> gridItems = GetGridItems(imageRegion.SrcMat, this.columns).Select(imageRegion.DeriveCrop);
this.imageRegions = new Queue<ImageRegion>(gridItems);
}
this.current = this.imageRegions.Dequeue();
return true;
}
public ValueTask DisposeAsync()
{
return ValueTask.CompletedTask;
}
}
/// <summary>
/// 顺便把顺序也从左到右从上到下排好了
/// </summary>
/// <param name="src"></param>
/// <param name="columns"></param>
/// <returns></returns>
public static IEnumerable<Rect> GetGridItems(Mat src, int columns)
{
using Mat grey = src.CvtColor(ColorConversionCodes.BGR2GRAY);
using Mat canny = grey.Canny(20, 40);
using Mat closeKernel = Cv2.GetStructuringElement(MorphShapes.Rect, new Size(3, 3));
using Mat close = canny.MorphologyEx(MorphTypes.Close, closeKernel);
//Cv2.ImShow("grey", grey);
//Cv2.ImShow("close", close);
//Cv2.WaitKey();
Cv2.FindContours(close, out Point[][] contours, out _, RetrievalModes.External, ContourApproximationModes.ApproxSimple, null);
contours = contours
.Where(c =>
{
Rect r = Cv2.BoundingRect(c);
if (r.Width < src.Width / columns * 0.66) // 剔除太小的
{
return false;
}
if (r.Height == 0)
{
return false;
}
return Math.Abs((float)r.Width / r.Height - 8.63) < 0.4; // 按形状筛选
}).ToArray();
IEnumerable<Rect> boxes = contours.Select(Cv2.BoundingRect);
double avgWidth = boxes.Average(r => r.Width);
double avgHeight = boxes.Average(r => r.Height);
List<Cell> cells = ClusterToCells(boxes, 10).ToList();
double avgColSpacing;
double avgRowSpace;
{
int count = 0;
int sum = 0;
foreach (var row in cells.GroupBy(t => t.RowNum))
{
for (int i = 0; i < row.Max(r => r.ColNum); i++)
{
var x1 = row.SingleOrDefault(r => r.ColNum == i);
var x2 = row.SingleOrDefault(r => r.ColNum == i + 1);
if (x1 == null || x2 == null)
{
continue;
}
sum += x2.Rect.X - x1.Rect.X - x1.Rect.Width;
count++;
}
}
avgColSpacing = Math.Round(((double)sum) / count, MidpointRounding.AwayFromZero);
}
{
int count = 0;
int sum = 0;
foreach (var col in cells.GroupBy(t => t.ColNum))
{
for (int i = 0; i < col.Max(r => r.RowNum); i++)
{
var y1 = col.SingleOrDefault(r => r.RowNum == i);
var y2 = col.SingleOrDefault(r => r.RowNum == i + 1);
if (y1 == null || y2 == null)
{
continue;
}
sum += y2.Rect.Y - y1.Rect.Y - y1.Rect.Height;
count++;
}
}
avgRowSpace = Math.Round(((double)sum) / count, MidpointRounding.AwayFromZero);
}
int avgLeft = (int)Math.Round(cells.Average(c => c.Rect.X - (avgWidth + avgColSpacing) * c.ColNum), MidpointRounding.AwayFromZero);
int avgTop = (int)Math.Round(cells.Average(c => c.Rect.Y - (avgHeight + avgRowSpace) * c.RowNum), MidpointRounding.AwayFromZero);
// 遍历方阵补上缺的Cell
for (int i = 0; i < cells.Max(r => r.ColNum) + 1; i++)
{
for (int j = 0; j < cells.Max(r => r.RowNum) + 1; j++)
{
if (cells.Any(c => c.ColNum == i && c.RowNum == j))
{
continue;
}
int x = (int)Math.Round(avgLeft + (avgWidth + avgColSpacing) * i, MidpointRounding.AwayFromZero);
int y = (int)Math.Round(avgTop + (avgHeight + avgRowSpace) * j, MidpointRounding.AwayFromZero);
int width = (int)Math.Round(avgWidth, MidpointRounding.AwayFromZero);
int height = (int)Math.Round(avgHeight, MidpointRounding.AwayFromZero);
Cell cell = new Cell(new Rect(x, y, width, height));
cell.ColNum = i;
cell.RowNum = j;
cells.Add(cell);
}
}
return cells.OrderBy(c => c.RowNum).ThenBy(c => c.ColNum).Select(c => c.Rect).ToArray();
}
/// <summary>
/// 具有行号列号的单元格
/// ColNum和RowNum也是0-based的
/// 不仅方便编程ClusterColsAndRows方法也需要一个引用类型
/// </summary>
/// <param name="rect"></param>
private class Cell(Rect rect)
{
internal Rect Rect = rect;
internal int ColNum;
internal int RowNum;
}
/// <summary>
/// 把检出的矩形聚簇成类似Excel的单元格集合
/// </summary>
/// <param name="rects"></param>
/// <param name="threshold"></param>
/// <returns></returns>
private static IEnumerable<Cell> ClusterToCells(IEnumerable<Rect> rects, int threshold)
{
var result = rects.Select(r => new Cell(r));
result = result.ToArray(); // 必需,不然引用会丢掉。。
var orderByX = result.OrderBy(t => t.Rect.Left).ToArray();
int col = 0;
int? lastX = null;
for (int i = 0; i < orderByX.Length; i++)
{
if (lastX != null && orderByX[i].Rect.X - lastX > threshold)
{
col++;
}
orderByX[i].ColNum = col;
lastX = orderByX[i].Rect.X;
}
var orderByY = result.OrderBy(t => t.Rect.Top).ToArray();
int row = 0;
int? lastY = null;
for (int i = 0; i < orderByY.Length; i++)
{
if (lastY != null && orderByY[i].Rect.Y - lastY > threshold)
{
row++;
}
orderByY[i].RowNum = row;
lastY = orderByY[i].Rect.Y;
}
return result;
}
}
}

View File

@@ -0,0 +1,44 @@
using BetterGenshinImpact.Core.Recognition.OpenCv;
using OpenCvSharp;
using System;
using System.Collections.Frozen;
using System.Collections.Generic;
using System.Text;
namespace BetterGenshinImpact.GameTask.Model.GameUI
{
public class GridParams
{
internal Rect Roi { get; private set; }
internal int Columns { get; private set; }
internal int S1Round { get; private set; }
internal int RoundMilliseconds { get; private set; }
internal int S2Round { get; private set; }
internal double S3Scale { get; private set; }
public GridParams(Rect roi1080p, int columns, int s1Round, int roundMilliseconds, int s2Round, double s3Scale)
{
Roi = roi1080p.Multiply(TaskContext.Instance().SystemInfo.AssetScale);
Columns = columns;
S1Round = s1Round;
RoundMilliseconds = roundMilliseconds;
S2Round = s2Round;
S3Scale = s3Scale;
}
private static readonly GridParams weapons = new GridParams(new Rect(106, 110, 1171, 845), 8, 3, 40, 32, 0.024);
internal static FrozenDictionary<GridScreenName, GridParams> Templates { get; } = new Dictionary<GridScreenName, GridParams>() {
{ GridScreenName.Weapons, weapons },
{ GridScreenName.Artifacts, new GridParams(new Rect(106, 162, 1171, 783), 8, 3, 40, 32, 0.024)},
{ GridScreenName.CharacterDevelopmentItems, weapons },
{ GridScreenName.Food, weapons },
{ GridScreenName.Materials, weapons },
{ GridScreenName.Gadget, weapons },
{ GridScreenName.Quest, weapons },
{ GridScreenName.PreciousItems, weapons },
{ GridScreenName.Furnishings, weapons },
{ GridScreenName.ArtifactSalvage, new GridParams(new Rect(48, 106, 1267, 768), 9, 3, 40, 28, 0.018)}
}.ToFrozenDictionary();
}
}

View File

@@ -1,5 +1,4 @@
using BetterGenshinImpact.Core.Simulator;
using BetterGenshinImpact.GameTask;
using BetterGenshinImpact.Core.Simulator;
using BetterGenshinImpact.GameTask.Common;
using BetterGenshinImpact.GameTask.Model.Area;
using Fischless.WindowsInput;
@@ -17,15 +16,10 @@ namespace BetterGenshinImpact.GameTask.Model.GameUI
{
public class GridScreen : IAsyncEnumerable<ImageRegion>
{
private readonly Rect gridRoi;
private readonly GridParams @params;
private readonly CancellationToken ct;
private readonly ILogger logger;
private readonly InputSimulator input = Simulation.SendInput;
private readonly int columns;
private readonly int s1Round;
private readonly int roundMilliseconds;
private readonly int s2Round;
private readonly double s3Scale;
/// <summary>
/// 对Gird类型界面的操作封装类
@@ -33,41 +27,32 @@ namespace BetterGenshinImpact.GameTask.Model.GameUI
/// 每次的截图是上次滚动后的,如果实时性要求高,应每次迭代自行截图
/// 在末页可能重复返回GridItem须自行处理
/// </summary>
/// <param name="gridRoi">Grid所在位置</param>
/// <param name="@params"></param>
/// <param name="logger"></param>
/// <param name="ct"></param>
public GridScreen(Rect gridRoi, GridScreenParams @params, ILogger logger, CancellationToken ct)
public GridScreen(GridParams @params, ILogger logger, CancellationToken ct)
{
this.gridRoi = gridRoi;
this.ct = ct;
this.logger = logger;
if (@params.Columns < 4)
{
throw new ArgumentOutOfRangeException(nameof(@params.Columns));
}
this.columns = @params.Columns;
this.s1Round = @params.S1Round;
this.roundMilliseconds = @params.RoundMilliseconds;
this.s2Round = @params.S2Round;
this.s3Scale = @params.S3Scale;
this.@params = @params;
}
public IAsyncEnumerator<ImageRegion> GetAsyncEnumerator(CancellationToken cancellationToken = default)
{
return new GridEnumerator(gridRoi, columns, s1Round, roundMilliseconds, s2Round, s3Scale, logger, input, ct);
return new GridEnumerator(@params.Roi, @params.Columns, input, new GridScroller(@params, logger, input, ct), ct);
}
public class GridEnumerator : IAsyncEnumerator<ImageRegion>
{
private readonly Rect roi;
private readonly CancellationToken ct;
private readonly ILogger logger;
private readonly InputSimulator input = Simulation.SendInput;
private readonly int columns;
private readonly int s1Round;
private readonly int roundMilliseconds;
private readonly int s2Round;
private readonly double s3Scale;
private readonly GridScroller gridScroller;
/// <summary>
/// 单次滚动得到的页面
@@ -91,59 +76,13 @@ namespace BetterGenshinImpact.GameTask.Model.GameUI
/// <param name="logger"></param>
/// <param name="input"></param>
/// <param name="ct"></param>
public GridEnumerator(Rect roi, int columns, int s1Round, int roundMilliseconds, int s2Round, double s3Scale, ILogger logger, InputSimulator input, CancellationToken ct)
internal GridEnumerator(Rect roi, int columns, InputSimulator input, GridScroller gridScroller, CancellationToken ct)
{
this.roi = roi;
this.ct = ct;
this.logger = logger;
this.input = input;
this.columns = columns;
this.s1Round = s1Round;
this.roundMilliseconds = roundMilliseconds;
this.s2Round = s2Round;
this.s3Scale = s3Scale;
}
public async Task<bool> TryVerticalScollDown()
{
using var ra = TaskControl.CaptureToRectArea();
using ImageRegion prevGrid = ra.DeriveCrop(roi);
for (int i = 0; i < this.s1Round; i++)
{
this.input.Mouse.VerticalScroll(-2);
await TaskControl.Delay(this.roundMilliseconds, this.ct);
}
await TaskControl.Delay(300, this.ct);
using var ra2 = TaskControl.CaptureToRectArea();
using ImageRegion scrolledGrid = ra2.DeriveCrop(this.roi);
bool isScrolling = IsScrolling(prevGrid.CacheGreyMat, scrolledGrid.CacheGreyMat, out Point2d shift, logger: this.logger);
return isScrolling;
}
/// <summary>
/// 判断是否还能继续滚动,如果到底了则只能滚动一丝并很快地回弹
/// </summary>
/// <param name="prevGray">先前的灰度图</param>
/// <param name="nextGray">尝试滚动并等待可能的回弹后的灰度图</param>
/// <param name="shift">估计的位移</param>
/// <param name="lowerThreshold">低于下限则可能不存在平移</param>
/// <param name="upperThreshold">上限用于抵消微小的其他差异,比如高亮图标的呼吸闪烁</param>
/// <param name="logger"></param>
/// <returns></returns>
public static bool IsScrolling(Mat prevGray, Mat nextGray, out Point2d shift, double lowerThreshold = 0.5, double upperThreshold = 0.95, ILogger? logger = null)
{
using Mat prev = new Mat();
prevGray.ConvertTo(prev, MatType.CV_32FC1);
using Mat next = new Mat();
nextGray.ConvertTo(next, MatType.CV_32FC1);
using Mat window = new Mat();
shift = Cv2.PhaseCorrelate(prev, next, window, out double response); // 相位相关性
//logger?.LogInformation($"response={response:F3}, shift=({shift.X:F2}, {shift.Y:F2})");
return response > lowerThreshold && response < upperThreshold;
this.gridScroller = gridScroller;
}
/// <summary>
@@ -153,20 +92,51 @@ namespace BetterGenshinImpact.GameTask.Model.GameUI
/// <param name="numbers">传入的Y列表</param>
/// <param name="threshold"></param>
/// <returns>外层是各行从上到下,内层是一行从左到右</returns>
static List<List<ImageRegion>> ClusterRows(IEnumerable<ImageRegion> regions, int threshold)
public static List<List<T>> ClusterRows<T>(IEnumerable<T> regions, int threshold)
{
// 先对Y排序便于聚簇
var sortedRegions = regions.OrderBy(r => r.Y).ToList();
static int getX(T t)
{
if (t is ImageRegion imageRegion)
{
return imageRegion.X;
}
else if (t is Rect rect)
{
return rect.X;
}
else
{
throw new NotSupportedException();
}
}
static int getY(T t)
{
if (t is ImageRegion imageRegion)
{
return imageRegion.Y;
}
else if (t is Rect rect)
{
return rect.Y;
}
else
{
throw new NotSupportedException();
}
}
List<List<ImageRegion>> clusters = new List<List<ImageRegion>>();
// 先对Y排序便于聚簇
var sortedRegions = regions.OrderBy(getY).ToList();
List<List<T>> clusters = new List<List<T>>();
if (sortedRegions.Count == 0)
return clusters;
// 初始化第一个聚簇
List<ImageRegion> currentCluster = new List<ImageRegion> { };
List<T> currentCluster = new List<T> { };
foreach (ImageRegion r in sortedRegions)
foreach (T r in sortedRegions)
{
if (currentCluster.Count <= 0)
{
@@ -174,23 +144,23 @@ namespace BetterGenshinImpact.GameTask.Model.GameUI
continue;
}
ImageRegion lastInCluster = currentCluster.Last();
T lastInCluster = currentCluster.Last();
// 如果当前数字与聚簇中最后一个数字的差值小于阈值,则加入当前聚簇
if (r.Y - lastInCluster.Y <= threshold)
if (getY(r) - getY(lastInCluster) <= threshold)
{
currentCluster.Add(r);
}
else
{
// 否则,创建一个新的聚簇
clusters.Add(currentCluster.OrderBy(r => r.X).ToList());
currentCluster = new List<ImageRegion> { r };
clusters.Add(currentCluster.OrderBy(getX).ToList());
currentCluster = new List<T> { r };
}
}
// 添加最后一个聚簇
clusters.Add(currentCluster.OrderBy(r => r.X).ToList());
clusters.Add(currentCluster.OrderBy(getX).ToList());
return clusters;
}
@@ -245,7 +215,7 @@ namespace BetterGenshinImpact.GameTask.Model.GameUI
{
return false;
}
return Math.Abs((float)r.Width / r.Height - 0.8) < 0.05; // 按形状筛选
return Math.Abs((float)r.Width / r.Height - 0.81) < 0.05; // 按形状筛选
}).ToArray();
IEnumerable<Rect> boxes = contours.Select(Cv2.BoundingRect);
@@ -294,9 +264,9 @@ namespace BetterGenshinImpact.GameTask.Model.GameUI
/// <summary>
/// 背包界面的背景是把打开界面之前的画面进行了模糊+黑白渐变滤镜+左上角水印叠加处理
/// 放任五彩斑斓的输入,并且允许点击高亮的话处理起来就复杂了
/// 所以这个Alpha版方法留在这里只是想说明
/// <para>所以这个Alpha版方法留在这里只是想说明
/// 越是琢磨算法,就越会发现传统算法的能力是有极限的
/// 既然是游戏画面,不如在输入的时候就尽量获得没有噪声的画面
/// 既然是游戏画面,不如在输入的时候就尽量获得没有噪声的画面</para>
/// </summary>
/// <param name="src"></param>
/// <returns></returns>
@@ -407,7 +377,8 @@ namespace BetterGenshinImpact.GameTask.Model.GameUI
{
if (this.currentPage == null || this.currentPage.ImageRegions.Count < 1)
{
if (this.currentPage != null)
IEnumerable<ImageRegion> gridItems;
if (this.currentPage != null) // 当前页遍历完了就向下滚动
{
if (this.currentPage.AntiRecycling.HasValue)
{
@@ -426,47 +397,16 @@ namespace BetterGenshinImpact.GameTask.Model.GameUI
ra4.MoveTo(this.roi.X + this.roi.Width / 2, this.roi.Y + this.roi.Height / 2);
await TaskControl.Delay(300, ct);
bool canScoll = await TryVerticalScollDown();
if (canScoll)
if (!await this.gridScroller.TryVerticalScollDown((src, columns) => GetGridItems(src, columns)))
{
for (int i = 0; i < this.s2Round; i++) // 再滚动差不多(最多行数-1
{
input.Mouse.VerticalScroll(-2);
await TaskControl.Delay(this.roundMilliseconds, ct);
}
DateTimeOffset rollingEndTime = DateTime.Now.AddSeconds(2);
while (DateTime.Now < rollingEndTime)
{
await TaskControl.Delay(60, ct);
using var ra2 = TaskControl.CaptureToRectArea();
using ImageRegion grid2 = ra2.DeriveCrop(this.roi);
IEnumerable<Rect> gridItems2 = GetGridItems(grid2.SrcMat, this.columns);
if (gridItems2.Min(i => i.Y) > (ra2.Width * this.s3Scale)) // 最后精细滚动,保证完整地显示最多行
{
input.Mouse.VerticalScroll(-1);
}
else
{
break;
}
}
using var ra3 = TaskControl.CaptureToRectArea();
using ImageRegion grid3 = ra3.DeriveCrop(this.roi);
grid3.MoveTo(grid3.Width, grid3.Height);
await TaskControl.Delay(300, ct);
}
else
{
await TaskControl.Delay(300, ct);
this.logger.LogInformation("滚动到底部了");
return false;
}
}
IEnumerable<ImageRegion> gridItems;
if (this.currentPage == null)
using ImageRegion ra = TaskControl.CaptureToRectArea();
using ImageRegion imageRegion = ra.DeriveCrop(this.roi);
gridItems = GetGridItems(imageRegion.SrcMat, this.columns).Select(imageRegion.DeriveCrop);
}
else
{
// 第一页采集时,主动操作来避免图标高亮
// 双击第四列,采集第一、二列
@@ -495,12 +435,6 @@ namespace BetterGenshinImpact.GameTask.Model.GameUI
gridItems = columns12Items.Select(imageRegion12.DeriveCrop).Union(columnsRestItems.Select(imageRegionRest.DeriveCrop)).ToArray();
}
else
{
using ImageRegion ra = TaskControl.CaptureToRectArea();
using ImageRegion imageRegion = ra.DeriveCrop(this.roi);
gridItems = GetGridItems(imageRegion.SrcMat, this.columns).Select(imageRegion.DeriveCrop);
}
List<List<ImageRegion>> clusterRows = ClusterRows(gridItems, (int)(0.025 * this.roi.Height));
this.currentPage = new Page(new Queue<ImageRegion>(clusterRows.SelectMany(r => r)), clusterRows.Reverse<List<ImageRegion>>().Skip(1)?.FirstOrDefault()?.FirstOrDefault()?.ToRect());

View File

@@ -0,0 +1,34 @@
using BetterGenshinImpact.Core.Recognition.OCR;
using OpenCvSharp;
using System;
using System.Collections.Generic;
using System.Text;
namespace BetterGenshinImpact.GameTask.Model.GameUI
{
public static class GridScreenExtensions
{
/// <summary>
/// 获取GridItem图标底部的文字
/// </summary>
/// <param name="mat"></param>
/// <returns></returns>
public static string GetGridItemIconText(this Mat mat, IOcrService ocrService)
{
Mat subMat = mat.SubMat(mat.Height * 128 / 153, mat.Height * 150 / 153, mat.Width * 5 / 125, mat.Width * 120 / 125);
using Mat resize = subMat.Resize(new Size(subMat.Width * 2, subMat.Height * 2));
return ocrService.Ocr(resize);
}
/// <summary>
/// 截取Grid图标中图案的部分
/// </summary>
/// <param name="mat"></param>
/// <returns></returns>
public static Mat GetGridIcon(this Mat mat)
{
using Mat resized = mat.Resize(new Size(125, 153));
return resized.SubMat(0, 125, 0, 125).Clone();
}
}
}

View File

@@ -1,4 +1,4 @@
using System;
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Text;
@@ -26,6 +26,8 @@ namespace BetterGenshinImpact.GameTask.Model.GameUI
[Description("摆设")]
Furnishings,
[Description("圣遗物分解")]
ArtifactSalvage
ArtifactSalvage,
[Description("圣遗物套装筛选")]
ArtifactSetFilter
}
}

View File

@@ -1,74 +0,0 @@
using BetterGenshinImpact.GameTask.Model.Area;
using OpenCvSharp;
using System;
using System.Collections.Frozen;
using System.Collections.Generic;
using System.Text;
namespace BetterGenshinImpact.GameTask.Model.GameUI
{
public class GridScreenParams
{
internal int X1080p { get; private set; }
internal int Y1080p { get; private set; }
internal int Width1080p { get; private set; }
internal int Height1080p { get; private set; }
internal int Columns { get; private set; }
internal int S1Round { get; private set; }
internal int RoundMilliseconds { get; private set; }
internal int S2Round { get; private set; }
internal double S3Scale { get; private set; }
internal Rect GetRect(ImageRegion gameScreen)
{
float scale = gameScreen.Height / 1080f;
return new Rect((int)(scale * X1080p), (int)(scale * Y1080p), (int)(scale * Width1080p), (int)(scale * Height1080p));
}
private static readonly GridScreenParams weapons = new GridScreenParams()
{
X1080p = 106,
Y1080p = 110,
Width1080p = 1171,
Height1080p = 845,
Columns = 8,
S1Round = 3,
RoundMilliseconds = 40,
S2Round = 32,
S3Scale = 0.024
};
internal static FrozenDictionary<GridScreenName, GridScreenParams> Templates { get; } = new Dictionary<GridScreenName, GridScreenParams>() {
{ GridScreenName.Weapons, weapons },
{ GridScreenName.Artifacts, new GridScreenParams(){
X1080p = 106,
Y1080p = 162,
Width1080p = 1171,
Height1080p = 783,
Columns = 8,
S1Round = 3,
RoundMilliseconds = 40,
S2Round = 32,
S3Scale = 0.024
}},
{ GridScreenName.CharacterDevelopmentItems, weapons },
{ GridScreenName.Food, weapons },
{ GridScreenName.Materials, weapons },
{ GridScreenName.Gadget, weapons },
{ GridScreenName.Quest, weapons },
{ GridScreenName.PreciousItems, weapons },
{ GridScreenName.Furnishings, weapons },
{ GridScreenName.ArtifactSalvage, new GridScreenParams(){
X1080p = 48,
Y1080p = 106,
Width1080p = 1267,
Height1080p = 768,
Columns = 9,
S1Round = 3,
RoundMilliseconds = 40,
S2Round = 28,
S3Scale = 0.018
}}
}.ToFrozenDictionary();
}
}

View File

@@ -0,0 +1,122 @@
using BetterGenshinImpact.Core.Simulator;
using BetterGenshinImpact.GameTask.Common;
using BetterGenshinImpact.GameTask.Model.Area;
using Fischless.WindowsInput;
using Microsoft.Extensions.Logging;
using OpenCvSharp;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
namespace BetterGenshinImpact.GameTask.Model.GameUI
{
/// <summary>
/// Grid界面垂直滚动服务类
/// </summary>
public class GridScroller
{
private readonly Rect roi;
private readonly CancellationToken ct;
private readonly ILogger logger;
private readonly InputSimulator input = Simulation.SendInput;
private readonly int columns;
private readonly int s1Round;
private readonly int roundMilliseconds;
private readonly int s2Round;
private readonly double s3Scale;
internal GridScroller(GridParams @params, ILogger logger, InputSimulator input, CancellationToken ct)
{
this.roi = @params.Roi;
this.ct = ct;
this.logger = logger;
this.input = input;
this.columns = @params.Columns;
this.s1Round = @params.S1Round;
this.roundMilliseconds = @params.RoundMilliseconds;
this.s2Round = @params.S2Round;
this.s3Scale = @params.S3Scale;
}
internal async Task<bool> TryVerticalScollDown(Func<Mat, int, IEnumerable<Rect>> GetGridItems)
{
using var ra = TaskControl.CaptureToRectArea();
using ImageRegion prevGrid = ra.DeriveCrop(roi);
for (int i = 0; i < this.s1Round; i++)
{
this.input.Mouse.VerticalScroll(-2);
await TaskControl.Delay(this.roundMilliseconds, this.ct);
}
await TaskControl.Delay(300, this.ct);
using var ra2 = TaskControl.CaptureToRectArea();
using ImageRegion scrolledGrid = ra2.DeriveCrop(this.roi);
bool isScrolling = IsScrolling(prevGrid.CacheGreyMat, scrolledGrid.CacheGreyMat, out Point2d shift, logger: this.logger);
if (isScrolling)
{
for (int i = 0; i < this.s2Round; i++) // 再滚动差不多(最多行数-1
{
input.Mouse.VerticalScroll(-2);
await TaskControl.Delay(this.roundMilliseconds, ct);
}
DateTimeOffset rollingEndTime = DateTime.Now.AddSeconds(2);
while (DateTime.Now < rollingEndTime)
{
await TaskControl.Delay(60, ct);
using var ra4 = TaskControl.CaptureToRectArea();
using ImageRegion grid2 = ra4.DeriveCrop(this.roi);
IEnumerable<Rect> gridItems2 = GetGridItems(grid2.SrcMat, this.columns);
if (gridItems2.Min(i => i.Y) > (ra4.Width * this.s3Scale)) // 最后精细滚动,保证完整地显示最多行
{
input.Mouse.VerticalScroll(-1);
}
else
{
break;
}
}
using var ra3 = TaskControl.CaptureToRectArea();
using ImageRegion grid3 = ra3.DeriveCrop(this.roi);
grid3.MoveTo(grid3.Width, grid3.Height);
await TaskControl.Delay(300, ct);
return true;
}
else
{
await TaskControl.Delay(300, ct);
this.logger.LogInformation("滚动到底部了");
return false;
}
}
/// <summary>
/// 判断是否还能继续滚动,如果到底了则只能滚动一丝并很快地回弹
/// </summary>
/// <param name="prevGray">先前的灰度图</param>
/// <param name="nextGray">尝试滚动并等待可能的回弹后的灰度图</param>
/// <param name="shift">估计的位移</param>
/// <param name="lowerThreshold">低于下限则可能不存在平移</param>
/// <param name="upperThreshold">上限用于抵消微小的其他差异,比如高亮图标的呼吸闪烁</param>
/// <param name="logger"></param>
/// <returns></returns>
public static bool IsScrolling(Mat prevGray, Mat nextGray, out Point2d shift, double lowerThreshold = 0.5, double upperThreshold = 0.95, ILogger? logger = null)
{
using Mat prev = new Mat();
prevGray.ConvertTo(prev, MatType.CV_32FC1);
using Mat next = new Mat();
nextGray.ConvertTo(next, MatType.CV_32FC1);
using Mat window = new Mat();
shift = Cv2.PhaseCorrelate(prev, next, window, out double response);
// 相位相关性
//logger?.LogInformation($"response={response:F3}, shift=({shift.X:F2}, {shift.Y:F2})");
return response > lowerThreshold && response < upperThreshold;
}
}
}

View File

@@ -3,9 +3,9 @@ using BetterGenshinImpact.GameTask.Model;
namespace BetterGenshinImpact.GameTask.Shell;
public class ShellTaskParam : BaseTaskParam
public class ShellTaskParam : BaseTaskParam<ShellTask>
{
private ShellTaskParam(string command, int configTimeoutSeconds, bool configNoWindow, bool configOutput, bool configDisable)
private ShellTaskParam(string command, int configTimeoutSeconds, bool configNoWindow, bool configOutput, bool configDisable) : base(null, null)
{
Command = command;
TimeoutSeconds = configTimeoutSeconds;

View File

@@ -4,6 +4,19 @@ namespace BetterGenshinImpact.Helpers;
public class DirectoryHelper
{
/// <summary>
/// 删除指定目录(如果存在)
/// </summary>
/// <param name="directoryPath">要删除的目录路径</param>
/// <param name="recursive">是否递归删除子目录和文件默认为true</param>
public static void DeleteDirectory(string directoryPath, bool recursive = true)
{
if (Directory.Exists(directoryPath))
{
Directory.Delete(directoryPath, recursive);
}
}
public static void DeleteReadOnlyDirectory(string directoryPath)
{
if (Directory.Exists(directoryPath))

View File

@@ -1,8 +1,10 @@
using BetterGenshinImpact.Model;
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Diagnostics.CodeAnalysis;
using System.Linq;
using BetterGenshinImpact.Model;
using System.Reflection;
namespace BetterGenshinImpact.Helpers.Extensions;
@@ -18,6 +20,33 @@ public static class EnumExtensions
?.Description ?? value.ToString();
}
public static bool TryGetEnumValueFromDescription<T>(this string description, [NotNullWhen(true)] out T? result) where T : struct, Enum
{
result = null;
if (string.IsNullOrEmpty(description))
return false;
var type = typeof(T);
foreach (var field in type.GetFields(BindingFlags.Public | BindingFlags.Static))
{
if (Attribute.GetCustomAttribute(field, typeof(DescriptionAttribute))
is DescriptionAttribute attribute)
{
if (attribute.Description.Equals(description, StringComparison.OrdinalIgnoreCase))
{
var value = field.GetValue(null);
if (value is T enumValue)
{
result = enumValue;
return true;
}
return false;
}
}
}
return false;
}
public static int GetOrder(this Enum value)
{
return value.GetType()

View File

@@ -39,12 +39,44 @@ public class ScriptObjectConverter
}
}
}
public static T GetValue<T>(ScriptObject source, string propertyName, T defaultValue)
{
if (source[propertyName] is not Undefined && source[propertyName] != null)
{
object value = source.GetProperty(propertyName);
Type type = typeof(T);
type = Nullable.GetUnderlyingType(type) ?? type;
if (type.IsEnum)
{
// 处理数字
if (value is int intValue)
{
if (Enum.IsDefined(type, intValue))
{
return (T)Enum.ToObject(type, intValue);
}
else
{
return defaultValue;
}
}
// 处理字符串
else if (value is string strValue)
{
if (Enum.TryParse(type, strValue, ignoreCase: true, out object? parsedEnum))
{
return (T)parsedEnum;
}
else
{
return defaultValue;
}
}
}
return (T)value;
}
return defaultValue;

View File

@@ -24,12 +24,12 @@ public class WindowHelper
switch (themeType)
{
case ThemeType.DarkNone:
window.Background = new SolidColorBrush(Colors.Transparent);
window.Background = new SolidColorBrush(Color.FromArgb(255, 32, 32, 32));
WindowBackdrop.ApplyBackdrop(window, WindowBackdropType.None);
break;
case ThemeType.LightNone:
window.Background = new SolidColorBrush(Colors.Transparent);
window.Background = new SolidColorBrush(Color.FromArgb(255, 243, 243, 243));
WindowBackdrop.ApplyBackdrop(window, WindowBackdropType.None);
break;

View File

@@ -213,8 +213,7 @@
Command="{Binding SwitchBackdropCommand}"
CornerRadius="0"
Icon="{ui:SymbolIcon Blur24}"
ToolTip="切换深浅主题背景和毛玻璃效果"
Visibility="{Binding IsWin11Later, Converter={StaticResource BooleanToVisibilityConverter}, Mode=OneWay}" />
ToolTip="切换深浅主题背景样式" />
<ui:Button Width="46"
Height="32"
Background="Transparent"

View File

@@ -877,7 +877,7 @@
</ui:CardExpander>
<!-- 新增手动导入本地脚本仓库功能 -->
<ui:CardControl Margin="0,0,0,12" Icon="{ui:SymbolIcon Folder24}">
<!--<ui:CardControl Margin="0,0,0,12" Icon="{ui:SymbolIcon Folder24}">
<ui:CardControl.Header>
<Grid>
<Grid.RowDefinitions>
@@ -907,7 +907,7 @@
</Grid>
</ui:CardControl.Header>
</ui:CardControl>
</ui:CardControl>-->
<!-- 其他设置 -->

View File

@@ -2339,10 +2339,11 @@
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="2*" />
<ColumnDefinition Width="3*" />
<ColumnDefinition Width="*" />
<ColumnDefinition Width="Auto" />
</Grid.ColumnDefinitions>
<ui:TextBlock Grid.Row="0"
Grid.Column="0"
@@ -2359,18 +2360,55 @@
Foreground="{ui:ThemeResource TextFillColorTertiaryBrush}"
TextWrapping="Wrap">
只要满足的圣遗物都会被选中
<LineBreak/>
JS接受ArtifactStat作为入参应对Output赋值一个布尔值作为返回
</ui:TextBlock>
<ui:TextBox Grid.Row="0"
<ui:Button Grid.Row="0"
Grid.RowSpan="2"
Grid.Column="1"
Margin="0,0,36,0"
Content="从脚本仓库复制"
Command="{Binding CopyArtifactSalvageJavaScriptFromRepositoryCommand}" />
<ui:TextBox Grid.Row="2"
Grid.RowSpan="2"
Grid.Column="1"
Grid.Column="0"
Grid.ColumnSpan="2"
MinWidth="180"
MaxWidth="800"
Margin="0,0,36,0"
Text="{Binding Config.AutoArtifactSalvageConfig.JavaScript, Mode=TwoWay}"
TextWrapping="Wrap" Cursor="IBeam" />
</Grid>
<Grid Margin="16">
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*" />
<ColumnDefinition Width="Auto" />
</Grid.ColumnDefinitions>
<ui:TextBlock Grid.Row="0"
Grid.Column="0"
FontTypography="Body"
TextWrapping="Wrap">
按套装筛选
</ui:TextBlock>
<ui:TextBlock Grid.Row="1"
Grid.Column="0"
Foreground="{ui:ThemeResource TextFillColorTertiaryBrush}"
TextWrapping="Wrap">
利用游戏自带的筛选功能先行筛选
<LineBreak/>
一般填写套装内生之花名,可填入多个名称;留空则不用
</ui:TextBlock>
<ui:TextBox Grid.Row="1"
Grid.RowSpan="2"
Grid.Column="1"
MinWidth="180"
MaxWidth="400"
Margin="0,0,36,0"
Text="{Binding Config.AutoArtifactSalvageConfig.ArtifactSetFilter, Mode=TwoWay}"
TextWrapping="Wrap" Cursor="IBeam" />
</Grid>
<Grid Margin="16">
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
@@ -2426,6 +2464,35 @@
Value="{Binding Config.AutoArtifactSalvageConfig.MaxNumToCheck, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"
Text="{Binding Config.AutoArtifactSalvageConfig.MaxNumToCheck, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" />
</Grid>
<Grid Margin="16">
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*" />
<ColumnDefinition Width="Auto" />
</Grid.ColumnDefinitions>
<ui:TextBlock Grid.Row="0"
Grid.Column="0"
FontTypography="Body"
Text="识别失败策略"
TextWrapping="Wrap" />
<ui:TextBlock Grid.Row="1"
Grid.Column="0"
Foreground="{ui:ThemeResource TextFillColorTertiaryBrush}"
Text="识别单个圣遗物面板信息失败时,是跳过还是终止"
TextWrapping="Wrap" />
<ComboBox Grid.Row="0"
Grid.RowSpan="2"
Grid.Column="1"
Width="100"
Margin="0,0,36,0"
ItemsSource="{Binding RecognitionFailurePolicyDict}"
SelectedValuePath="Key"
DisplayMemberPath="Value"
SelectedItem="{Binding Config.AutoArtifactSalvageConfig.RecognitionFailurePolicy, Converter={StaticResource EnumToKVPConverter}, Mode=TwoWay}" />
</Grid>
</StackPanel>
</ui:CardExpander>

View File

@@ -1445,8 +1445,14 @@
<ui:TextBlock Grid.Row="0"
Grid.Column="0"
FontTypography="Body"
Text="自动吃食物配置"
TextWrapping="Wrap" />
Text=""
TextWrapping="Wrap">
自动吃食物配置 -
<Hyperlink Command="{Binding GoToAutoEatUrlCommand}"
Foreground="{ui:ThemeResource TextFillColorSecondaryBrush}">
点击查看调用方法
</Hyperlink>
</ui:TextBlock>
<ui:TextBlock Grid.Row="1"
Grid.Column="0"
Foreground="{ui:ThemeResource TextFillColorTertiaryBrush}"

View File

@@ -1,4 +1,4 @@
<ui:FluentWindow x:Class="BetterGenshinImpact.View.Windows.OcrDialog"
<ui:FluentWindow x:Class="BetterGenshinImpact.View.Windows.OcrDialog"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
@@ -35,8 +35,15 @@
<Image Name="Screenshot" Source="pack://application:,,,/Resources/Images/logo.png" Stretch="None" Margin="3"/>
</Grid>
<StackPanel Margin="12">
<ui:TextBox Name="TxtRecognized" Margin="5" />
<ui:TextBox Name="RegexResult" Margin="5" />
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*"></ColumnDefinition>
<ColumnDefinition Width="*"></ColumnDefinition>
</Grid.ColumnDefinitions>
<ui:TextBox Grid.Column="0" Name="TxtRecognized" Margin="2" />
<ui:TextBox Grid.Column="1" Name="ModelStructure" Margin="2" />
</Grid>
<ui:TextBox Name="RegexResult" Margin="2" />
<StackPanel Margin="5"
HorizontalAlignment="Right"
Orientation="Horizontal">

View File

@@ -3,8 +3,12 @@ using BetterGenshinImpact.GameTask;
using BetterGenshinImpact.GameTask.AutoArtifactSalvage;
using BetterGenshinImpact.GameTask.Common;
using BetterGenshinImpact.Helpers.Extensions;
using OpenCvSharp;
using System;
using System.Globalization;
using System.IO;
using System.Windows;
using System.Windows.Controls;
namespace BetterGenshinImpact.View.Windows;
@@ -15,6 +19,8 @@ public partial class OcrDialog
private readonly double widthRatio;
private readonly double heightRatio;
private readonly string? javaScript;
private readonly AutoArtifactSalvageTask autoArtifactSalvageTask;
public OcrDialog(double xRatio, double yRatio, double widthRatio, double heightRatio, string title, string? javaScript = null)
{
this.xRatio = xRatio;
@@ -22,6 +28,7 @@ public partial class OcrDialog
this.widthRatio = widthRatio;
this.heightRatio = heightRatio;
this.javaScript = javaScript;
this.autoArtifactSalvageTask = new AutoArtifactSalvageTask(new AutoArtifactSalvageTaskParam(5, null, null, null, null, new CultureInfo(TaskContext.Instance().Config.OtherConfig.GameCultureInfoName)));
InitializeComponent();
@@ -33,16 +40,46 @@ public partial class OcrDialog
{
using var ra = TaskControl.CaptureToRectArea();
using var card = ra.DeriveCrop(new OpenCvSharp.Rect((int)(ra.Width * xRatio), (int)(ra.Height * yRatio), (int)(ra.Width * widthRatio), (int)(ra.Height * heightRatio)));
//Cv2.ImWrite($"{DateTime.Now.ToString("yyyyMMddHHmm")}_GetArtifactStat.png", card.SrcMat);
var bitmapImage = card.SrcMat.ToWriteableBitmap();
this.Screenshot.Source = bitmapImage;
ArtifactStat artifact = AutoArtifactSalvageTask.GetArtifactStat(card.SrcMat, OcrFactory.Paddle, new CultureInfo(TaskContext.Instance().Config.OtherConfig.GameCultureInfoName), out string allText);
this.TxtRecognized.Text = allText;
if (this.javaScript != null)
try
{
bool isMatch = AutoArtifactSalvageTask.IsMatchJavaScript(artifact, this.javaScript);
this.RegexResult.Text = isMatch ? "匹配" : "不匹配";
ArtifactStat artifact = this.autoArtifactSalvageTask.GetArtifactStat(card.SrcMat, OcrFactory.Paddle, out string allText);
this.TxtRecognized.Text = allText;
this.ModelStructure.Text = artifact.ToStructuredString();
if (this.javaScript != null)
{
bool isMatch = AutoArtifactSalvageTask.IsMatchJavaScript(artifact, this.javaScript);
this.RegexResult.Text = isMatch ? "匹配" : "不匹配";
}
}
catch (Exception e)
{
var multilineTextBox = new TextBox
{
TextWrapping = TextWrapping.Wrap,
VerticalScrollBarVisibility = ScrollBarVisibility.Auto,
Text = e.ToString(),
IsReadOnly = true
};
var p = new PromptDialog($"出错了:{e.Message}\r\n\r\n是否保存该圣遗物截图至log/autoArtifactSalvageException/", $"异常处理", multilineTextBox, null);
p.Height = 600;
p.MaxWidth = 800;
p.ShowDialog();
if (p.DialogResult == true)
{
string directory = Path.Combine(AppContext.BaseDirectory, "log/autoArtifactSalvageException");
Directory.CreateDirectory(directory);
string filePath = Path.Combine(directory, $"{DateTime.Now.ToString("yyyyMMddHHmmss")}_GetArtifactStat.png");
Cv2.ImWrite(filePath, card.SrcMat);
}
throw;
}
this.UpdateLayout();
}

View File

@@ -7,7 +7,7 @@
xmlns:ui="http://schemas.lepo.co/wpfui/2022/xaml"
xmlns:vio="http://schemas.lepo.co/wpfui/2022/xaml/violeta"
Title="脚本仓库"
Width="400"
Width="410"
MinWidth="360"
MinHeight="50"
ResizeMode="NoResize"
@@ -36,107 +36,220 @@
<!-- 主内容区域 -->
<Grid Grid.Row="1" Margin="12,0,12,12">
<Border Background="{ui:ThemeResource CardBackgroundFillColorDefaultBrush}"
<Grid.RowDefinitions>
<RowDefinition Height="*" />
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
<!-- 标签页区域 -->
<Border Grid.Row="0"
Background="{ui:ThemeResource CardBackgroundFillColorDefaultBrush}"
BorderBrush="{ui:ThemeResource CardStrokeColorDefaultBrush}"
BorderThickness="1,1,1,1"
CornerRadius="8">
<Grid Margin="12">
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
<TabControl Margin="12">
<!-- Git仓库标签页 -->
<TabItem Width="110">
<TabItem.Header>
<StackPanel Orientation="Horizontal">
<ui:SymbolIcon Margin="0,0,6,0" Symbol="BranchFork24" />
<TextBlock Text="Git一键更新" />
</StackPanel>
</TabItem.Header>
<Grid Margin="0,12,0,0">
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
<!-- 第一行:更新渠道 -->
<Grid Grid.Row="0" Margin="0,0,0,12">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto" />
<ColumnDefinition Width="*" />
</Grid.ColumnDefinitions>
<!-- 第一行:更新渠道 -->
<Grid Grid.Row="0" Margin="0,0,0,12">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto" />
<ColumnDefinition Width="*" />
</Grid.ColumnDefinitions>
<ui:TextBlock Grid.Column="0"
Margin="0,0,8,0"
VerticalAlignment="Center"
Foreground="{ui:ThemeResource TextFillColorPrimaryBrush}"
Text="更新渠道:" />
<ComboBox Grid.Column="1"
MinWidth="160"
VerticalAlignment="Center"
DisplayMemberPath="Name"
ItemsSource="{Binding RepoChannels}"
SelectedItem="{Binding SelectedRepoChannel}" />
</Grid>
<ui:TextBlock Grid.Column="0"
Margin="0,0,8,0"
VerticalAlignment="Center"
Foreground="{ui:ThemeResource TextFillColorPrimaryBrush}"
Text="更新渠道:" />
<ComboBox Grid.Column="1"
MinWidth="160"
VerticalAlignment="Center"
DisplayMemberPath="Name"
ItemsSource="{Binding RepoChannels}"
SelectedItem="{Binding SelectedRepoChannel}" />
</Grid>
<!-- 第二行脚本仓库URL -->
<Grid Grid.Row="1" Margin="0,0,0,12">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto" />
<ColumnDefinition Width="*" />
</Grid.ColumnDefinitions>
<!-- 第二行脚本仓库URL -->
<Grid Grid.Row="1" Margin="0,0,0,12">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto" />
<ColumnDefinition Width="*" />
</Grid.ColumnDefinitions>
<ui:TextBlock Grid.Column="0"
Margin="0,0,8,0"
VerticalAlignment="Center"
Foreground="{ui:ThemeResource TextFillColorPrimaryBrush}"
Text="仓库地址:" />
<TextBox Grid.Column="1"
MinWidth="160"
VerticalAlignment="Center"
IsEnabled="{Binding IsRepoUrlReadOnly, Converter={StaticResource InverseBooleanConverter}}"
IsReadOnly="{Binding IsRepoUrlReadOnly}"
Text="{Binding Config.SelectedRepoUrl}" />
</Grid>
<ui:TextBlock Grid.Column="0"
Margin="0,0,8,0"
VerticalAlignment="Center"
Foreground="{ui:ThemeResource TextFillColorPrimaryBrush}"
Text="仓库地址:" />
<TextBox Grid.Column="1"
MinWidth="160"
VerticalAlignment="Center"
IsEnabled="{Binding IsRepoUrlReadOnly, Converter={StaticResource InverseBooleanConverter}}"
IsReadOnly="{Binding IsRepoUrlReadOnly}"
Text="{Binding Config.SelectedRepoUrl}" />
</Grid>
<!-- 第三行:按钮组 -->
<Grid Grid.Row="2" Margin="0,0,0,8">
<Grid.ColumnDefinitions>
<ColumnDefinition/>
<ColumnDefinition/>
<ColumnDefinition/>
</Grid.ColumnDefinitions>
<!-- 第三行:按钮组 -->
<Grid Grid.Row="2" Margin="0,0,0,8">
<Grid.ColumnDefinitions>
<ColumnDefinition />
<ColumnDefinition />
</Grid.ColumnDefinitions>
<ui:Button Grid.Column="0"
Margin="0,0,4,0"
Command="{Binding UpdateRepoCommand}"
Content="更新仓库"
Icon="{ui:SymbolIcon CloudSync24}"
HorizontalAlignment="Stretch" />
<ui:Button Grid.Column="1"
Margin="4,0,4,0"
Command="{Binding ResetRepoCommand}"
Content="重置仓库"
Icon="{ui:SymbolIcon ArrowReset24}"
HorizontalAlignment="Stretch"/>
<ui:Button Grid.Column="2"
Margin="4,0,0,0"
Appearance="Primary"
Command="{Binding OpenLocalScriptRepoCommand}"
Content="打开仓库"
Icon="{ui:SymbolIcon BookStar24}"
HorizontalAlignment="Stretch"/>
</Grid>
<ui:Button Grid.Column="0"
Margin="0,0,4,0"
Command="{Binding UpdateRepoCommand}"
Content="更新仓库"
Icon="{ui:SymbolIcon CloudSync24}"
HorizontalAlignment="Stretch" />
<ui:Button Grid.Column="1"
Margin="4,0,0,0"
Command="{Binding ResetRepoCommand}"
Content="重置仓库"
Icon="{ui:SymbolIcon ArrowReset24}"
HorizontalAlignment="Stretch" />
</Grid>
<!-- 进度条区域 -->
<Grid Grid.Row="3"
Margin="0,8,0,4"
Visibility="{Binding IsUpdating, Converter={StaticResource BooleanToVisibilityConverter}}">
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
<ui:TextBlock Grid.Row="0"
Margin="0,0,0,4"
VerticalAlignment="Center"
Foreground="{ui:ThemeResource TextFillColorSecondaryBrush}"
Text="{Binding UpdateProgressText}" />
<ProgressBar Grid.Row="1"
Maximum="100"
Value="{Binding UpdateProgressValue}" />
</Grid>
</Grid>
</Grid>
</TabItem>
<!-- 在线更新标签页 -->
<!--<TabItem Width="100">
<TabItem.Header>
<StackPanel Orientation="Horizontal">
<ui:SymbolIcon Margin="0,0,6,0" Symbol="DocumentArrowDown20" />
<TextBlock Text="URL更新" />
</StackPanel>
</TabItem.Header>
<Grid Margin="0,12,0,0">
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
<ui:TextBlock Grid.Row="0"
Margin="0,0,0,12"
Foreground="{ui:ThemeResource TextFillColorSecondaryBrush}"
TextWrapping="Wrap"
Text="从在线地址直接下载脚本仓库压缩包,适用于网络环境较好的情况。" />
<Grid Grid.Row="1" Margin="0,0,0,12">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto" />
<ColumnDefinition Width="*" />
</Grid.ColumnDefinitions>
<ui:TextBlock Grid.Column="0"
Margin="0,0,8,0"
VerticalAlignment="Center"
Foreground="{ui:ThemeResource TextFillColorPrimaryBrush}"
Text="下载地址:" />
<TextBox Grid.Column="1"
MinWidth="160"
VerticalAlignment="Center"
Text="{Binding OnlineDownloadUrl}" />
</Grid>
<ui:Button Grid.Row="2"
Command="{Binding DownloadOnlineRepoCommand}"
Content="下载并导入"
Icon="{ui:SymbolIcon Cloud24}"
HorizontalAlignment="Left" />
</Grid>
</TabItem>-->
<!-- 手动导入标签页 -->
<TabItem Width="100">
<TabItem.Header>
<StackPanel Orientation="Horizontal">
<ui:SymbolIcon Margin="0,0,6,0" Symbol="FolderZip24" />
<TextBlock Text="手动导入" />
</StackPanel>
</TabItem.Header>
<Grid Margin="0,12,0,0">
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
<ui:TextBlock Grid.Row="0"
Margin="0,0,0,12"
Foreground="{ui:ThemeResource TextFillColorSecondaryBrush}"
TextWrapping="Wrap"
Text="从本地zip文件导入脚本仓库适用于离线环境或网络不稳定的情况。" />
<ui:TextBlock Grid.Row="1"
Margin="0,0,0,12"
Foreground="{ui:ThemeResource TextFillColorTertiaryBrush}"
TextWrapping="Wrap"
Text="请选择包含脚本仓库内容的zip压缩包文件进行导入。" />
<ui:TextBlock Grid.Row="2"
Margin="0,0,0,12"
Foreground="{ui:ThemeResource TextFillColorSecondaryBrush}"
TextWrapping="Wrap">
<Run Text="可以从这里获取zip" />
<Hyperlink NavigateUri="https://bettergi.com/feats/autos/srepo.html" RequestNavigate="Hyperlink_RequestNavigate">
<Run Text="脚本仓库文档" />
</Hyperlink>
</ui:TextBlock>
<ui:Button Grid.Row="3"
Command="{Binding ImportLocalScriptsRepoZipCommand}"
Content="选择zip文件导入"
Icon="{ui:SymbolIcon FolderZip24}"
HorizontalAlignment="Left" />
</Grid>
</TabItem>
</TabControl>
</Border>
<!-- 进度条区域 -->
<Grid Grid.Row="1"
Margin="0,12,0,8"
Visibility="{Binding IsUpdating, Converter={StaticResource BooleanToVisibilityConverter}}">
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
<ui:TextBlock Grid.Row="0"
Margin="0,0,0,4"
VerticalAlignment="Center"
Foreground="{ui:ThemeResource TextFillColorSecondaryBrush}"
Text="{Binding UpdateProgressText}" />
<ProgressBar Grid.Row="1"
Maximum="100"
Value="{Binding UpdateProgressValue}" />
</Grid>
<!-- 公共按钮区域 -->
<Grid Grid.Row="2" Margin="0,4,0,0">
<ui:Button Appearance="Primary"
Command="{Binding OpenLocalScriptRepoCommand}"
Content="打开仓库"
Icon="{ui:SymbolIcon BookStar24}"
HorizontalAlignment="Center" />
</Grid>
</Grid>
</Grid>
</ui:FluentWindow>

View File

@@ -5,13 +5,18 @@ using BetterGenshinImpact.Helpers;
using BetterGenshinImpact.Helpers.Ui;
using CommunityToolkit.Mvvm.ComponentModel;
using CommunityToolkit.Mvvm.Input;
using Microsoft.Win32;
using System;
using System.Collections.ObjectModel;
using System.ComponentModel;
using System.Diagnostics;
using System.IO;
using System.IO.Compression;
using System.Linq;
using System.Net.Http;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Navigation;
using Wpf.Ui.Violeta.Controls;
namespace BetterGenshinImpact.View.Windows;
@@ -48,6 +53,9 @@ public partial class ScriptRepoWindow
[ObservableProperty] private string _updateProgressText = "准备更新,请耐心等待...";
[ObservableProperty] private ScriptConfig _config = TaskContext.Instance().Config.ScriptConfig;
// 在线更新相关属性
[ObservableProperty] private string _onlineDownloadUrl = "";
public ScriptRepoWindow()
{
InitializeRepoChannels();
@@ -250,4 +258,212 @@ public partial class ScriptRepoWindow
}
}
}
/*
[RelayCommand]
private async Task DownloadOnlineRepo()
{
if (string.IsNullOrWhiteSpace(OnlineDownloadUrl))
{
Toast.Warning("请输入有效的下载地址。");
return;
}
if (IsUpdating)
{
Toast.Warning("请等待当前操作完成后再进行下载。");
return;
}
try
{
IsUpdating = true;
UpdateProgressValue = 0;
UpdateProgressText = "正在下载脚本仓库...";
using var httpClient = new HttpClient();
httpClient.Timeout = TimeSpan.FromMinutes(10);
// 下载文件
var response = await httpClient.GetAsync(OnlineDownloadUrl);
response.EnsureSuccessStatusCode();
var tempZipPath = Path.Combine(Path.GetTempPath(), $"script_repo_{DateTime.Now:yyyyMMddHHmmss}.zip");
await using (var fileStream = File.Create(tempZipPath))
{
await response.Content.CopyToAsync(fileStream);
}
UpdateProgressText = "正在解压并导入...";
UpdateProgressValue = 50;
// 导入下载的zip文件
await ImportZipFile(tempZipPath);
// 清理临时文件
if (File.Exists(tempZipPath))
{
File.Delete(tempZipPath);
}
Toast.Success("在线脚本仓库下载并导入成功!");
}
catch (Exception ex)
{
Toast.Error($"下载失败: {ex.Message}");
}
finally
{
IsUpdating = false;
}
}*/
[RelayCommand]
private async Task ImportLocalScriptsRepoZip()
{
if (IsUpdating)
{
Toast.Warning("请等待当前操作完成后再进行导入。");
return;
}
try
{
var openFileDialog = new OpenFileDialog
{
Title = "选择脚本仓库压缩包",
Filter = "压缩包文件 (*.zip)|*.zip",
Multiselect = false
};
if (openFileDialog.ShowDialog() == true)
{
IsUpdating = true;
UpdateProgressValue = 0;
UpdateProgressText = "正在导入脚本仓库,请耐心等待...";
await ImportZipFile(openFileDialog.FileName);
Toast.Success("脚本仓库导入成功!");
}
}
catch (Exception ex)
{
Toast.Error($"导入失败: {ex.Message}");
}
finally
{
IsUpdating = false;
}
}
private async Task ImportZipFile(string zipFilePath)
{
await Task.Run(() =>
{
var tempPath = ScriptRepoUpdater.ReposTempPath;
try
{
// 阶段1: 准备工作 (0-10%)
Dispatcher.Invoke(() =>
{
UpdateProgressValue = 0;
UpdateProgressText = "正在准备导入环境...";
});
var tempUnzipDir = Path.Combine(tempPath, "importZipFile");
// 先删除临时目录
DirectoryHelper.DeleteReadOnlyDirectory(tempPath);
// 创建目标目录
Directory.CreateDirectory(tempUnzipDir);
Dispatcher.Invoke(() =>
{
UpdateProgressValue = 10;
UpdateProgressText = "准备完成,开始解压文件...";
});
// 阶段2: 解压zip文件 (10-50%)
ZipFile.ExtractToDirectory(zipFilePath, tempUnzipDir, true);
Dispatcher.Invoke(() =>
{
UpdateProgressValue = 50;
UpdateProgressText = "文件解压完成,正在验证仓库结构...";
});
// 阶段3: 查找并验证 repo.json (50-60%)
var repoJsonPath = Directory.GetFiles(tempUnzipDir, "repo.json", SearchOption.AllDirectories).FirstOrDefault();
if (repoJsonPath == null)
{
throw new FileNotFoundException("未找到 repo.json 文件,导入失败。");
}
var repoDir = Path.GetDirectoryName(repoJsonPath)!;
Dispatcher.Invoke(() =>
{
UpdateProgressValue = 60;
UpdateProgressText = "仓库结构验证通过,正在清理旧数据...";
});
// 阶段4: 删除旧的目标目录 (60-70%)
if (Directory.Exists(ScriptRepoUpdater.CenterRepoPath))
{
DirectoryHelper.DeleteReadOnlyDirectory(ScriptRepoUpdater.CenterRepoPath);
}
Dispatcher.Invoke(() =>
{
UpdateProgressValue = 70;
UpdateProgressText = "旧数据清理完成,正在复制新仓库...";
});
// 阶段5: 复制新目录 (70-95%)
DirectoryHelper.CopyDirectory(repoDir, ScriptRepoUpdater.CenterRepoPath);
Dispatcher.Invoke(() =>
{
UpdateProgressValue = 95;
UpdateProgressText = "仓库复制完成,正在清理临时文件...";
});
}
finally
{
// 阶段6: 清理临时文件 (95-100%)
DirectoryHelper.DeleteReadOnlyDirectory(tempPath);
}
});
// 最终完成
Dispatcher.Invoke(() =>
{
UpdateProgressValue = 100;
UpdateProgressText = "导入完成";
});
}
/// <summary>
/// 处理超链接点击事件
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
private void Hyperlink_RequestNavigate(object sender, RequestNavigateEventArgs e)
{
try
{
Process.Start(new ProcessStartInfo
{
FileName = e.Uri.AbsoluteUri,
UseShellExecute = true
});
}
catch (Exception ex)
{
MessageBox.Show($"无法打开链接: {ex.Message}", "错误", MessageBoxButton.OK, MessageBoxImage.Warning);
}
e.Handled = true;
}
}

View File

@@ -131,7 +131,30 @@ public partial class MainWindowViewModel : ObservableObject, IViewModel
private void ApplyTheme(ThemeType themeType)
{
var originalThemeType = themeType;
// 根据主题类型设置应用程序主题(深色/浅色和背景效果类型Mica/Acrylic/None
if (!OsVersionHelper.IsWindows11_22523_OrGreater)
{
// 22523以下版本只支持深浅色切换,修正背景材质为纯色
if (themeType == ThemeType.DarkMica || themeType == ThemeType.DarkAcrylic)
{
themeType = ThemeType.DarkNone;
}
else if (themeType == ThemeType.LightMica || themeType == ThemeType.LightAcrylic)
{
themeType = ThemeType.LightNone;
}
}
// 如果主题类型被修正,更新配置并保存
if (themeType != originalThemeType)
{
Config.CommonConfig.CurrentThemeType = themeType;
_configService.Save();
_logger.LogInformation($"主题类型已从 {originalThemeType} 修正为 {themeType},因为当前系统不支持该主题效果");
}
switch (themeType)
{
case ThemeType.DarkNone:

View File

@@ -1,43 +1,44 @@
using BetterGenshinImpact.Core.Config;
using BetterGenshinImpact.Core.Config;
using BetterGenshinImpact.Core.Recognition.OCR;
using BetterGenshinImpact.Core.Recognition.OpenCv;
using BetterGenshinImpact.Core.Recorder;
using BetterGenshinImpact.Core.Script;
using BetterGenshinImpact.Core.Simulator;
using BetterGenshinImpact.GameTask;
using BetterGenshinImpact.GameTask.AutoArtifactSalvage;
using BetterGenshinImpact.GameTask.AutoFight;
using BetterGenshinImpact.GameTask.AutoFight.Assets;
using BetterGenshinImpact.GameTask.AutoPathing;
using BetterGenshinImpact.GameTask.AutoPathing.Handler;
using BetterGenshinImpact.GameTask.AutoTrackPath;
using BetterGenshinImpact.GameTask.Common;
using BetterGenshinImpact.GameTask.Common.BgiVision;
using BetterGenshinImpact.GameTask.Common.Job;
using BetterGenshinImpact.GameTask.Common.Map.Maps.Base;
using BetterGenshinImpact.GameTask.Macro;
using BetterGenshinImpact.GameTask.Model.Area;
using BetterGenshinImpact.GameTask.QuickBuy;
using BetterGenshinImpact.GameTask.QuickSereniteaPot;
using BetterGenshinImpact.GameTask.QuickTeleport.Assets;
using BetterGenshinImpact.GameTask.UseRedeemCode;
using BetterGenshinImpact.Helpers;
using BetterGenshinImpact.Helpers.Extensions;
using BetterGenshinImpact.Model;
using BetterGenshinImpact.Service.Interface;
using BetterGenshinImpact.View;
using CommunityToolkit.Mvvm.ComponentModel;
using CommunityToolkit.Mvvm.Input;
using CommunityToolkit.Mvvm.Messaging;
using CommunityToolkit.Mvvm.Messaging.Messages;
using Microsoft.Extensions.Logging;
using OpenCvSharp;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Diagnostics;
using System.Globalization;
using System.Reflection;
using System.Threading;
using System.Threading.Tasks;
using BetterGenshinImpact.Core.Recognition.OCR;
using BetterGenshinImpact.Core.Recognition.OpenCv;
using BetterGenshinImpact.GameTask.AutoArtifactSalvage;
using BetterGenshinImpact.GameTask.AutoFight.Assets;
using BetterGenshinImpact.GameTask.Common.Map.Maps.Base;
using BetterGenshinImpact.GameTask.Model.Area;
using BetterGenshinImpact.GameTask.QuickBuy;
using BetterGenshinImpact.GameTask.QuickTeleport.Assets;
using BetterGenshinImpact.GameTask.UseRedeemCode;
using BetterGenshinImpact.View;
using OpenCvSharp;
using Vanara.PInvoke;
using HotKeySettingModel = BetterGenshinImpact.Model.HotKeySettingModel;
@@ -533,7 +534,7 @@ public partial class HotKeyPageViewModel : ObservableObject, IViewModel
if (pathRecording)
{
Task.Run(() => { pathRecorder.AddWaypoint(); });
}
}
));
@@ -583,7 +584,7 @@ public partial class HotKeyPageViewModel : ObservableObject, IViewModel
Config.HotKeyConfig.Test1HotkeyType,
(_, _) =>
{
Task.Run(async () => { await new AutoArtifactSalvageTask(4).Start(new CancellationToken()); });
Task.Run(async () => { await new AutoArtifactSalvageTask(new AutoArtifactSalvageTaskParam(star: 4, null, null, null, null)).Start(new CancellationToken()); });
}
));

View File

@@ -51,7 +51,7 @@ public partial class ScriptControlViewModel : ViewModel
private readonly ILogger<ScriptControlViewModel> _logger = App.GetLogger<ScriptControlViewModel>();
private readonly IScriptService _scriptService;
/// <summary>
/// 配置组配置
/// </summary>
@@ -143,13 +143,13 @@ public partial class ScriptControlViewModel : ViewModel
GameInfo? gameInfo = null;
var config = LogParse.LoadConfig();
OtherConfig.Miyoushe mcfg = TaskContext.Instance().Config.OtherConfig.MiyousheConfig;
if (mcfg.LogSyncCookie && !string.IsNullOrEmpty(mcfg.Cookie))
{
config.Cookie = mcfg.Cookie;
}
if (!string.IsNullOrEmpty(config.Cookie))
{
config.CookieDictionary.TryGetValue(config.Cookie, out gameInfo);
@@ -220,7 +220,7 @@ public partial class ScriptControlViewModel : ViewModel
VerticalAlignment = VerticalAlignment.Center
};
stackPanel.Children.Add(mergerStatsSwitch);
// 开关控件ToggleButton 或 CheckBox
CheckBox faultStatsSwitch = new CheckBox
{
@@ -228,21 +228,21 @@ public partial class ScriptControlViewModel : ViewModel
VerticalAlignment = VerticalAlignment.Center
};
stackPanel.Children.Add(faultStatsSwitch);
// 开关控件ToggleButton 或 CheckBox
CheckBox hoeingStatsSwitch = new CheckBox
{
Content = "统计锄地摩拉怪物数",
VerticalAlignment = VerticalAlignment.Center
};
CheckBox GenerateFarmingPlanData = new CheckBox
{
Content = "生成锄地规划数据",
VerticalAlignment = VerticalAlignment.Center
};
stackPanel.Children.Add(GenerateFarmingPlanData);
//firstRow.Children.Add(toggleSwitch);
// 将第一行添加到 StackPanel
@@ -338,7 +338,7 @@ public partial class ScriptControlViewModel : ViewModel
GenerateFarmingPlanData.IsChecked = sgpc.GenerateFarmingPlanData;
faultStatsSwitch.IsChecked = sgpc.FaultStatsSwitch;
mergerStatsSwitch.IsChecked = sgpc.MergerStatsSwitch;
hoeingDelayTextBox.Text = sgpc.HoeingDelay;
MessageBoxResult result = await uiMessageBox.ShowDialogAsync();
@@ -364,9 +364,9 @@ public partial class ScriptControlViewModel : ViewModel
if (mcfg.LogSyncCookie && !string.IsNullOrEmpty(cookieValue))
{
mcfg.Cookie = cookieValue;
mcfg.Cookie = cookieValue;
}
LogParse.WriteConfigFile(config);
@@ -383,7 +383,7 @@ public partial class ScriptControlViewModel : ViewModel
{
Application.Current.Dispatcher.Invoke(() =>
{
Toast.Information(status, time:5000);
Toast.Information(status, time: 5000);
});
}
@@ -465,7 +465,7 @@ public partial class ScriptControlViewModel : ViewModel
// 生成HTML并加载
win.NavigateToHtml(LogParse.GenerHtmlByConfigGroupEntity(configGroupEntities,
hoeingStats ? realGameInfo : null, sgpc));
win.ShowDialog();
win.ShowDialog();
// 取消订阅事件
LogParse.HtmlGenerationStatusChanged -= OnHtmlGenerationStatusChanged;
@@ -559,14 +559,14 @@ public partial class ScriptControlViewModel : ViewModel
private void ExportMergerJsons()
{
int count = 0;
var pathDir = Path.Combine(LogPath,"exportMergerJson",DateTimeOffset.UtcNow.ToUnixTimeSeconds().ToString(),"AutoPathing");
var pathDir = Path.Combine(LogPath, "exportMergerJson", DateTimeOffset.UtcNow.ToUnixTimeSeconds().ToString(), "AutoPathing");
foreach (var scriptGroupProject in SelectedScriptGroup?.Projects ?? [])
{
if (scriptGroupProject.Type == "Pathing")
{
var mergerJson= JsonMerger.getMergePathingJson(Path.Combine(MapPathingViewModel.PathJsonPath,
var mergerJson = JsonMerger.getMergePathingJson(Path.Combine(MapPathingViewModel.PathJsonPath,
scriptGroupProject.FolderName, scriptGroupProject.Name));
string fullPath = Path.Combine(pathDir,scriptGroupProject.FolderName,scriptGroupProject.Name);
string fullPath = Path.Combine(pathDir, scriptGroupProject.FolderName, scriptGroupProject.Name);
string dir = Path.GetDirectoryName(fullPath);
if (!Directory.Exists(dir))
{
@@ -576,13 +576,13 @@ public partial class ScriptControlViewModel : ViewModel
count++;
}
}
if (count>0)
if (count > 0)
{
Process.Start("explorer.exe", pathDir);
}
}
[RelayCommand]
public void AddScriptGroupNextFlag(ScriptGroup? item)
{
@@ -591,7 +591,7 @@ public partial class ScriptControlViewModel : ViewModel
scriptGroup.NextFlag = false;
}
if (item!=null)
if (item != null)
{
item.NextFlag = true;
TaskContext.Instance().Config.NextScriptGroupName = item.Name;
@@ -731,7 +731,7 @@ public partial class ScriptControlViewModel : ViewModel
private void OnAddJsScript()
{
var list = LoadAllJsScriptProjects();
var stackPanel = CreateJsScriptSelectionPanel(list);
var stackPanel = CreateJsScriptSelectionPanel(list, typeof(CheckBox));
var result = PromptDialog.Prompt("请选择需要添加的JS脚本", "请选择需要添加的JS脚本", stackPanel, new Size(500, 600));
if (!string.IsNullOrEmpty(result))
@@ -740,19 +740,19 @@ public partial class ScriptControlViewModel : ViewModel
}
}
private ScrollViewer CreateJsScriptSelectionPanel(List<ScriptProject> list)
internal static ScrollViewer CreateJsScriptSelectionPanel(List<ScriptProject> list, Type selectType)
{
var stackPanel = new StackPanel();
var filterTextBox = new TextBox
{
Margin = new Thickness(0, 0, 0, 10),
PlaceholderText = "输入搜索条件...",
};
filterTextBox.TextChanged += delegate { ApplyJsScriptFilter(stackPanel, list, filterTextBox.Text); };
filterTextBox.TextChanged += delegate { ApplyJsScriptFilter(stackPanel, list, filterTextBox.Text, selectType); };
stackPanel.Children.Add(filterTextBox);
AddJsScriptsToPanel(stackPanel, list, filterTextBox.Text);
AddJsScriptsToPanel(stackPanel, list, filterTextBox.Text, selectType);
var scrollViewer = new ScrollViewer
{
@@ -764,7 +764,7 @@ public partial class ScriptControlViewModel : ViewModel
return scrollViewer;
}
private void ApplyJsScriptFilter(StackPanel parentPanel, List<ScriptProject> scripts, string filter)
private static void ApplyJsScriptFilter(StackPanel parentPanel, List<ScriptProject> scripts, string filter, Type selectType)
{
if (parentPanel.Children.Count > 0)
{
@@ -780,16 +780,16 @@ public partial class ScriptControlViewModel : ViewModel
removeElements.ForEach(parentPanel.Children.Remove);
}
AddJsScriptsToPanel(parentPanel, scripts, filter);
AddJsScriptsToPanel(parentPanel, scripts, filter, selectType);
}
private void AddJsScriptsToPanel(StackPanel parentPanel, List<ScriptProject> scripts, string filter)
private static void AddJsScriptsToPanel(StackPanel parentPanel, List<ScriptProject> scripts, string filter, Type selectType)
{
foreach (var script in scripts)
{
var displayText = script.FolderName + " - " + script.Manifest.Name;
if (!string.IsNullOrEmpty(filter) &&
if (!string.IsNullOrEmpty(filter) &&
!displayText.Contains(filter, StringComparison.OrdinalIgnoreCase) &&
!script.FolderName.Contains(filter, StringComparison.OrdinalIgnoreCase) &&
!script.Manifest.Name.Contains(filter, StringComparison.OrdinalIgnoreCase))
@@ -797,15 +797,33 @@ public partial class ScriptControlViewModel : ViewModel
continue;
}
var checkBox = new CheckBox
if (selectType == typeof(CheckBox))
{
Content = displayText,
Tag = script.FolderName,
Margin = new Thickness(0, 2, 0, 2),
Name = "dynamic_" + Guid.NewGuid().ToString().Replace("-", "_")
};
parentPanel.Children.Add(checkBox);
var checkBox = new CheckBox
{
Content = displayText,
Tag = script.FolderName,
Margin = new Thickness(0, 2, 0, 2),
Name = "dynamic_" + Guid.NewGuid().ToString().Replace("-", "_")
};
parentPanel.Children.Add(checkBox);
}
else if (selectType == typeof(RadioButton))
{
var radioButton = new RadioButton
{
Content = displayText,
Tag = script.FolderName,
Margin = new Thickness(0, 2, 0, 2),
Name = "dynamic_" + Guid.NewGuid().ToString().Replace("-", "_"),
GroupName = "JsScriptsRadioButtonGroup"
};
parentPanel.Children.Add(radioButton);
}
else
{
throw new ArgumentOutOfRangeException();
}
}
}
@@ -844,7 +862,7 @@ public partial class ScriptControlViewModel : ViewModel
[RelayCommand]
private void OnAddShell()
{
var str = PromptDialog.Prompt("执行 shell 操作存在极大风险!请勿输入你看不懂的指令!以免引发安全隐患并损坏系统!\n执行 shell 的时候,游戏可能会失去焦点","请输入需要执行的shell");
var str = PromptDialog.Prompt("执行 shell 操作存在极大风险!请勿输入你看不懂的指令!以免引发安全隐患并损坏系统!\n执行 shell 的时候,游戏可能会失去焦点", "请输入需要执行的shell");
if (!string.IsNullOrEmpty(str))
{
SelectedScriptGroup?.AddProject(ScriptGroupProject.BuildShellProject(str));
@@ -1067,7 +1085,7 @@ public partial class ScriptControlViewModel : ViewModel
// 递归时,传递当前节点的匹配状态
// 每个子节点深度相同,所以如果递归过程中任意子节点应该显示,则当前节点也应该显示
if (ShouldShowNode(child, filter, isDeepSearch, currentDepth + 1, currentNodeMatches))
return true;
return true;
}
}
@@ -1248,7 +1266,7 @@ public partial class ScriptControlViewModel : ViewModel
// return result;
// }
private List<ScriptProject> LoadAllJsScriptProjects()
internal static List<ScriptProject> LoadAllJsScriptProjects()
{
var path = Global.ScriptPath();
Directory.CreateDirectory(path);
@@ -1394,13 +1412,13 @@ public partial class ScriptControlViewModel : ViewModel
}
[RelayCommand]
public async void OnDeleteScriptByFolder(ScriptGroupProject? item)
public async void OnDeleteScriptByFolder(ScriptGroupProject? item)
{
if (item == null)
{
return;
}
}
if (SelectedScriptGroup != null)
{
var toBeDeletedProjects = SelectedScriptGroup.Projects
@@ -1411,7 +1429,7 @@ public partial class ScriptControlViewModel : ViewModel
{
SelectedScriptGroup.Projects.Remove(project);
}
_snackbarService.Show(
"脚本配置移除成功",
$"已移除 {item.FolderName} 下的所有关联配置",
@@ -1657,13 +1675,13 @@ public partial class ScriptControlViewModel : ViewModel
RunnerContext.Instance.Reset();
TaskProgress taskProgress = new()
{
ScriptGroupNames = [SelectedScriptGroup.Name]
};
{
ScriptGroupNames = [SelectedScriptGroup.Name]
};
RunnerContext.Instance.taskProgress = taskProgress;
taskProgress.CurrentScriptGroupName = SelectedScriptGroup.Name;
TaskProgressManager.SaveTaskProgress(taskProgress);
await _scriptService.RunMulti(GetNextProjects(SelectedScriptGroup), SelectedScriptGroup.Name,taskProgress);
await _scriptService.RunMulti(GetNextProjects(SelectedScriptGroup), SelectedScriptGroup.Name, taskProgress);
}
[RelayCommand]
@@ -1737,7 +1755,7 @@ public partial class ScriptControlViewModel : ViewModel
{
SetTaskContextNextFlag(group);
List<ScriptGroupProject> ls = new List<ScriptGroupProject>();
if (group.Projects.Where(g=>g.NextFlag ?? false).Count() > 0)
if (group.Projects.Where(g => g.NextFlag ?? false).Count() > 0)
{
bool start = false;
foreach (var item in group.Projects)
@@ -1780,17 +1798,17 @@ public partial class ScriptControlViewModel : ViewModel
return ls;
}
return group.Projects.Select(g=>g).ToList();
return group.Projects.Select(g => g).ToList();
}
[RelayCommand]
public async Task OnContinueMultiScriptGroupAsync()
{
// 创建一个 StackPanel 来包含全选按钮和所有配置组的 CheckBox
// 创建一个 StackPanel 来包含全选按钮和所有配置组的 CheckBox
var stackPanel = new StackPanel();
// 添加分割线
var separator = new Separator
@@ -1800,21 +1818,21 @@ public partial class ScriptControlViewModel : ViewModel
stackPanel.Children.Add(separator);
List<TaskProgress> taskProgresses = TaskProgressManager.LoadAllTaskProgress();
var checkBox = new ComboBox();;
var checkBox = new ComboBox(); ;
stackPanel.Children.Add(checkBox);
ObservableCollection<KeyValuePair<string, string>> kvs=new ObservableCollection<KeyValuePair<string, string>>();
ObservableCollection<KeyValuePair<string, string>> kvs = new ObservableCollection<KeyValuePair<string, string>>();
foreach (var taskProgress in taskProgresses)
{
var name = taskProgress.Name+"_"+taskProgress.CurrentScriptGroupName+"_";
var name = taskProgress.Name + "_" + taskProgress.CurrentScriptGroupName + "_";
if (taskProgress.Loop)
{
name += "循环("+taskProgress.LoopCount+")_";
name += "循环(" + taskProgress.LoopCount + ")_";
}
if (taskProgress.CurrentScriptGroupProjectInfo!=null)
if (taskProgress.CurrentScriptGroupProjectInfo != null)
{
name = name +taskProgress.CurrentScriptGroupProjectInfo.Index+ "_" + taskProgress.CurrentScriptGroupProjectInfo.Name;
name = name + taskProgress.CurrentScriptGroupProjectInfo.Index + "_" + taskProgress.CurrentScriptGroupProjectInfo.Name;
}
kvs.Add(new KeyValuePair<string, string>(taskProgress.Name,name));
kvs.Add(new KeyValuePair<string, string>(taskProgress.Name, name));
}
checkBox.SelectedValuePath = "Key";
@@ -1822,7 +1840,7 @@ public partial class ScriptControlViewModel : ViewModel
checkBox.ItemsSource = kvs;
checkBox.SelectedIndex = 0;
//SelectedValuePath="Key"
// DisplayMemberPath="Value"
// DisplayMemberPath="Value"
var uiMessageBox = new Wpf.Ui.Controls.MessageBox
{
Title = "选择需要继续执行的进度记录",
@@ -1831,7 +1849,8 @@ public partial class ScriptControlViewModel : ViewModel
Content = stackPanel,
VerticalScrollBarVisibility = ScrollBarVisibility.Auto,
Height = 300 // 设置固定高度
,Width = 600
,
Width = 600
},
CloseButtonText = "关闭",
PrimaryButtonText = "确认执行",
@@ -1842,7 +1861,7 @@ public partial class ScriptControlViewModel : ViewModel
var result = await uiMessageBox.ShowDialogAsync();
if (result == MessageBoxResult.Primary)
{
/*var selectedGroups = checkBoxes
.Where(kv => kv.Value.IsChecked == true)
.Select(kv => kv.Key)
@@ -1857,7 +1876,7 @@ public partial class ScriptControlViewModel : ViewModel
}
}
public async Task OnContinueTaskProgressAsync(string name,List<TaskProgress>? taskProgresses = null)
public async Task OnContinueTaskProgressAsync(string name, List<TaskProgress>? taskProgresses = null)
{
if (taskProgresses == null)
{
@@ -1873,24 +1892,24 @@ public partial class ScriptControlViewModel : ViewModel
}
else
{
taskProgress=taskProgresses.FirstOrDefault(t=>t.Name == name);
taskProgress = taskProgresses.FirstOrDefault(t => t.Name == name);
}
if (taskProgress!=null)
if (taskProgress != null)
{
//await StartGroups(selectedGroups);
//taskProgress.Next
var sg = ScriptGroups.ToList().Where(sg => taskProgress.ScriptGroupNames.Contains(sg.Name)).ToList();
TaskProgressManager.GenerNextProjectInfo(taskProgress,sg);
if (taskProgress.Next==null)
TaskProgressManager.GenerNextProjectInfo(taskProgress, sg);
if (taskProgress.Next == null)
{
_logger.LogWarning("无法定位到下一个要执行的项目next为空"+taskProgress.Name+")");
_logger.LogWarning("无法定位到下一个要执行的项目next为空" + taskProgress.Name + ")");
}
else
{
await StartGroups(sg,taskProgress);
await StartGroups(sg, taskProgress);
}
}
@@ -1927,13 +1946,13 @@ public partial class ScriptControlViewModel : ViewModel
var stackPanel = new StackPanel();
var checkBoxes = new Dictionary<ScriptGroup, CheckBox>();
var loopCheckBox = new CheckBox
{
Content = "循环",
};
// 创建全选按钮
var selectAllCheckBox = new CheckBox
{
@@ -2046,7 +2065,7 @@ public partial class ScriptControlViewModel : ViewModel
);
return;
}
await StartGroups(selectedGroups,null,loopCheckBox.IsChecked ?? false);;
await StartGroups(selectedGroups, null, loopCheckBox.IsChecked ?? false);
}
}
@@ -2057,7 +2076,7 @@ public partial class ScriptControlViewModel : ViewModel
public async Task OnStartMultiScriptGroupWithNamesAsync(params string[] names)
{
if( ScriptGroups.Count == 0)
if (ScriptGroups.Count == 0)
{
ReadScriptGroup();
}
@@ -2085,7 +2104,7 @@ public partial class ScriptControlViewModel : ViewModel
}
}
public async Task StartGroups(List<ScriptGroup> scriptGroups,TaskProgress? taskProgress = null,bool loop = false)
public async Task StartGroups(List<ScriptGroup> scriptGroups, TaskProgress? taskProgress = null, bool loop = false)
{
_logger.LogInformation("开始连续执行选中配置组:{Names}", string.Join(",", scriptGroups.Select(x => x.Name)));
try
@@ -2096,7 +2115,8 @@ public partial class ScriptControlViewModel : ViewModel
taskProgress = new()
{
ScriptGroupNames = scriptGroups.Select(x => x.Name).ToList()
,Loop = loop
,
Loop = loop
};
}
@@ -2104,16 +2124,16 @@ public partial class ScriptControlViewModel : ViewModel
var sg = GetNextScriptGroups(scriptGroups);
foreach (var scriptGroup in sg)
{
if (taskProgress.Next!=null)
if (taskProgress.Next != null)
{
if (scriptGroup.Name!=taskProgress.Next.GroupName)
if (scriptGroup.Name != taskProgress.Next.GroupName)
{
continue;
}
}
taskProgress.CurrentScriptGroupName = scriptGroup.Name;
TaskProgressManager.SaveTaskProgress(taskProgress);
await _scriptService.RunMulti(GetNextProjects(scriptGroup), scriptGroup.Name,taskProgress);
await _scriptService.RunMulti(GetNextProjects(scriptGroup), scriptGroup.Name, taskProgress);
await Task.Delay(2000);
}
@@ -2133,7 +2153,7 @@ public partial class ScriptControlViewModel : ViewModel
taskProgress.EndTime = DateTime.Now;
TaskProgressManager.SaveTaskProgress(taskProgress);
}
}
}
catch (Exception e)

View File

@@ -1,44 +1,41 @@
using BetterGenshinImpact.Core.Config;
using BetterGenshinImpact.Core.Script;
using BetterGenshinImpact.Core.Script.Project;
using BetterGenshinImpact.GameTask;
using BetterGenshinImpact.GameTask.AutoArtifactSalvage;
using BetterGenshinImpact.GameTask.AutoDomain;
using BetterGenshinImpact.GameTask.AutoFight;
using BetterGenshinImpact.GameTask.AutoFishing;
using BetterGenshinImpact.GameTask.AutoGeniusInvokation;
using BetterGenshinImpact.GameTask.AutoMusicGame;
using BetterGenshinImpact.GameTask.AutoSkip.Model;
using BetterGenshinImpact.GameTask.AutoTrackPath;
using BetterGenshinImpact.GameTask.AutoWood;
using BetterGenshinImpact.GameTask.Model;
using BetterGenshinImpact.Service.Interface;
using BetterGenshinImpact.View.Pages;
using CommunityToolkit.Mvvm.ComponentModel;
using CommunityToolkit.Mvvm.Input;
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.IO;
using System.Threading;
using System.Threading.Tasks;
using Windows.System;
using BetterGenshinImpact.GameTask.AutoFishing;
using BetterGenshinImpact.GameTask.Common.Element.Assets;
using BetterGenshinImpact.Helpers;
using Wpf.Ui;
using Wpf.Ui.Violeta.Controls;
using BetterGenshinImpact.ViewModel.Pages.View;
using System.Linq;
using System.Reflection;
using System.Collections.Frozen;
using System.Diagnostics;
using System.Windows;
using System.Windows.Controls;
using BetterGenshinImpact.GameTask.AutoArtifactSalvage;
using BetterGenshinImpact.GameTask.AutoStygianOnslaught;
using BetterGenshinImpact.View.Windows;
using BetterGenshinImpact.GameTask.AutoWood;
using BetterGenshinImpact.GameTask.Common.Element.Assets;
using BetterGenshinImpact.GameTask.GetGridIcons;
using BetterGenshinImpact.GameTask.Model.GameUI;
using BetterGenshinImpact.GameTask.UseRedeemCode;
using BetterGenshinImpact.Helpers;
using BetterGenshinImpact.Service.Interface;
using BetterGenshinImpact.View.Pages;
using BetterGenshinImpact.View.Windows;
using BetterGenshinImpact.ViewModel.Pages.View;
using CommunityToolkit.Mvvm.ComponentModel;
using CommunityToolkit.Mvvm.Input;
using System;
using System.Collections.Frozen;
using System.Collections.Generic;
using System.ComponentModel;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Reflection;
using System.Threading;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Controls;
using Windows.System;
using Wpf.Ui;
using Wpf.Ui.Violeta.Controls;
using TextBox = Wpf.Ui.Controls.TextBox;
namespace BetterGenshinImpact.ViewModel.Pages;
@@ -162,6 +159,16 @@ public partial class TaskSettingsPageViewModel : ViewModel
[ObservableProperty]
private bool _switchArtifactSalvageEnabled;
[ObservableProperty]
private FrozenDictionary<Enum, string> _recognitionFailurePolicyDict = Enum.GetValues(typeof(RecognitionFailurePolicy))
.Cast<RecognitionFailurePolicy>()
.ToFrozenDictionary(
e => (Enum)e,
e => e.GetType()
.GetField(e.ToString())?
.GetCustomAttribute<DescriptionAttribute>()?
.Description ?? e.ToString());
[ObservableProperty]
private bool _switchGetGridIconsEnabled;
[ObservableProperty]
@@ -527,7 +534,13 @@ public partial class TaskSettingsPageViewModel : ViewModel
{
SwitchArtifactSalvageEnabled = true;
await new TaskRunner()
.RunSoloTaskAsync(new AutoArtifactSalvageTask(int.Parse(Config.AutoArtifactSalvageConfig.MaxArtifactStar), Config.AutoArtifactSalvageConfig.JavaScript, Config.AutoArtifactSalvageConfig.MaxNumToCheck));
.RunSoloTaskAsync(new AutoArtifactSalvageTask(new AutoArtifactSalvageTaskParam(
int.Parse(Config.AutoArtifactSalvageConfig.MaxArtifactStar),
Config.AutoArtifactSalvageConfig.JavaScript,
Config.AutoArtifactSalvageConfig.ArtifactSetFilter,
Config.AutoArtifactSalvageConfig.MaxNumToCheck,
Config.AutoArtifactSalvageConfig.RecognitionFailurePolicy
)));
SwitchArtifactSalvageEnabled = false;
}
@@ -540,10 +553,56 @@ public partial class TaskSettingsPageViewModel : ViewModel
[RelayCommand]
private void OnOpenArtifactSalvageTestOCRWindow()
{
OcrDialog ocrDialog = new OcrDialog(0.70, 0.098, 0.24, 0.52, "圣遗物分解", this.Config.AutoArtifactSalvageConfig.JavaScript);
OcrDialog ocrDialog = new OcrDialog(0.70, 0.112, 0.275, 0.50, "圣遗物分解", this.Config.AutoArtifactSalvageConfig.JavaScript);
ocrDialog.ShowDialog();
}
[RelayCommand]
private async Task OnCopyArtifactSalvageJavaScriptFromRepository()
{
var list = ScriptControlViewModel.LoadAllJsScriptProjects();
var stackPanel = ScriptControlViewModel.CreateJsScriptSelectionPanel(list, typeof(RadioButton));
var result = PromptDialog.Prompt("请选择需要复制的JS脚本", "请选择需要复制的JS脚本", stackPanel, new Size(500, 600));
if (!string.IsNullOrEmpty(result))
{
string? selectedFolderName = null;
foreach (var child in ((Wpf.Ui.Controls.StackPanel)stackPanel.Content).Children)
{
if (child is RadioButton { IsChecked: true } radioButton && radioButton.Tag is string folderName)
{
selectedFolderName = folderName;
}
}
if (selectedFolderName == null)
{
return;
}
ScriptProject scriptProject = new ScriptProject(selectedFolderName);
string jsCode = await scriptProject.LoadCode();
var multilineTextBox = new TextBox
{
TextWrapping = TextWrapping.Wrap,
VerticalScrollBarVisibility = ScrollBarVisibility.Auto,
Text = jsCode,
IsReadOnly = true
};
var p = new PromptDialog($"{scriptProject.Manifest.Name}\r\n{scriptProject.Manifest.ShortDescription}\r\n\r\n将覆盖现有的JavaScript是否继续", $"预览 - {scriptProject.FolderName}", multilineTextBox, null);
p.Height = 600;
p.MaxWidth = 800;
p.ShowDialog();
if (p.DialogResult != true)
{
return;
}
this.Config.AutoArtifactSalvageConfig.JavaScript = jsCode;
}
}
[RelayCommand]
private async Task OnSwitchGetGridIcons()
{

View File

@@ -74,7 +74,11 @@ public partial class TriggerSettingsPageViewModel : ViewModel
"牢固的箭簇"
};
var p = new PromptDialog(
"黑名单配置,每行一条记录",
"黑名单配置,每行一条记录\n"+
"示例:\n" +
"精致的宝箱\n" +
"史莱姆凝液\n" +
"牢固的箭簇",
"黑名单配置",
multilineTextBox,
text);
@@ -111,7 +115,11 @@ public partial class TriggerSettingsPageViewModel : ViewModel
"启动"
};
var p = new PromptDialog(
"白名单配置,每行一条记录",
"白名单配置,每行一条记录\n" +
"示例:\n" +
"调查\n" +
"合成\n" +
"启动",
"白名单配置",
multilineTextBox,
text);

View File

@@ -1,11 +1,12 @@
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using BetterGenshinImpact.Core.Config;
using BetterGenshinImpact.Core.Script.Group;
using BetterGenshinImpact.View.Windows;
using CommunityToolkit.Mvvm.ComponentModel;
using CommunityToolkit.Mvvm.Input;
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Threading.Tasks;
using Windows.System;
using Wpf.Ui.Violeta.Controls;
namespace BetterGenshinImpact.ViewModel.Pages.View;
@@ -96,4 +97,10 @@ public partial class ScriptGroupConfigViewModel : ObservableObject, IViewModel
{
PathingConfig.Enabled = true;
}
[RelayCommand]
private async Task OnGoToAutoEatUrlAsync()
{
await Launcher.LaunchUriAsync(new Uri("https://bettergi.com/dev/js/dispatcher.html#autoeat-自动吃食物"));
}
}

View File

@@ -1,4 +1,4 @@
using BetterGenshinImpact.Core.Recognition.ONNX;
using BetterGenshinImpact.Core.Recognition.ONNX;
using System.Collections.Concurrent;
using System.Globalization;
using BetterGenshinImpact.Core.Recognition.OCR.Paddle;

View File

@@ -1,6 +1,7 @@
using BetterGenshinImpact.GameTask.AutoArtifactSalvage;
using BetterGenshinImpact.GameTask.Model.GameUI;
using BetterGenshinImpact.UnitTest.CoreTests.RecognitionTests.OCRTests;
using Microsoft.Extensions.Localization;
using OpenCvSharp;
using System;
using System.Collections.Concurrent;
@@ -13,12 +14,15 @@ using static BetterGenshinImpact.GameTask.AutoArtifactSalvage.AutoArtifactSalvag
namespace BetterGenshinImpact.UnitTest.GameTaskTests.AutoArtifactSalvageTests
{
[Collection("Init Collection")]
public partial class AutoArtifactSalvageTaskTests
public class AutoArtifactSalvageTaskTests
{
private readonly PaddleFixture paddle;
public AutoArtifactSalvageTaskTests(PaddleFixture paddle)
private readonly IStringLocalizer<AutoArtifactSalvageTask> stringLocalizer;
public AutoArtifactSalvageTaskTests(PaddleFixture paddle, LocalizationFixture localization)
{
this.paddle = paddle;
this.stringLocalizer = localization.CreateStringLocalizer<AutoArtifactSalvageTask>();
}
[Theory]
@@ -94,10 +98,11 @@ namespace BetterGenshinImpact.UnitTest.GameTaskTests.AutoArtifactSalvageTests
CultureInfo cultureInfo = new CultureInfo("zh-Hans");
//
AutoArtifactSalvageTask sut = new AutoArtifactSalvageTask(new AutoArtifactSalvageTaskParam(5, null, null, null, null, cultureInfo, this.stringLocalizer), new FakeLogger());
string result = PaddleResultDic.GetOrAdd(screenshot, screenshot_ =>
{
using Mat mat = new Mat(@$"..\..\..\Assets\AutoArtifactSalvage\{screenshot_}");
GetArtifactStat(mat, paddle.Get(), cultureInfo, out string allText);
sut.GetArtifactStat(mat, paddle.Get(), out string allText);
return allText;
});
@@ -112,30 +117,105 @@ namespace BetterGenshinImpact.UnitTest.GameTaskTests.AutoArtifactSalvageTests
}
}
public static IEnumerable<object[]> GetArtifactStatTestData
{
get
{
yield return new object[] { "ArtifactAffixes.png", new ArtifactStat("异种的期许", new ArtifactAffix(ArtifactAffixType.HP, 717f), [
new ArtifactAffix(ArtifactAffixType.ElementalMastery, 16f),
new ArtifactAffix(ArtifactAffixType.EnergyRecharge, 6.5f),
new ArtifactAffix(ArtifactAffixType.ATKPercent, 5.8f),
new ArtifactAffix(ArtifactAffixType.DEF, 23f)
], 0), new CultureInfo("zh-Hans") };
yield return new object[] { "202508242224_GetArtifactStat.png", new ArtifactStat("裁判的时刻", new ArtifactAffix(ArtifactAffixType.DEFPercent, 8.7f), [
new ArtifactAffix(ArtifactAffixType.ATK, 16f),
new ArtifactAffix(ArtifactAffixType.CRITDMG, 7.8f),
new ArtifactAffix(ArtifactAffixType.DEF, 19),
new ArtifactAffix(ArtifactAffixType.CRITRate, 2.7f)
], 0), new CultureInfo("zh-Hans") };
yield return new object[] { "202508252004_GetArtifactStat.png", new ArtifactStat("Deep Gallery's Bestowed Banquet", new ArtifactAffix(ArtifactAffixType.DEFPercent, 8.7f), [
new ArtifactAffix(ArtifactAffixType.HP, 239),
new ArtifactAffix(ArtifactAffixType.ATK, 18),
new ArtifactAffix(ArtifactAffixType.HPPercent, 4.1f)
], 0), new CultureInfo("en") };
yield return new object[] { "20250827000123_GetArtifactStat.png", new ArtifactStat("Pacte distant des galeries profondes", new ArtifactAffix(ArtifactAffixType.ATK, 47), [
new ArtifactAffix(ArtifactAffixType.DEFPercent, 7.3f),
new ArtifactAffix(ArtifactAffixType.DEF, 16),
new ArtifactAffix(ArtifactAffixType.HPPercent, 5.3f)
], 0), new CultureInfo("fr") };
yield return new object[] { "20250827000246_GetArtifactStat.png", new ArtifactStat("Reckoning of the Xenogenic", new ArtifactAffix(ArtifactAffixType.HP, 717f), [
new ArtifactAffix(ArtifactAffixType.ElementalMastery, 16),
new ArtifactAffix(ArtifactAffixType.EnergyRecharge, 6.5f),
new ArtifactAffix(ArtifactAffixType.ATKPercent, 5.8f),
new ArtifactAffix(ArtifactAffixType.DEF, 23)
], 0), new CultureInfo("en") };
yield return new object[] { "20250827151043_GetArtifactStat.png", new ArtifactStat("Couronne de laurier", new ArtifactAffix(ArtifactAffixType.ATKPercent, 7.0f), [
new ArtifactAffix(ArtifactAffixType.CRITDMG, 5.4f),
new ArtifactAffix(ArtifactAffixType.DEFPercent, 7.3f),
new ArtifactAffix(ArtifactAffixType.DEF, 16),
new ArtifactAffix(ArtifactAffixType.HPPercent, 5.8f)
], 0), new CultureInfo("fr") }; // 一个百里挑一的识别失败的例子
yield return new object[] { "20250827163340_GetArtifactStat.png", new ArtifactStat("Couronne de Watatsumi", new ArtifactAffix(ArtifactAffixType.DEFPercent, 8.7f), [
new ArtifactAffix(ArtifactAffixType.HP, 299),
new ArtifactAffix(ArtifactAffixType.EnergyRecharge, 4.5f),
new ArtifactAffix(ArtifactAffixType.CRITDMG, 6.2f),
new ArtifactAffix(ArtifactAffixType.ElementalMastery, 23)
], 0), new CultureInfo("fr") };
yield return new object[] { "20250827204545_GetArtifactStat.png", new ArtifactStat("Agitation de la nuit dorée", new ArtifactAffix(ArtifactAffixType.GeoDMGBonus, 7.0f), [
new ArtifactAffix(ArtifactAffixType.HP, 269),
new ArtifactAffix(ArtifactAffixType.ElementalMastery, 23),
new ArtifactAffix(ArtifactAffixType.CRITRate, 3.1f)
], 0), new CultureInfo("fr") };
yield return new object[] { "20250828084843_GetArtifactStat.png", new ArtifactStat("異種的期許", new ArtifactAffix(ArtifactAffixType.HP, 717f), [
new ArtifactAffix(ArtifactAffixType.DEFPercent, 7.3f),
new ArtifactAffix(ArtifactAffixType.ElementalMastery, 23),
new ArtifactAffix(ArtifactAffixType.ATKPercent, 4.1f)
], 0), new CultureInfo("zh-Hant") };
yield return new object[] { "20250828093344_GetArtifactStat.png", new ArtifactStat("黃金時代的先聲",new ArtifactAffix(ArtifactAffixType.DEFPercent, 8.7f), [
new ArtifactAffix(ArtifactAffixType.DEF, 19),
new ArtifactAffix(ArtifactAffixType.CRITDMG, 7.8f),
new ArtifactAffix(ArtifactAffixType.ATK, 18),
new ArtifactAffix(ArtifactAffixType.ElementalMastery, 23)
], 0), new CultureInfo("zh-Hant") };
}
}
/// <summary>
/// 测试获取分解圣遗物界面右侧圣遗物的各种结构化信息,结果应正确
/// </summary>
/// <param name="screenshot"></param>
[Theory]
[InlineData(@"ArtifactAffixes.png")]
public void GetArtifactStat_AffixesShouldBeRight(string screenshot)
[MemberData(nameof(GetArtifactStatTestData))]
public void GetArtifactStat_AffixesShouldBeRight(string screenshot, ArtifactStat expectedArtifactStat, CultureInfo cultureInfo)
{
//
CultureInfo cultureInfo = new CultureInfo("zh-Hans");
//
using Mat mat = new Mat(@$"..\..\..\Assets\AutoArtifactSalvage\{screenshot}");
ArtifactStat artifact = GetArtifactStat(mat, paddle.Get(), cultureInfo, out string _);
/*
using var gray = mat.CvtColor(ColorConversionCodes.BGR2GRAY);
using Mat kernel = Cv2.GetStructuringElement(MorphShapes.Rect, new Size(15, 15));
using var bottomHat = gray.MorphologyEx(MorphTypes.TopHat, kernel);
Cv2.ImShow("bottomHat", bottomHat);
Mat threshold = new Mat();
var otsu = Cv2.Threshold(bottomHat, threshold, 30, 255, ThresholdTypes.Binary);
Cv2.ImShow($"thres = {otsu}", threshold);
Cv2.WaitKey();
*/
//
Assert.Equal("异种的期许", artifact.Name);
Assert.True(artifact.MainAffix.Type == ArtifactAffixType.HP);
Assert.True(artifact.MainAffix.Value == 717f);
Assert.Contains(artifact.MinorAffixes, a => a.Type == ArtifactAffixType.ElementalMastery && a.Value == 16f);
Assert.Contains(artifact.MinorAffixes, a => a.Type == ArtifactAffixType.EnergyRecharge && a.Value == 6.5f);
Assert.Contains(artifact.MinorAffixes, a => a.Type == ArtifactAffixType.ATKPercent && a.Value == 5.8f);
Assert.Contains(artifact.MinorAffixes, a => a.Type == ArtifactAffixType.DEF && a.Value == 23f);
Assert.True(artifact.Level == 0);
AutoArtifactSalvageTask sut = new AutoArtifactSalvageTask(new AutoArtifactSalvageTaskParam(5, null, null, null, null, cultureInfo, this.stringLocalizer), new FakeLogger());
ArtifactStat result = sut.GetArtifactStat(mat, paddle.Get(cultureInfo.Name), out string _);
//
Assert.Equal(expectedArtifactStat.Name, result.Name);
Assert.Equal(expectedArtifactStat.MainAffix.Type, result.MainAffix.Type);
Assert.Equal(expectedArtifactStat.MainAffix.Value, result.MainAffix.Value);
foreach (ArtifactAffix expectedArtifactAffix in expectedArtifactStat.MinorAffixes)
{
Assert.Contains(result.MinorAffixes, a =>
a.Type == expectedArtifactAffix.Type && a.Value == expectedArtifactAffix.Value);
}
Assert.True(result.Level == expectedArtifactStat.Level);
}
[Theory]
@@ -153,11 +233,12 @@ namespace BetterGenshinImpact.UnitTest.GameTaskTests.AutoArtifactSalvageTests
public void IsMatchJavaScript_JSShouldBeRight(string screenshot, string js, bool expected)
{
//
using Mat mat = new Mat(@$"..\..\..\Assets\AutoArtifactSalvage\{screenshot}");
CultureInfo cultureInfo = new CultureInfo("zh-Hans");
//
using Mat mat = new Mat(@$"..\..\..\Assets\AutoArtifactSalvage\{screenshot}");
ArtifactStat artifact = GetArtifactStat(mat, paddle.Get(), cultureInfo, out string _);
AutoArtifactSalvageTask sut = new AutoArtifactSalvageTask(new AutoArtifactSalvageTaskParam(5, null, null, null, null, cultureInfo, this.stringLocalizer), new FakeLogger());
ArtifactStat artifact = sut.GetArtifactStat(mat, paddle.Get(), out string _);
bool result = IsMatchJavaScript(artifact, js);
//

View File

@@ -1,4 +1,4 @@
using BehaviourTree;
using BehaviourTree;
using BetterGenshinImpact.GameTask.AutoFishing;
using BetterGenshinImpact.GameTask.AutoFishing.Model;
using BetterGenshinImpact.GameTask.Model.Area;
@@ -17,7 +17,7 @@ namespace BetterGenshinImpact.UnitTest.GameTaskTests.AutoFishingTests
{
[Theory]
[InlineData(@"20250225101300361_ChooseBait_Succeeded.png", new string[] { "medaka", "butterflyfish", "butterflyfish", "pufferfish" })]
[InlineData(@"20250226161354285_ChooseBait_Succeeded.png", new string[] { "medaka", "medaka" })]
[InlineData(@"20250226161354285_ChooseBait_Succeeded.png", new string[] { "medaka", "medaka" })] // todo 更新用例
[InlineData(@"202503160917566615@900p.png", new string[] { "pufferfish" })]
/// <summary>
/// 测试各种选取鱼饵,结果为成功
@@ -35,7 +35,7 @@ namespace BetterGenshinImpact.UnitTest.GameTaskTests.AutoFishingTests
};
//
ChooseBait sut = new ChooseBait("-", blackboard, new FakeLogger(), false, systemInfo, new FakeInputSimulator());
ChooseBait sut = new ChooseBait("-", blackboard, new FakeLogger(), false, systemInfo, new FakeInputSimulator(), this.session, this.prototypes);
BehaviourStatus actual = sut.Tick(imageRegion);
//
@@ -69,11 +69,11 @@ namespace BetterGenshinImpact.UnitTest.GameTaskTests.AutoFishingTests
FakeTimeProvider fakeTimeProvider = new FakeTimeProvider(dateTime);
//
ChooseBait sut = new ChooseBait("-", blackboard, new FakeLogger(), false, systemInfo, new FakeInputSimulator(), fakeTimeProvider);
ChooseBait sut = new ChooseBait("-", blackboard, new FakeLogger(), false, systemInfo, new FakeInputSimulator(), this.session, this.prototypes, fakeTimeProvider);
BehaviourStatus actual = sut.Tick(imageRegion);
//
Assert.True(String.IsNullOrEmpty(blackboard.selectedBaitName));
Assert.Null(blackboard.selectedBait);
Assert.True(blackboard.chooseBaitUIOpening);
Assert.Equal(BehaviourStatus.Running, actual);
@@ -84,7 +84,7 @@ namespace BetterGenshinImpact.UnitTest.GameTaskTests.AutoFishingTests
actual = sut.Tick(imageRegion);
//
Assert.False(String.IsNullOrEmpty(blackboard.selectedBaitName));
Assert.NotNull(blackboard.selectedBait);
Assert.True(blackboard.chooseBaitUIOpening);
Assert.Equal(BehaviourStatus.Running, actual);
@@ -95,7 +95,7 @@ namespace BetterGenshinImpact.UnitTest.GameTaskTests.AutoFishingTests
actual = sut.Tick(imageRegion);
//
Assert.True(String.IsNullOrEmpty(blackboard.selectedBaitName));
Assert.Null(blackboard.selectedBait);
Assert.False(blackboard.chooseBaitUIOpening);
Assert.Equal(BehaviourStatus.Failed, actual);
}
@@ -124,15 +124,15 @@ namespace BetterGenshinImpact.UnitTest.GameTaskTests.AutoFishingTests
#region 1
//
ChooseBait sut = new ChooseBait("-", blackboard, new FakeLogger(), false, systemInfo, new FakeInputSimulator(), fakeTimeProvider);
ChooseBait sut = new ChooseBait("-", blackboard, new FakeLogger(), false, systemInfo, new FakeInputSimulator(), this.session, this.prototypes, fakeTimeProvider);
BehaviourStatus actual = sut.Tick(imageRegion);
fakeTimeProvider.SetUtcNow(dateTime.AddSeconds(3));
actual = sut.Tick(imageRegion);
//
Assert.True(String.IsNullOrEmpty(blackboard.selectedBaitName));
Assert.Null(blackboard.selectedBait);
Assert.Equal(BehaviourStatus.Failed, actual);
Assert.Single(blackboard.chooseBaitFailures.Where(f => f == "fake fly bait"));
Assert.Single(blackboard.chooseBaitFailures.Where(f => f == BaitType.FakeFlyBait));
#endregion
#region 2
@@ -146,9 +146,9 @@ namespace BetterGenshinImpact.UnitTest.GameTaskTests.AutoFishingTests
actual = sut.Tick(imageRegion);
//
Assert.True(String.IsNullOrEmpty(blackboard.selectedBaitName));
Assert.Null(blackboard.selectedBait);
Assert.Equal(BehaviourStatus.Failed, actual);
Assert.Equal(2, blackboard.chooseBaitFailures.Where(f => f == "fake fly bait").Count());
Assert.Equal(2, blackboard.chooseBaitFailures.Where(f => f == BaitType.FakeFlyBait).Count());
Assert.False(blackboard.abort);
#endregion
@@ -165,9 +165,9 @@ namespace BetterGenshinImpact.UnitTest.GameTaskTests.AutoFishingTests
actual = sut.Tick(imageRegion);
//
Assert.True(String.IsNullOrEmpty(blackboard.selectedBaitName));
Assert.Null(blackboard.selectedBait);
Assert.Equal(BehaviourStatus.Failed, actual);
Assert.Single(blackboard.chooseBaitFailures.Where(f => f == "spinelgrain bait"));
Assert.Single(blackboard.chooseBaitFailures.Where(f => f == BaitType.SpinelgrainBait));
#endregion
#region sunfish受到遮挡medaka再次出现4medaka
@@ -183,9 +183,9 @@ namespace BetterGenshinImpact.UnitTest.GameTaskTests.AutoFishingTests
actual = sut.Tick(imageRegion);
//
Assert.False(String.IsNullOrEmpty(blackboard.selectedBaitName));
Assert.NotNull(blackboard.selectedBait); // todo 更新用例
Assert.Equal(BehaviourStatus.Succeeded, actual);
Assert.Single(blackboard.chooseBaitFailures.Where(f => f == "spinelgrain bait"));
Assert.Single(blackboard.chooseBaitFailures.Where(f => f == BaitType.SpinelgrainBait));
#endregion
#region sunfish再次出现5
@@ -201,9 +201,9 @@ namespace BetterGenshinImpact.UnitTest.GameTaskTests.AutoFishingTests
actual = sut.Tick(imageRegion);
//
Assert.True(String.IsNullOrEmpty(blackboard.selectedBaitName));
Assert.Null(blackboard.selectedBait);
Assert.Equal(BehaviourStatus.Failed, actual);
Assert.Equal(2, blackboard.chooseBaitFailures.Where(f => f == "spinelgrain bait").Count());
Assert.Equal(2, blackboard.chooseBaitFailures.Where(f => f == BaitType.SpinelgrainBait).Count());
#endregion
}
@@ -230,15 +230,15 @@ namespace BetterGenshinImpact.UnitTest.GameTaskTests.AutoFishingTests
#region 1
//
ChooseBait sut = new ChooseBait("-", blackboard, new FakeLogger(), false, systemInfo, new FakeInputSimulator(), fakeTimeProvider);
ChooseBait sut = new ChooseBait("-", blackboard, new FakeLogger(), false, systemInfo, new FakeInputSimulator(), this.session, this.prototypes, fakeTimeProvider);
BehaviourStatus actual = sut.Tick(imageRegion);
fakeTimeProvider.SetUtcNow(dateTime.AddSeconds(3));
actual = sut.Tick(imageRegion);
//
Assert.True(String.IsNullOrEmpty(blackboard.selectedBaitName));
Assert.Null(blackboard.selectedBait);
Assert.Equal(BehaviourStatus.Failed, actual);
Assert.Single(blackboard.chooseBaitFailures.Where(f => f == "fake fly bait"));
Assert.Single(blackboard.chooseBaitFailures.Where(f => f == BaitType.FakeFlyBait));
#endregion
#region koi受到遮挡2
@@ -254,9 +254,9 @@ namespace BetterGenshinImpact.UnitTest.GameTaskTests.AutoFishingTests
actual = sut.Tick(imageRegion);
//
Assert.True(String.IsNullOrEmpty(blackboard.selectedBaitName));
Assert.Null(blackboard.selectedBait);
Assert.Equal(BehaviourStatus.Failed, actual);
Assert.Single(blackboard.chooseBaitFailures.Where(f => f == "spinelgrain bait"));
Assert.Single(blackboard.chooseBaitFailures.Where(f => f == BaitType.SpinelgrainBait));
Assert.False(blackboard.abort);
#endregion
@@ -273,9 +273,9 @@ namespace BetterGenshinImpact.UnitTest.GameTaskTests.AutoFishingTests
actual = sut.Tick(imageRegion);
//
Assert.True(String.IsNullOrEmpty(blackboard.selectedBaitName));
Assert.Null(blackboard.selectedBait);
Assert.Equal(BehaviourStatus.Failed, actual);
Assert.Equal(2, blackboard.chooseBaitFailures.Where(f => f == "fake fly bait").Count());
Assert.Equal(2, blackboard.chooseBaitFailures.Where(f => f == BaitType.FakeFlyBait).Count());
#endregion
#region 4
@@ -291,9 +291,9 @@ namespace BetterGenshinImpact.UnitTest.GameTaskTests.AutoFishingTests
actual = sut.Tick(imageRegion);
//
Assert.True(String.IsNullOrEmpty(blackboard.selectedBaitName));
Assert.Null(blackboard.selectedBait);
Assert.Equal(BehaviourStatus.Failed, actual);
Assert.Equal(2, blackboard.chooseBaitFailures.Where(f => f == "spinelgrain bait").Count());
Assert.Equal(2, blackboard.chooseBaitFailures.Where(f => f == BaitType.SpinelgrainBait).Count());
#endregion
}
}

View File

@@ -1,4 +1,4 @@
using BehaviourTree;
using BehaviourTree;
using BetterGenshinImpact.GameTask.AutoFishing;
using BetterGenshinImpact.GameTask.Model.Area.Converter;
using BetterGenshinImpact.GameTask.Model.Area;
@@ -6,8 +6,6 @@ using BehaviourTree.Composites;
using BehaviourTree.FluentBuilder;
using Microsoft.Extensions.Time.Testing;
using OpenCvSharp;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Localization;
namespace BetterGenshinImpact.UnitTest.GameTaskTests.AutoFishingTests
{
@@ -106,17 +104,12 @@ namespace BetterGenshinImpact.UnitTest.GameTaskTests.AutoFishingTests
Mat mat = new Mat(@$"..\..\..\Assets\AutoFishing\{screenshot1080p}");
var imageRegion = new GameCaptureRegion(mat, 0, 0, new DesktopRegion(new FakeMouseSimulator()), converter: new ScaleConverter(1d), drawContent: new FakeDrawContent());
ServiceCollection services = new ServiceCollection();
services.AddLogging().AddLocalization();
using ServiceProvider sp = services.BuildServiceProvider();
IStringLocalizer<AutoFishingImageRecognition> autoFishingImageRecognitionStringLocalizer = sp.GetRequiredService<IStringLocalizer<AutoFishingImageRecognition>>();
FakeSystemInfo systemInfo = new FakeSystemInfo(new Vanara.PInvoke.RECT(0, 0, mat.Width, mat.Height), 1);
AutoFishingAssets autoFishingAssets = new AutoFishingAssets(systemInfo);
Blackboard blackboard = new Blackboard(autoFishingAssets: autoFishingAssets);
//
FishBite sut = new FishBite("-", blackboard, new FakeLogger(), false, new FakeInputSimulator(), OcrService, drawContent: new FakeDrawContent(), new System.Globalization.CultureInfo(cultureName), autoFishingImageRecognitionStringLocalizer);
FishBite sut = new FishBite("-", blackboard, new FakeLogger(), false, new FakeInputSimulator(), OcrService, drawContent: new FakeDrawContent(), new System.Globalization.CultureInfo(cultureName), stringLocalizer);
BehaviourStatus actual = sut.Tick(imageRegion);
//

View File

@@ -1,8 +1,9 @@
using BetterGenshinImpact.GameTask.AutoFishing;
using BetterGenshinImpact.GameTask.AutoFishing;
using BehaviourTree;
using BetterGenshinImpact.GameTask.Model.Area;
using Microsoft.Extensions.Time.Testing;
using OpenCvSharp;
using BetterGenshinImpact.GameTask.AutoFishing.Model;
namespace BetterGenshinImpact.UnitTest.GameTaskTests.AutoFishingTests
{
@@ -38,10 +39,10 @@ namespace BetterGenshinImpact.UnitTest.GameTaskTests.AutoFishingTests
}
[Theory]
[InlineData("20250225101257889_GetFishpond_Succeeded.png", new string[] { "fruit paste bait", "fruit paste bait", "redrot bait", "redrot bait" }, new string[] { "false worm bait", "false worm bait", "fake fly bait", "fake fly bait" })]
[InlineData("20250225101257889_GetFishpond_Succeeded.png", new BaitType[] { BaitType.FruitPasteBait, BaitType.FruitPasteBait, BaitType.RedrotBait, BaitType.RedrotBait }, new BaitType[] { BaitType.FalseWormBait, BaitType.FalseWormBait, BaitType.FakeFlyBait, BaitType.FakeFlyBait })]
/// 测试鱼的鱼饵均在失败列表中且被忽略,结果为运行中
/// </summary>
public void GetFishpondTest_AllIgnored_ShouldBeRunning(string screenshot1080p, IEnumerable<string> chooseBaitfailures, IEnumerable<string> throwRodNoTargetFishfailures)
public void GetFishpondTest_AllIgnored_ShouldBeRunning(string screenshot1080p, IEnumerable<BaitType> chooseBaitfailures, IEnumerable<BaitType> throwRodNoTargetFishfailures)
{
//
Mat mat = new Mat(@$"..\..\..\Assets\AutoFishing\{screenshot1080p}");

View File

@@ -1,4 +1,4 @@
using BehaviourTree;
using BehaviourTree;
using BetterGenshinImpact.GameTask.AutoFishing;
using BetterGenshinImpact.GameTask.Model.Area.Converter;
using BetterGenshinImpact.GameTask.Model.Area;
@@ -6,18 +6,19 @@ using Microsoft.Extensions.Time.Testing;
using OpenCvSharp;
using BehaviourTree.Composites;
using BehaviourTree.FluentBuilder;
using BetterGenshinImpact.GameTask.AutoFishing.Model;
namespace BetterGenshinImpact.UnitTest.GameTaskTests.AutoFishingTests
{
public partial class BehavioursTests
{
[Theory]
[InlineData(@"20250225101304534_ThrowRod_Succeeded.png", "false worm bait")]
[InlineData(@"20250226162217468_ThrowRod_Succeeded.png", "fruit paste bait")]
[InlineData(@"20250225101304534_ThrowRod_Succeeded.png", BaitType.FalseWormBait)]
[InlineData(@"20250226162217468_ThrowRod_Succeeded.png", BaitType.FruitPasteBait)]
/// <summary>
/// 测试各种抛竿,结果为成功
/// </summary>
public void ThrowRodTest_VariousFish_ShouldSuccess(string screenshot1080p, string selectedBaitName)
public void ThrowRodTest_VariousFish_ShouldSuccess(string screenshot1080p, BaitType selectedBait)
{
//
Mat mat = new Mat(@$"..\..\..\Assets\AutoFishing\{screenshot1080p}");
@@ -25,7 +26,7 @@ namespace BetterGenshinImpact.UnitTest.GameTaskTests.AutoFishingTests
var blackboard = new Blackboard(Predictor, sleep: i => { })
{
selectedBaitName = selectedBaitName
selectedBait = selectedBait
};
FakeTimeProvider fakeTimeProvider = new FakeTimeProvider();
@@ -40,12 +41,12 @@ namespace BetterGenshinImpact.UnitTest.GameTaskTests.AutoFishingTests
}
[Theory]
[InlineData(@"20250225101304534_ThrowRod_Succeeded.png", "redrot bait")]
[InlineData(@"20250225101304534_ThrowRod_Succeeded.png", "fake fly bait")]
[InlineData(@"20250225101304534_ThrowRod_Succeeded.png", BaitType.RedrotBait)]
[InlineData(@"20250225101304534_ThrowRod_Succeeded.png", BaitType.FakeFlyBait)]
/// <summary>
/// 测试各种抛竿未满足HutaoFisher判定结果为运行中
/// </summary>
public void ThrowRodTest_VariousFish_ShouldFail(string screenshot1080p, string selectedBaitName)
public void ThrowRodTest_VariousFish_ShouldFail(string screenshot1080p, BaitType selectedBait)
{
//
Mat mat = new Mat(@$"..\..\..\Assets\AutoFishing\{screenshot1080p}");
@@ -53,7 +54,7 @@ namespace BetterGenshinImpact.UnitTest.GameTaskTests.AutoFishingTests
var blackboard = new Blackboard(Predictor, sleep: i => { })
{
selectedBaitName = selectedBaitName
selectedBait = selectedBait
};
FakeTimeProvider fakeTimeProvider = new FakeTimeProvider();
@@ -68,11 +69,11 @@ namespace BetterGenshinImpact.UnitTest.GameTaskTests.AutoFishingTests
}
[Theory]
[InlineData(@"20250225101304534_ThrowRod_Succeeded.png", "flashing maintenance mek bait")]
[InlineData(@"20250225101304534_ThrowRod_Succeeded.png", BaitType.FlashingMaintenanceMekBait)]
/// <summary>
/// 测试各种抛竿,无鱼饵适用鱼,结果为失败
/// </summary>
public void ThrowRodTest_NoBaitFish_ShouldFail(string screenshot1080p, string selectedBaitName)
public void ThrowRodTest_NoBaitFish_ShouldFail(string screenshot1080p, BaitType selectedBait)
{
//
Mat mat = new Mat(@$"..\..\..\Assets\AutoFishing\{screenshot1080p}");
@@ -80,7 +81,7 @@ namespace BetterGenshinImpact.UnitTest.GameTaskTests.AutoFishingTests
var blackboard = new Blackboard(Predictor, sleep: i => { })
{
selectedBaitName = selectedBaitName
selectedBait = selectedBait
};
FakeTimeProvider fakeTimeProvider = new FakeTimeProvider();
@@ -119,7 +120,7 @@ namespace BetterGenshinImpact.UnitTest.GameTaskTests.AutoFishingTests
var blackboard = new Blackboard(Predictor, sleep: i => { })
{
selectedBaitName = "fake fly bait"
selectedBait = GameTask.AutoFishing.Model.BaitType.FakeFlyBait
};
//

Some files were not shown because too many files have changed in this diff Show More