Files
better-genshin-impact/BetterGenshinImpact/GameTask/AutoFishing/RodNet.cs
FishmanTheMurloc 6d4f8b80e8 Feat/multi lan (#1336)
* 记录一次对hutaofisher的访谈,帮助开发者理解其算法

* 本地化HelloWorld

* .csproj取消windows版本号,此处导致了IDE在新建代码文件和自动生成代码时,默认命名空间丢失的问题。已知VisualStudio和ReSharper存在这个问题。

* 优化扩展方法写法,改为从localizer扩展;Converter优化写法,避免冲突;新增两种语言,待测试ocr效果

* Revert ".csproj取消windows版本号,此处导致了IDE在新建代码文件和自动生成代码时,默认命名空间丢失的问题。已知VisualStudio和ReSharper存在这个问题。"

This reverts commit 8bd7ee74c5.

* localizer改为由构造函数传入以支持单元测试;一个英语上钩的单元测试

* 传送任务支持英语游戏界面;本地化参数挪至OtherConfig类下,但界面位置暂不挪动,待定

* 调整resx位置风格,放在直接使用字符串的类下;一条龙合成树脂及领取每日奖励支持游戏内中英双语

* 删除无用碎片文件

* 删去两个不必要的Sdcb包引用

* Paddle服务类去掉分类模型;检测和识别新增支持繁中和法语,配有单元测试;因小语种识别效果不理想,使用正则匹配替换多处识别文本相等或包含判断;钓鱼、一条龙合成树脂及领取每日奖励支持游戏内繁中和法语;

* 检查今日奖励任务的多语言化;右侧联机的P图标检测区域宽度缩减,避免英语角色名被误识别成P

* AutoDomainTask的游戏多语言化,由于我的游戏账号无法测试,仅配一些测试用例

* 修复有3个Mizuki导致异常的bug,临时用拼音代替新角色英文名,并为该数据初始化方法添加单元测试

* 瓦雷莎删去别名“牛牛”,因荒泷一斗已占用此别名;别名加载和读取优化

* 加个锁避免单元测试中多线程初始化paddle崩溃
2025-03-28 11:00:08 +08:00

208 lines
7.5 KiB
C#
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
using OpenCvSharp;
using System;
using System.Linq;
namespace BetterGenshinImpact.GameTask.AutoFishing;
/// <summary>
/// copy from https://github.com/myHuTao-qwq/HutaoFisher/blob/master/src/rodnet.cpp
///
/// 以下是hutaofisher的访谈
///
/// 修改我的qq昵称: 03-20 00:22:48
/// 有任何问题可以问我 只要我在工位而不是在实验室里跟XXXX斗智斗勇就回答你
///
/// 额 我的算法有几个感觉挺符合直觉的假设
///
/// 就是 你抛竿的时候鱼是否会咬钩取决于鱼饵落点到鱼的距离
///
/// 这里的距离是鱼饵落点的(x, y, z)坐标与鱼的(x, y, z)坐标之差在xy平面(水平面)上的投影
///
/// 现在问题转化成了如何估计这两个坐标
///
/// 这里我们先约定x轴就是水平面上与屏幕长边平行的线 y轴就是屏幕短边的对于 剩下一个z就是游戏里面往天上的方向
///
/// 然后经验告诉我鱼饵落点环的x几乎为0
///
/// 剩下我们可以用鱼饵落点环的长和宽算出y和z
///
/// 再近似鱼的bouding box的中心就是鱼计算咬钩的那个点 同时近似一个大类里面的所有鱼的z是相同的(这个参数可学习) 于是我们也可以计算出鱼的xyz
///
/// 最后一步就是把对应的投影距离算出来 然后线性回归一下得到太近 刚好 太远三个类
///
/// tmd 今天我意识到 XXXX可不就是XXXX
///
/// 哦 到这一步以后剩下的就很弱智了 远了挪近一点 近了挪远一点 调调参差不多得了
/// </summary>
public class RodNet
{
const double alpha = 1734.34 / 2.5;
static readonly double[] dz = [ 0.561117562965, 0.637026851288, 0.705579317577,
1.062734463845, 0.949307580751, 1.015620474332,
1.797904203405, 1.513476738412, 1.013873007495,
1.159949954831, 1.353650974146, 1.302893195071 ];
static readonly double[,] theta = {
{-0.262674397633, 0.317025388945, -0.457150765450, 0.174522158281,
-0.957110676932, -0.095339800558, -0.119519564026, -0.139914755291,
-0.580893838475, 0.702302245305, 0.271575851220, 0.708473199472,
0.699108382380},
{-1.062702043060, -0.280779165943, -0.289891597384, 0.220173840594,
0.493463877037, -0.326492366566, 1.215859141832, 1.607133159643,
1.619199133672, 0.356402262447, 0.365385941958, 0.411869019381,
0.224962055122},
{0.460481782256, 0.048180392806, 0.475529271293, -0.150186412126,
0.135512307120, 0.087365984352, -1.317661146364, -1.882438208662,
-1.502483859283, -0.580228373556, -1.005821958682, -1.184199131739,
-1.285988918494}
};
static readonly double[] B = [1.241950004386, 3.051113640564, -3.848898190087];
static readonly double[] offset = [ 0.4, 0.2, 0.4, 0, 0.3, 0.3,
0.3, 0.15, 0.5, 0.5, 0.5, 0.5 ];
static void F(double[] dst, double[] x, double[] y)
{
double y0 = x[0], z0 = x[1], t = x[2];
double tmp = (y0 + t * z0) * (y0 + t * z0) - 1;
dst[0] = Math.Sqrt((1 + t * t) / tmp) - y[0];
dst[1] = (1 + t * t) * z0 / tmp - y[1];
dst[2] = ((t * t - 1) * y0 * z0 + t * (y0 * y0 - z0 * z0 - 1)) / tmp - y[2];
}
static void DfInv(double[] dst, double[] x)
{
double y0 = x[0], z0 = x[1], t = x[2];
double tmp1 = (y0 + t * z0) * (y0 + t * z0) - 1;
double tmp2 = 1 + t * t;
dst[0] = (1 - y0 * y0 + z0 * z0) / y0 * Math.Sqrt(tmp1 / tmp2);
dst[1] = -z0 * (y0 * y0 + t * (t * (1 + z0 * z0) + 2 * y0 * z0)) / tmp2 / y0;
dst[2] = ((t * t - 1) * y0 * z0 + t * (y0 * y0 - z0 * z0 - 1)) / y0 / tmp2;
dst[3] = -2 * z0 * Math.Sqrt(tmp1 / tmp2);
dst[4] = tmp1 / tmp2;
dst[5] = 0;
dst[6] = -z0 / y0 * Math.Sqrt(tmp1 / tmp2);
dst[7] = (y0 + t * z0) * (y0 + t * z0) / y0;
dst[8] = 1 + t * z0 / y0;
}
static bool NewtonRaphson(Action<double[], double[], double[]> f, Action<double[], double[]> dfInv, double[] dst, double[] y,
double[] init, int n, int maxIter, double eps)
{
double[] fEst = new double[n];
double[] dfInvMat = new double[n * n];
double[] x = new double[n];
double err;
Array.Copy(init, x, n);
for (int iter = 0; iter < maxIter; iter++)
{
err = 0;
f(fEst, x, y);
for (int i = 0; i < n; i++)
{
err += Math.Abs(fEst[i]);
}
if (err < eps)
{
Array.Copy(x, dst, n);
// printf("Newton-Raphson solver converge after %d steps, err: %lf !\n",
// iter, err);
return true;
}
dfInv(dfInvMat, x);
for (int i = 0; i < n; i++)
{
for (int j = 0; j < n; j++)
{
x[i] -= dfInvMat[n * i + j] * fEst[j];
}
}
}
return false;
}
static void Softmax(double[] dst, double[] x, int n)
{
double sum = 0;
for (int i = 0; i < n; i++)
{
dst[i] = Math.Exp(x[i]);
sum += dst[i];
}
for (int i = 0; i < n; i++)
{
dst[i] /= sum;
}
}
public static int GetRodState(RodInput input)
{
double a, b, v0, u, v;
a = (input.rod_x2 - input.rod_x1) / 2 / alpha;
b = (input.rod_y2 - input.rod_y1) / 2 / alpha;
if (a < b)
{
(b, a) = (a, b);
}
v0 = (288 - (input.rod_y1 + input.rod_y2) / 2) / alpha;
u = (input.fish_x1 + input.fish_x2 - input.rod_x1 - input.rod_x2) / 2 / alpha;
v = (288 - (input.fish_y1 + input.fish_y2) / 2) / alpha;
double[] y0z0t = new double[3];
double[] abv0 = [a, b, v0];
double[] init = [30, 15, 1];
bool solveSuccess = NewtonRaphson(F, DfInv, y0z0t, abv0, init, 3, 1000, 1e-6);
if (!solveSuccess)
{
return -1;
}
double y0 = y0z0t[0], z0 = y0z0t[1], t = y0z0t[2];
double x, y, dist;
x = u * (z0 + dz[input.fish_label]) * Math.Sqrt(1 + t * t) / (t - v);
y = (z0 + dz[input.fish_label]) * (1 + t * v) / (t - v);
dist = Math.Sqrt(x * x + (y - y0) * (y - y0));
double[] logits = new double[3];
for (int i = 0; i < 3; i++)
{
logits[i] = theta[i, 0] * dist + theta[i, 1 + input.fish_label] + B[i];
}
double[] pred = new double[3];
Softmax(pred, logits, 3);
pred[0] -= offset[input.fish_label];
return Array.IndexOf(pred, pred.Max());
}
public static int GetRodState(Rect rod, Rect fish, int fishTypeIndex)
{
RodInput input = new()
{
rod_x1 = rod.Left,
rod_x2 = rod.Right,
rod_y1 = rod.Top,
rod_y2 = rod.Bottom,
fish_x1 = fish.Left,
fish_x2 = fish.Right,
fish_y1 = fish.Top,
fish_y2 = fish.Bottom,
fish_label = fishTypeIndex
};
return GetRodState(input);
}
}