using BetterGenshinImpact.Core.Recognition.OCR;
using BetterGenshinImpact.Core.Recognition.OpenCv;
using BetterGenshinImpact.Core.Simulator;
using BetterGenshinImpact.Core.Simulator.Extensions;
using BetterGenshinImpact.GameTask.AutoFight.Config;
using BetterGenshinImpact.GameTask.Model.Area;
using BetterGenshinImpact.Helpers;
using Microsoft.Extensions.Logging;
using OpenCvSharp;
using System;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using BetterGenshinImpact.GameTask.AutoGeniusInvokation.Exception;
using BetterGenshinImpact.GameTask.AutoTrackPath;
using BetterGenshinImpact.GameTask.Common.BgiVision;
using Vanara.PInvoke;
using static BetterGenshinImpact.GameTask.Common.TaskControl;
using BetterGenshinImpact.Core.Config;
using BetterGenshinImpact.GameTask.AutoFight.Assets;
using BetterGenshinImpact.ViewModel.Pages;
namespace BetterGenshinImpact.GameTask.AutoFight.Model;
///
/// 队伍内的角色
///
public class Avatar
{
///
/// 配置文件中的角色信息
///
public readonly CombatAvatar CombatAvatar;
///
/// 角色名称 中文
///
public string Name { get; set; }
///
/// 队伍内序号
///
public int Index { get; set; }
///
/// 最近一次OCR识别出的CD到期时间
///
private DateTime OcrSkillCd { get; set; }
///
/// 手动配置的技能CD,有它就不使用OCR,小于0为自动
///
public double ManualSkillCd { get; set; }
///
/// 最近一次使用元素战技的时间
///
public DateTime LastSkillTime { get; set; }
///
/// 元素爆发是否就绪
///
public bool IsBurstReady { get; set; }
///
/// 名字所在矩形位置
///
public Rect NameRect { get; set; }
///
/// 名字右边的编号位置
///
public Rect IndexRect { get; set; }
///
/// 任务取消令牌
///
public CancellationToken Ct { get; set; }
///
/// 战斗场景
///
public CombatScenes CombatScenes { get; set; }
public static string? LastActiveAvatar { get; internal set; } = null;
public Avatar(CombatScenes combatScenes, string name, int index, Rect nameRect, double manualSkillCd = -1)
{
CombatScenes = combatScenes;
Name = name;
Index = index;
NameRect = nameRect;
CombatAvatar = DefaultAutoFightConfig.CombatAvatarMap[name];
ManualSkillCd = manualSkillCd;
}
///
/// 是否存在角色被击败
/// 通过判断确认按钮
///
///
///
///
public static void ThrowWhenDefeated(ImageRegion region, CancellationToken ct)
{
if (Bv.IsInRevivePrompt(region))
{
Logger.LogWarning("检测到复苏界面,存在角色被击败,前往七天神像复活");
// 先打开地图
Simulation.SendInput.Keyboard.KeyPress(User32.VK.VK_ESCAPE); // NOTE: 此处按下Esc是为了关闭复苏界面,无需改键
Sleep(600, ct);
TpForRecover(ct, new RetryException("检测到复苏界面,存在角色被击败,前往七天神像复活"));
}
}
///
/// tp 到七天神像恢复
///
///
///
///
public static void TpForRecover(CancellationToken ct, Exception ex)
{
// tp 到七天神像复活
var tpTask = new TpTask(ct);
tpTask.TpToStatueOfTheSeven().Wait(ct);
Logger.LogInformation("血量恢复完成。【设置】-【七天神像设置】可以修改回血相关配置。");
throw ex;
}
///
/// 切换到本角色
/// 切换cd是1秒,如果切换失败,会尝试再次切换,最多尝试5次
///
public void Switch()
{
for (var i = 0; i < 30; i++)
{
if (Ct is { IsCancellationRequested: true })
{
return;
}
var region = CaptureToRectArea();
ThrowWhenDefeated(region, Ct);
var notActiveCount = CombatScenes.GetAvatars().Count(avatar => !avatar.IsActive(region));
if (IsActive(region) && notActiveCount == CombatScenes.ExpectedTeamAvatarNum - 1)
{
return;
}
Simulation.SendInput.SimulateAction(GIActions.Drop);
switch (Index)
{
case 1:
Simulation.SendInput.SimulateAction(GIActions.SwitchMember1);
break;
case 2:
Simulation.SendInput.SimulateAction(GIActions.SwitchMember2);
break;
case 3:
Simulation.SendInput.SimulateAction(GIActions.SwitchMember3);
break;
case 4:
Simulation.SendInput.SimulateAction(GIActions.SwitchMember4);
break;
case 5:
Simulation.SendInput.SimulateAction(GIActions.SwitchMember5);
break;
default:
break;
}
// Debug.WriteLine($"切换到{Index}号位");
// Cv2.ImWrite($"log/切换.png", region.SrcMat);
Sleep(250, Ct);
}
}
///
/// 尝试切换到本角色
///
///
///
///
public bool TrySwitch(int tryTimes = 4, bool needLog = true)
{
for (var i = 0; i < tryTimes; i++)
{
if (Ct is { IsCancellationRequested: true })
{
return false;
}
var region = CaptureToRectArea();
ThrowWhenDefeated(region, Ct);
var notActiveCount = CombatScenes.GetAvatars().Count(avatar => !avatar.IsActive(region));
if (IsActive(region) && notActiveCount == CombatScenes.ExpectedTeamAvatarNum - 1)
{
if (needLog && i > 0)
{
LastActiveAvatar = Name;
Logger.LogInformation("成功切换角色:{Name}", Name);
}
return true;
}
Simulation.SendInput.SimulateAction(GIActions.Drop); //反正会重试就不等落地了
switch (Index)
{
case 1:
Simulation.SendInput.SimulateAction(GIActions.SwitchMember1);
break;
case 2:
Simulation.SendInput.SimulateAction(GIActions.SwitchMember2);
break;
case 3:
Simulation.SendInput.SimulateAction(GIActions.SwitchMember3);
break;
case 4:
Simulation.SendInput.SimulateAction(GIActions.SwitchMember4);
break;
case 5:
Simulation.SendInput.SimulateAction(GIActions.SwitchMember5);
break;
default:
break;
}
Sleep(250, Ct);
}
return false;
}
///
/// 切换到本角色
/// 切换cd是1秒,如果切换失败,会尝试再次切换,最多尝试5次
///
public void SwitchWithoutCts()
{
for (var i = 0; i < 10; i++)
{
var region = CaptureToRectArea();
ThrowWhenDefeated(region, Ct);
var notActiveCount = CombatScenes.GetAvatars().Count(avatar => !avatar.IsActive(region));
if (IsActive(region) && notActiveCount == 3)
{
return;
}
Simulation.SendInput.SimulateAction(GIActions.Drop);
switch (Index)
{
case 1:
Simulation.SendInput.SimulateAction(GIActions.SwitchMember1);
break;
case 2:
Simulation.SendInput.SimulateAction(GIActions.SwitchMember2);
break;
case 3:
Simulation.SendInput.SimulateAction(GIActions.SwitchMember3);
break;
case 4:
Simulation.SendInput.SimulateAction(GIActions.SwitchMember4);
break;
case 5:
Simulation.SendInput.SimulateAction(GIActions.SwitchMember5);
break;
default:
break;
}
Sleep(250);
}
}
///
/// 是否出战状态
///
///
public bool IsActive(ImageRegion region)
{
if (IndexRect == default)
{
throw new Exception("IndexRect为空");
}
else
{
// 剪裁出IndexRect区域
var indexRa = region.DeriveCrop(IndexRect);
// Cv2.ImWrite($"log/indexRa_{Name}.png", indexRa.SrcMat);
var count = OpenCvCommonHelper.CountGrayMatColor(indexRa.CacheGreyMat, 251, 255);
if (count * 1.0 / (IndexRect.Width * IndexRect.Height) > 0.5)
{
return false;
}
else
{
return true;
}
}
}
///
/// 是否出战状态
///
///
[Obsolete]
public bool IsActiveNoIndexRect(ImageRegion region)
{
// 通过寻找右侧人物编号来判断是否出战
if (IndexRect == default)
{
var assetScale = TaskContext.Instance().SystemInfo.AssetScale;
// 剪裁出队伍区域
var teamRa = region.DeriveCrop(AutoFightAssets.Instance.TeamRect);
var blockX = NameRect.X + NameRect.Width * 2 - 10;
var block = teamRa.DeriveCrop(new Rect(blockX, NameRect.Y, teamRa.Width - blockX, NameRect.Height * 2));
// Cv2.ImWrite($"block_{Name}.png", block.SrcMat);
// 取白色区域
var bMat = OpenCvCommonHelper.Threshold(block.SrcMat, new Scalar(255, 255, 255), new Scalar(255, 255, 255));
// Cv2.ImWrite($"block_b_{Name}.png", bMat);
// 矩形识别
Cv2.FindContours(bMat, out var contours, out _, RetrievalModes.External,
ContourApproximationModes.ApproxSimple);
if (contours.Length > 0)
{
var boxes = contours.Select(Cv2.BoundingRect)
.Where(w => w.Width >= 20 * assetScale && w.Height >= 18 * assetScale)
.OrderByDescending(w => w.Width).ToList();
if (boxes.Count is not 0)
{
IndexRect = boxes.First();
return false;
}
}
}
else
{
// 剪裁出IndexRect区域
var teamRa = region.DeriveCrop(AutoFightAssets.Instance.TeamRect);
var blockX = NameRect.X + NameRect.Width * 2 - 10;
var indexBlock = teamRa.DeriveCrop(new Rect(blockX + IndexRect.X, NameRect.Y + IndexRect.Y, IndexRect.Width,
IndexRect.Height));
// Cv2.ImWrite($"indexBlock_{Name}.png", indexBlock.SrcMat);
var count = OpenCvCommonHelper.CountGrayMatColor(indexBlock.CacheGreyMat, 255);
if (count * 1.0 / (IndexRect.Width * IndexRect.Height) > 0.5)
{
return false;
}
}
Logger.LogInformation("{Name} 当前出战", Name);
return true;
}
///
/// 普通攻击
///
/// 攻击时长,建议是200的倍数
public void Attack(int ms = 0)
{
while (ms >= 0)
{
if (Ct is { IsCancellationRequested: true })
{
return;
}
Simulation.SendInput.SimulateAction(GIActions.NormalAttack);
ms -= 200;
Sleep(200, Ct);
}
}
///
/// 使用元素战技 E
///
public void UseSkill(bool hold = false)
{
for (var i = 0; i < 1; i++)
{
if (Ct is { IsCancellationRequested: true })
{
return;
}
if (hold)
{
if (Name == "纳西妲")
{
Simulation.SendInput.SimulateAction(GIActions.ElementalSkill, KeyType.KeyDown);
Sleep(300, Ct);
for (int j = 0; j < 10; j++)
{
Simulation.SendInput.Mouse.MoveMouseBy(1000, 0);
Sleep(50); // 持续操作不应该被cts取消
}
Sleep(300); // 持续操作不应该被cts取消
Simulation.SendInput.SimulateAction(GIActions.ElementalSkill, KeyType.KeyUp);
}
else if (Name == "坎蒂丝")
{
Simulation.SendInput.SimulateAction(GIActions.ElementalSkill, KeyType.KeyDown);
Thread.Sleep(3000);
Simulation.SendInput.SimulateAction(GIActions.ElementalSkill, KeyType.KeyUp);
}
else
{
Simulation.SendInput.SimulateAction(GIActions.ElementalSkill, KeyType.Hold);
}
}
else
{
Simulation.SendInput.SimulateAction(GIActions.ElementalSkill);
}
Sleep(200, Ct);
var region = CaptureToRectArea();
ThrowWhenDefeated(region, Ct); // 检测是不是要跑神像
var cd = AfterUseSkill(region);
if (cd > 0)
{
Logger.LogInformation(hold ? "{Name} 长按元素战技,cd:{Cd} 秒" : "{Name} 点按元素战技,cd:{Cd} 秒", Name,
Math.Round(cd, 2));
return;
}
}
}
///
/// 使用完元素战技的回调,注意,不会在这里检测是不是需要跑七天神像
/// UseSkill 方法内会调用,如果没有使用UseSkill但是释放了技能之后记得调用一下这个方法
///
/// 当前技能CD
public double AfterUseSkill(ImageRegion? givenRegion = null)
{
LastSkillTime = DateTime.UtcNow;
if (ManualSkillCd > 0)
{
return GetSkillCdSeconds();
}
var region = givenRegion ?? CaptureToRectArea();
return GetSkillCurrentCd(region);
}
///
/// 元素战技是否正在CD中
/// 右下 267x132
/// 77x77
///
private double GetSkillCurrentCd(ImageRegion imageRegion)
{
var eRa = imageRegion.DeriveCrop(AutoFightAssets.Instance.ECooldownRect);
var eRaWhite = OpenCvCommonHelper.InRangeHsv(eRa.SrcMat, new Scalar(0, 0, 235), new Scalar(0, 25, 255));
var text = OcrFactory.Paddle.OcrWithoutDetector(eRaWhite);
var cd = StringUtils.TryParseDouble(text);
if (cd > 0 && cd <= CombatAvatar.SkillCd)
{
OcrSkillCd = DateTime.UtcNow.AddSeconds(cd);
}
return cd;
}
///
/// 使用元素爆发 Q
/// Q释放等待 2s 超时认为没有Q技能
///
public void UseBurst()
{
// var isBurstReleased = false;
for (var i = 0; i < 10; i++)
{
if (Ct is { IsCancellationRequested: true })
{
return;
}
Simulation.SendInput.SimulateAction(GIActions.ElementalBurst);
Sleep(200, Ct);
var region = CaptureToRectArea();
ThrowWhenDefeated(region, Ct);
var notActiveCount = CombatScenes.GetAvatars().Count(avatar => !avatar.IsActive(region));
if (notActiveCount == 0)
{
// isBurstReleased = true;
Sleep(1500, Ct);
return;
}
// else
// {
// if (!isBurstReleased)
// {
// var cd = GetBurstCurrentCd(content);
// if (cd > 0)
// {
// Logger.LogInformation("{Name} 释放元素爆发,cd:{Cd}", Name, cd);
// // todo 把cd加入执行队列
// return;
// }
// }
// }
}
}
// ///
// /// 元素爆发是否正在CD中
// /// 右下 157x165
// /// 110x110
// ///
// public double GetBurstCurrentCd(CaptureContent content)
// {
// var qRa = content.CaptureRectArea.Crop(AutoFightAssets.Instance.QRect);
// var text = OcrFactory.Paddle.Ocr(qRa.SrcGreyMat);
// return StringUtils.TryParseDouble(text);
// }
///
/// 冲刺
///
public void Dash(int ms = 0)
{
if (Ct is { IsCancellationRequested: true })
{
return;
}
if (ms == 0)
{
ms = 200;
}
Simulation.SendInput.SimulateAction(GIActions.SprintMouse, KeyType.KeyDown);
Sleep(ms); // 冲刺不能被cts取消
Simulation.SendInput.SimulateAction(GIActions.SprintMouse, KeyType.KeyUp);
}
public void Walk(string key, int ms)
{
if (Ct is { IsCancellationRequested: true })
{
return;
}
User32.VK vk = User32.VK.VK_NONAME;
if (key == "w")
{
vk = GIActions.MoveForward.ToActionKey().ToVK();
}
else if (key == "s")
{
vk = GIActions.MoveBackward.ToActionKey().ToVK();
}
else if (key == "a")
{
vk = GIActions.MoveLeft.ToActionKey().ToVK();
}
else if (key == "d")
{
vk = GIActions.MoveRight.ToActionKey().ToVK();
}
if (vk == User32.VK.VK_NONAME)
{
return;
}
Simulation.SendInput.Keyboard.KeyDown(vk);
Sleep(ms); // 行走不能被cts取消
Simulation.SendInput.Keyboard.KeyUp(vk);
}
///
/// 移动摄像机
///
/// 负数是左移,正数是右移
///
public void MoveCamera(int pixelDeltaX, int pixelDeltaY)
{
Simulation.SendInput.Mouse.MoveMouseBy(pixelDeltaX, pixelDeltaY);
}
///
/// 等待
///
///
public void Wait(int ms)
{
Sleep(ms); // 由于存在宏操作,等待不应被cts取消
}
///
///
/// 根据cd推算E技能是否好了
///
/// 强制指定技能CD
/// log是否输出
/// 是否好了
public bool IsSkillReady(bool printLog = false)
{
var cd = GetSkillCdSeconds();
if (cd > 0)
{
if (printLog)
{
Logger.LogInformation("{Name}的E技能未准备好,CD还有{Seconds}秒", Name, Math.Round(cd, 2));
}
return false;
}
return true;
}
///
/// 计算上一次使用技能到现在还剩下多长时间的cd
///
///
public double GetSkillCdSeconds()
{
switch (ManualSkillCd)
{
case < 0:
{
var now = DateTime.UtcNow;
// 若未经过OCR的技能释放,上次时间加上最长的技能时间
var maxCd = Math.Max(CombatAvatar.SkillHoldCd, CombatAvatar.SkillCd);
var target =
LastSkillTime >= OcrSkillCd
? LastSkillTime.AddSeconds(Math.Max(CombatAvatar.SkillHoldCd, CombatAvatar.SkillCd))
: OcrSkillCd;
var result = now > target ? 0d : (target - now).TotalSeconds;
if (!(result > maxCd)) return result;
Logger.LogWarning("{Name}的当前技能CD大于其最大技能CD{MaxCd}。如果你没有调整系统时间的话,这是一个bug。", Name, maxCd);
return maxCd;
}
case > 0:
{
// 用户设置,所以直接通过上次释放技能的时间计算
var dif = DateTime.UtcNow - LastSkillTime;
if (ManualSkillCd > dif.TotalSeconds)
{
return ManualSkillCd - dif.TotalSeconds;
}
break;
}
}
return 0;
}
///
/// 等待技能CD
///
/// CancellationToken
public async Task WaitSkillCd(CancellationToken ct = default)
{
// 获取CD时间
if (IsSkillReady())
{
return;
}
var s = GetSkillCdSeconds() + 0.2;
Logger.LogInformation("{Name}的E技能CD未结束,等待{Seconds}秒", Name, Math.Round(s, 2));
await Delay((int)Math.Ceiling(s * 1000), ct);
}
///
/// 跳跃
///
public void Jump()
{
Simulation.SendInput.SimulateAction(GIActions.Jump);
}
///
/// 重击
///
public void Charge(int ms = 0)
{
if (ms == 0)
{
ms = 1000;
}
if (Name == "那维莱特")
{
var dpi = TaskContext.Instance().DpiScale;
Simulation.SendInput.SimulateAction(GIActions.NormalAttack, KeyType.KeyDown);
while (ms >= 0)
{
if (Ct is { IsCancellationRequested: true })
{
return;
}
Simulation.SendInput.Mouse.MoveMouseBy((int)(1000 * dpi), 0);
ms -= 50;
Sleep(50); // 持续操作不应该被cts取消
}
Simulation.SendInput.SimulateAction(GIActions.NormalAttack, KeyType.KeyUp);
}
else if (Name == "恰斯卡")
{
var dpi = TaskContext.Instance().DpiScale;
Simulation.SendInput.SimulateAction(GIActions.NormalAttack, KeyType.KeyDown);
int tick = -4; // 起飞那一刻需要多一点点时间用来矫正视角高度
while (ms >= 0)
{
if (Ct is { IsCancellationRequested: true })
{
return;
}
// 恰在蓄力时转得越快越容易把视角趋向于水平
// 基于上面这个特性,如果我们用同一个鼠标方向向量,大致能在所有设备上控制视角高低(只要帧率不太低)
// 恰的子弹上膛机制:怪物要在HUD准星框内超过一定时长(体感0.2-0.3秒)才能让子弹上膛。所以搜索敌人要低速。不然敌人体型小或者远就很容易锁不上。
const double lowspeed = 0.7, highspeed = 50;
double rateX, rateY;
if (tick < 3)
{
rateX = highspeed;
rateY = highspeed * 0.23;
}
else if (tick < 40)
{
rateX = lowspeed * 0.7;
rateY = 0;
}
else if (tick < 43)
{
rateX = highspeed;
rateY = highspeed * 0.4;
}
else if (tick < 70)
{
rateX = lowspeed * 0.9;
rateY = 0;
}
else if (tick < 73)
{
rateX = highspeed;
rateY = highspeed;
}
else
{
rateX = lowspeed;
rateY = 0;
}
Simulation.SendInput.Mouse.MoveMouseBy((int)(rateX * 50 * dpi), (int)(rateY * 50 * dpi));
tick = (tick + 1) % 100;
Sleep(25);
ms -= 25;
}
Simulation.SendInput.SimulateAction(GIActions.NormalAttack, KeyType.KeyUp);
}
else
{
Simulation.SendInput.SimulateAction(GIActions.NormalAttack, KeyType.KeyDown);
Sleep(ms); // 持续操作不应该被cts取消
Simulation.SendInput.SimulateAction(GIActions.NormalAttack, KeyType.KeyUp);
}
}
public void MouseDown(string key = "left")
{
key = key.ToLower();
if (key == "left")
{
Simulation.SendInput.Mouse.LeftButtonDown();
}
else if (key == "right")
{
Simulation.SendInput.Mouse.RightButtonDown();
}
else if (key == "middle")
{
Simulation.SendInput.Mouse.MiddleButtonDown();
}
}
public void MouseUp(string key = "left")
{
key = key.ToLower();
if (key == "left")
{
Simulation.SendInput.Mouse.LeftButtonUp();
}
else if (key == "right")
{
Simulation.SendInput.Mouse.RightButtonUp();
}
else if (key == "middle")
{
Simulation.SendInput.Mouse.MiddleButtonUp();
}
}
public void Click(string key = "left")
{
key = key.ToLower();
if (key == "left")
{
Simulation.SendInput.Mouse.LeftButtonClick();
}
else if (key == "right")
{
Simulation.SendInput.Mouse.RightButtonClick();
}
else if (key == "middle")
{
Simulation.SendInput.Mouse.MiddleButtonClick();
}
}
public void MoveBy(int x, int y)
{
Simulation.SendInput.Mouse.MoveMouseBy(x, y);
}
public void KeyDown(string key)
{
var vk = KeyBindingsSettingsPageViewModel.MappingKey(User32Helper.ToVk(key));
switch (key)
{
case "VK_LBUTTON":
Simulation.SendInput.Mouse.LeftButtonDown();
break;
case "VK_RBUTTON":
Simulation.SendInput.Mouse.RightButtonDown();
break;
case "VK_MBUTTON":
Simulation.SendInput.Mouse.MiddleButtonDown();
break;
case "VK_XBUTTON1":
Simulation.SendInput.Mouse.XButtonDown(0x0001);
break;
case "VK_XBUTTON2":
Simulation.SendInput.Mouse.XButtonDown(0x0001);
break;
default:
Simulation.SendInput.Keyboard.KeyDown(vk);
break;
}
}
public void KeyUp(string key)
{
var vk = KeyBindingsSettingsPageViewModel.MappingKey(User32Helper.ToVk(key));
switch (key)
{
case "VK_LBUTTON":
Simulation.SendInput.Mouse.LeftButtonUp();
break;
case "VK_RBUTTON":
Simulation.SendInput.Mouse.RightButtonUp();
break;
case "VK_MBUTTON":
Simulation.SendInput.Mouse.MiddleButtonUp();
break;
case "VK_XBUTTON1":
Simulation.SendInput.Mouse.XButtonUp(0x0001);
break;
case "VK_XBUTTON2":
Simulation.SendInput.Mouse.XButtonUp(0x0001);
break;
default:
Simulation.SendInput.Keyboard.KeyUp(vk);
break;
}
}
public void KeyPress(string key)
{
var vk = KeyBindingsSettingsPageViewModel.MappingKey(User32Helper.ToVk(key));
switch (key)
{
case "VK_LBUTTON":
Simulation.SendInput.Mouse.LeftButtonClick();
break;
case "VK_RBUTTON":
Simulation.SendInput.Mouse.RightButtonClick();
break;
case "VK_MBUTTON":
Simulation.SendInput.Mouse.MiddleButtonClick();
break;
case "VK_XBUTTON1":
Simulation.SendInput.Mouse.XButtonClick(0x0001);
break;
case "VK_XBUTTON2":
Simulation.SendInput.Mouse.XButtonClick(0x0001);
break;
default:
Simulation.SendInput.Keyboard.KeyPress(vk);
break;
}
}
///
/// 从配置字符串中查找角色cd
/// 仅有角色名时返回 -1 ,没找到角色返回null
///
/// 角色名
/// 序列
///
public static double? ParseActionSchedulerByCd(string avatarName, string input)
{
if (string.IsNullOrEmpty(input) || string.IsNullOrEmpty(avatarName))
return null;
var searchIndex = input.Length - 1;
while (true)
{
// 逆向查找角色名最后一次出现的位置
var foundIndex = input.LastIndexOf(avatarName, searchIndex, StringComparison.Ordinal);
if (foundIndex == -1) return null;
// 验证前向边界(分号或字符串起点)
var startValid = foundIndex == 0 ||
input[foundIndex - 1] == ';';
// 验证后向边界(逗号或分号/字符串终点)
var endValid = foundIndex + avatarName.Length == input.Length ||
input[foundIndex + avatarName.Length] == ',' ||
input[foundIndex + avatarName.Length] == ';';
if (startValid && endValid)
{
var valueStart = foundIndex + avatarName.Length;
// 处理逗号后的数值部分
if (valueStart >= input.Length || input[valueStart] != ',') return -1;
var valueEnd = input.IndexOf(';', valueStart);
if (valueEnd == -1) valueEnd = input.Length;
if (double.TryParse(input.AsSpan(valueStart + 1, valueEnd - valueStart - 1),
out var result))
{
return result;
}
// 存在角色名但没有数值的情况
return -1;
}
// 更新搜索范围继续查找
searchIndex = foundIndex - 1;
if (searchIndex < 0) break;
}
return null;
}
}