Skip to content

Commit 48b5697

Browse files
Szabkadaniel-widrick
authored andcommitted
Epic bobber detection via image analysis
Inspired by https://www.codeproject.com/Articles/10248/Motion-Detection-Algorithms
1 parent 7fdd497 commit 48b5697

File tree

5 files changed

+175
-22
lines changed

5 files changed

+175
-22
lines changed

UltimateFishBot/Classes/BodyParts/Eyes.cs

Lines changed: 85 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -3,9 +3,12 @@
33
using System.ComponentModel;
44
using System.Drawing;
55
using System.Drawing.Imaging;
6+
using System.IO;
67
using System.Runtime.InteropServices;
78
using System.Threading;
89
using System.Threading.Tasks;
10+
using AForge.Imaging;
11+
using AForge.Imaging.Filters;
912
using Serilog;
1013
using UltimateFishBot.Classes.Helpers;
1114

@@ -18,22 +21,29 @@ class Eyes
1821
private IntPtr Wow;
1922
private Bitmap capturedCursorIcon;
2023
private Dictionary<Win32.Point, int> bobberPosDict;
24+
private Bitmap background;
25+
private Rectangle wowRectangle;
2126

22-
public Eyes(IntPtr wowWindow)
23-
{
27+
public Eyes(IntPtr wowWindow) {
2428
setWow(wowWindow);
2529
bobberPosDict = new Dictionary<Win32.Point, int>();
26-
2730
}
2831

2932
public void setWow(IntPtr wowWindow) {
3033
this.Wow = wowWindow;
34+
m_noFishCursor = Win32.GetNoFishCursor(this.Wow);
35+
wowRectangle = Win32.GetWowRectangle(this.Wow);
36+
}
37+
38+
// capture in grayscale
39+
public void updateBackground() {
40+
background = new Grayscale(0.3725, 0.6154, 0.0121).Apply(Win32.CaptureWindow(Wow));
41+
background = new Pixellate().Apply(background);
42+
3143
}
3244

3345
public async Task<Win32.Point> LookForBobber(CancellationToken cancellationToken)
3446
{
35-
m_noFishCursor = Win32.GetNoFishCursor(this.Wow);
36-
Rectangle wowRectangle = Win32.GetWowRectangle(this.Wow);
3747
if (System.IO.File.Exists("capturedcursor.bmp")) {
3848
capturedCursorIcon = new Bitmap("capturedcursor.bmp", true);
3949
}
@@ -53,23 +63,33 @@ public void setWow(IntPtr wowWindow) {
5363
//Log.Information("Using custom area");
5464
}
5565
Log.Information("Scanning area: " + scanArea.Left.ToString() + " , " + scanArea.Top.ToString() + " , " + scanArea.Right.ToString() + " , " + scanArea.Bottom.ToString() + " cs: " + bobberPosDict.Keys.Count.ToString());
56-
Win32.Point bobberPos;
57-
bobberPos.x = 0;
58-
bobberPos.y = 0;
59-
// utilize previous hits
60-
foreach (KeyValuePair<Win32.Point, int> pos in System.Linq.Enumerable.OrderBy(bobberPosDict, (key => key.Value))) {
61-
// do something with item.Key and item.Value
62-
if (await MoveMouseAndCheckCursor(pos.Key.x, pos.Key.y, cancellationToken)) {
63-
bobberPos = pos.Key;
64-
Log.Information("Bobber position cache hit. ({bx},{by})", bobberPos.x, bobberPos.y);
66+
Win32.Point bobberPos= new Win32.Point { x = 0, y = 0 };
67+
68+
foreach (Win32.Point dp in PointOfScreenDifferences()) {
69+
if (await MoveMouseAndCheckCursor(dp.x, dp.y, cancellationToken,2)) {
70+
bobberPos = dp;
71+
Log.Information("Bobber imagescan hit. ({bx},{by})", bobberPos.x, bobberPos.y);
6572
break;
6673
}
6774
}
75+
76+
if (bobberPos.x == 0 && bobberPos.y == 0) {
77+
// utilize previous hits
78+
foreach (KeyValuePair<Win32.Point, int> pos in System.Linq.Enumerable.OrderBy(bobberPosDict, (key => key.Value))) {
79+
// do something with item.Key and item.Value
80+
if (await MoveMouseAndCheckCursor(pos.Key.x, pos.Key.y, cancellationToken,2)) {
81+
bobberPos = pos.Key;
82+
Log.Information("Bobber position cache hit. ({bx},{by})", bobberPos.x, bobberPos.y);
83+
break;
84+
}
85+
}
86+
}
6887
if (bobberPos.x == 0 && bobberPos.y == 0) {
69-
if (Properties.Settings.Default.AlternativeRoute)
88+
if (Properties.Settings.Default.AlternativeRoute) {
7089
bobberPos = await LookForBobberSpiralImpl(scanArea, bobberPos, Properties.Settings.Default.ScanningSteps, Properties.Settings.Default.ScanningRetries, cancellationToken);
71-
else
90+
} else {
7291
bobberPos = await LookForBobberImpl(scanArea, bobberPos, Properties.Settings.Default.ScanningSteps, Properties.Settings.Default.ScanningRetries, cancellationToken);
92+
}
7393
}
7494
if (bobberPos.x != 0 && bobberPos.y != 0) {
7595
int hitcount = 1;
@@ -86,8 +106,51 @@ public void setWow(IntPtr wowWindow) {
86106

87107
}
88108

109+
private List<Win32.Point> PointOfScreenDifferences() {
110+
Bitmap castbmp = Win32.CaptureWindow(Wow);
111+
112+
FiltersSequence processingFilter = new FiltersSequence();
113+
processingFilter.Add(new Grayscale(0.3725, 0.6154, 0.0121));
114+
processingFilter.Add(new Pixellate());
115+
processingFilter.Add(new Difference(background));
116+
processingFilter.Add(new Threshold(15));
117+
processingFilter.Add(new Erosion());
118+
119+
var blobCounter = new BlobCounter();
120+
blobCounter.ProcessImage(processingFilter.Apply(castbmp));
121+
122+
Rectangle[] brl = blobCounter.GetObjectsRectangles();
123+
Log.Information("Bobber imagescan brl: {brl}", brl.Length);
124+
List<Win32.Point> sdl = new List<Win32.Point>();
125+
foreach (Rectangle br in brl) {
126+
Win32.Point pt = new Win32.Point { x = (br.Left + br.Left + br.Right) * 4 / 12, y = (br.Top+br.Bottom+br.Bottom)*4/12 };
127+
Win32.ClientToScreen(Wow, ref pt);
128+
if ((br.Right - br.Left)>9&& (br.Bottom - br.Top)>9) {
129+
// Win32.Point pt = new Win32.Point { x= wowRectangle.X+(br.Left + br.Right) / 2, y= wowRectangle.Y+(br.Top+br.Bottom)/2 };
130+
Log.Information("Bobber imagescan br: {bx},{by} - {w},{h}", pt.x,pt.y, (br.Right-br.Left),(br.Bottom-br.Top));
131+
sdl.Add(pt);
132+
// } else {
133+
// Log.Information("Bobber imagescan ignore br: {bx},{by} - {w},{h}", pt.x,pt.y, (br.Right-br.Left),(br.Bottom-br.Top));
134+
}
135+
}
136+
// debug
137+
/*
138+
Bitmap bmpDst = new Bitmap(castbmp);
139+
using (var g = Graphics.FromImage(bmpDst)) {
140+
foreach (var br in brl) {
141+
if ((br.Right - br.Left) > 11 && (br.Bottom - br.Top) > 11) {
142+
g.DrawRectangle(Pens.White, br);
143+
}
144+
}
145+
}
146+
bmpDst.Save("sc_"+DateTime.UtcNow.Ticks+".png", ImageFormat.Png);
147+
*/
148+
149+
return sdl;
150+
}
151+
89152
public async Task<bool> SetMouseToBobber(Win32.Point bobberPos, CancellationToken cancellationToken) {// move mouse to previous recorded position and check shape
90-
if (!await MoveMouseAndCheckCursor(bobberPos.x, bobberPos.y, cancellationToken)) {
153+
if (!await MoveMouseAndCheckCursor(bobberPos.x, bobberPos.y, cancellationToken,1)) {
91154
Log.Information("Bobber lost. ({bx},{by})", bobberPos.x, bobberPos.y);
92155
int fixr = 24;
93156
Win32.Rect scanArea;
@@ -122,7 +185,7 @@ public async Task<bool> SetMouseToBobber(Win32.Point bobberPos, CancellationToke
122185
for (int tryCount = 0; tryCount < retries; ++tryCount) {
123186
for (int x = (int)(scanArea.Left + (XOFFSET * tryCount)); x < scanArea.Right; x += XPOSSTEP) {
124187
for (int y = scanArea.Top; y < scanArea.Bottom; y += YPOSSTEP) {
125-
if (await MoveMouseAndCheckCursor(x, y, cancellationToken)) {
188+
if (await MoveMouseAndCheckCursor(x, y, cancellationToken,1)) {
126189
bobberPos.x = x;
127190
bobberPos.y = y;
128191
return bobberPos;
@@ -166,7 +229,7 @@ public async Task<bool> SetMouseToBobber(Win32.Point bobberPos, CancellationToke
166229
}
167230
x += dx;
168231
y += dy;
169-
if (await MoveMouseAndCheckCursor(x, y, cancellationToken)) {
232+
if (await MoveMouseAndCheckCursor(x, y, cancellationToken,1)) {
170233
bobberPos.x = x;
171234
bobberPos.y = y;
172235
return bobberPos;
@@ -177,14 +240,14 @@ public async Task<bool> SetMouseToBobber(Win32.Point bobberPos, CancellationToke
177240
return bobberPos;
178241
}
179242

180-
private async Task<bool> MoveMouseAndCheckCursor(int x, int y, CancellationToken cancellationToken) {
243+
private async Task<bool> MoveMouseAndCheckCursor(int x, int y, CancellationToken cancellationToken,int mpy) {
181244
if (cancellationToken.IsCancellationRequested)
182245
throw new TaskCanceledException();
183246

184247
Win32.MoveMouse(x, y);
185248

186249
// Pause (give the OS a chance to change the cursor)
187-
await Task.Delay(Properties.Settings.Default.ScanningDelay, cancellationToken);
250+
await Task.Delay(mpy*Properties.Settings.Default.ScanningDelay, cancellationToken);
188251

189252
Win32.CursorInfo actualCursor = Win32.GetCurrentCursor();
190253

@@ -221,7 +284,7 @@ private static bool ImageCompare(Bitmap bmp1, Bitmap bmp2) {
221284
return false;
222285
}
223286

224-
int bytes = bmp1.Width * bmp1.Height * (Image.GetPixelFormatSize(bmp1.PixelFormat) / 8);
287+
int bytes = bmp1.Width * bmp1.Height * (System.Drawing.Image.GetPixelFormatSize(bmp1.PixelFormat) / 8);
225288

226289
bool result = true;
227290
byte[] b1bytes = new byte[bytes];

UltimateFishBot/Classes/Helpers/Win32.cs

Lines changed: 77 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,9 @@ private enum ShowWindowEnum
5858
[DllImport("user32.dll")]
5959
private static extern bool GetWindowRect(IntPtr hwnd, ref Rect rectangle);
6060

61+
[DllImport("user32.dll")]
62+
public static extern bool GetClientRect(IntPtr hWnd, out Rect lpRect);
63+
6164
[DllImport("user32.dll")]
6265
private static extern bool SetCursorPos(int X, int Y);
6366

@@ -85,12 +88,86 @@ private enum ShowWindowEnum
8588
[DllImport("user32.dll")]
8689
static extern bool ShowWindow(IntPtr hWnd, ShowWindowEnum flags);
8790

91+
[DllImport("User32.Dll")]
92+
public static extern bool ClientToScreen(IntPtr hWnd, ref Point point);
93+
94+
[DllImport("user32.dll")]
95+
public static extern IntPtr GetWindowDC(IntPtr hWnd);
96+
97+
[DllImport("user32.dll")]
98+
public static extern IntPtr ReleaseDC(IntPtr hWnd, IntPtr hDC);
99+
100+
[DllImport("gdi32.dll")]
101+
public static extern bool BitBlt(IntPtr hObject, int nXDest, int nYDest,
102+
int nWidth, int nHeight, IntPtr hObjectSource,
103+
int nXSrc, int nYSrc, int dwRop);
104+
[DllImport("gdi32.dll")]
105+
public static extern IntPtr CreateCompatibleBitmap(IntPtr hDC, int nWidth,
106+
int nHeight);
107+
[DllImport("gdi32.dll")]
108+
public static extern IntPtr CreateCompatibleDC(IntPtr hDC);
109+
[DllImport("gdi32.dll")]
110+
public static extern bool DeleteDC(IntPtr hDC);
111+
[DllImport("gdi32.dll")]
112+
public static extern bool DeleteObject(IntPtr hObject);
113+
[DllImport("gdi32.dll")]
114+
public static extern IntPtr SelectObject(IntPtr hDC, IntPtr hObject);
115+
88116
private const uint WM_LBUTTONDOWN = 513;
89117
private const uint WM_LBUTTONUP = 514;
90118

91119
private const uint WM_RBUTTONDOWN = 516;
92120
private const uint WM_RBUTTONUP = 517;
93121

122+
public const int SRCCOPY = 0x00CC0020; // BitBlt dwRop parameter
123+
124+
/// <summary>
125+
/// Creates an Image object containing a screen shot of a specific window
126+
/// </summary>
127+
/// <param name="handle">The handle to the window. (In windows forms, this is obtained by the Handle property)</param>
128+
/// <returns></returns>
129+
public static Bitmap CaptureWindow(IntPtr handle)
130+
{
131+
// get te hDC of the target window
132+
IntPtr hdcSrc = GetWindowDC(handle);
133+
// get the size
134+
Rect windowRect = new Rect();
135+
GetWindowRect(handle, ref windowRect);
136+
137+
Rect clientRect = new Rect();
138+
GetClientRect(handle, out clientRect);
139+
140+
141+
Point point = new Point { x = 0, y = 0 };
142+
ClientToScreen(handle, ref point);
143+
144+
int x = point.x - windowRect.Left;
145+
int y = point.y - windowRect.Top;
146+
int width = (clientRect.Right - clientRect.Left);
147+
int height = (clientRect.Bottom - clientRect.Top);
148+
149+
150+
// create a device context we can copy to
151+
IntPtr hdcDest = CreateCompatibleDC(hdcSrc);
152+
// create a bitmap we can copy it to,
153+
// using GetDeviceCaps to get the width/height
154+
IntPtr hBitmap = CreateCompatibleBitmap(hdcSrc, width, height);
155+
// select the bitmap object
156+
IntPtr hOld = SelectObject(hdcDest, hBitmap);
157+
// bitblt over
158+
BitBlt(hdcDest, 0, 0, width, height, hdcSrc, x, y, SRCCOPY);
159+
// restore selection
160+
SelectObject(hdcDest, hOld);
161+
// clean up
162+
DeleteDC(hdcDest);
163+
ReleaseDC(handle, hdcSrc);
164+
// get a .NET image object for it
165+
Bitmap img = Image.FromHbitmap(hBitmap);
166+
// free up the Bitmap object
167+
DeleteObject(hBitmap);
168+
return img;
169+
}
170+
94171
public static Rectangle GetWowRectangle(IntPtr Wow)
95172
{
96173
Rect Win32ApiRect = new Rect();

UltimateFishBot/Classes/Manager.cs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -271,6 +271,7 @@ private void ResetTimers()
271271
private async Task Fish(CancellationToken cancellationToken)
272272
{
273273
m_mouth.Say(Translate.GetTranslate("manager", "LABEL_CASTING"));
274+
m_eyes.updateBackground();
274275
await m_hands.Cast(cancellationToken);
275276

276277
m_mouth.Say(Translate.GetTranslate("manager", "LABEL_FINDING"));

UltimateFishBot/UltimateFishBot.csproj

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,15 @@
5656
<ApplicationIcon>icon.ico</ApplicationIcon>
5757
</PropertyGroup>
5858
<ItemGroup>
59+
<Reference Include="AForge, Version=2.2.5.0, Culture=neutral, PublicKeyToken=c1db6ff4eaa06aeb, processorArchitecture=MSIL">
60+
<HintPath>..\packages\AForge.2.2.5\lib\AForge.dll</HintPath>
61+
</Reference>
62+
<Reference Include="AForge.Imaging, Version=2.2.5.0, Culture=neutral, PublicKeyToken=ba8ddea9676ca48b, processorArchitecture=MSIL">
63+
<HintPath>..\packages\AForge.Imaging.2.2.5\lib\AForge.Imaging.dll</HintPath>
64+
</Reference>
65+
<Reference Include="AForge.Math, Version=2.2.5.0, Culture=neutral, PublicKeyToken=abba2e25397ee8c9, processorArchitecture=MSIL">
66+
<HintPath>..\packages\AForge.Math.2.2.5\lib\AForge.Math.dll</HintPath>
67+
</Reference>
5968
<Reference Include="CoreAudioApi">
6069
<HintPath>Resources\CoreAudioApi.dll</HintPath>
6170
</Reference>

UltimateFishBot/packages.config

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,8 @@
11
<?xml version="1.0" encoding="utf-8"?>
22
<packages>
3+
<package id="AForge" version="2.2.5" targetFramework="net46" />
4+
<package id="AForge.Imaging" version="2.2.5" targetFramework="net46" />
5+
<package id="AForge.Math" version="2.2.5" targetFramework="net46" />
36
<package id="Costura.Fody" version="1.3.5.0" targetFramework="net4" developmentDependency="true" />
47
<package id="Fody" version="1.29.3" targetFramework="net46" developmentDependency="true" />
58
<package id="Serilog" version="1.5.14" targetFramework="net46" />

0 commit comments

Comments
 (0)