Skip to content

Commit 47fd2d4

Browse files
author
siriusdemon
committed
workable version
1 parent 3fb1c94 commit 47fd2d4

File tree

5 files changed

+476
-0
lines changed

5 files changed

+476
-0
lines changed

.gitignore

+4
Original file line numberDiff line numberDiff line change
@@ -30,3 +30,7 @@
3030
*.exe
3131
*.out
3232
*.app
33+
34+
35+
.vs
36+
out

CMakeLists.txt

+18
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
cmake_minimum_required(VERSION 3.20)
2+
3+
project(coffeestory)
4+
5+
set(CMAKE_CXX_STANDARD 17)
6+
set(CMAKE_CXX_STANDARD_REQUIRED ON)
7+
8+
9+
include_directories("D:\\softwares\\opencv\\opencv\\build\\include\\")
10+
add_executable("coffeestory" "coffeestory.cpp")
11+
target_link_libraries(coffeestory "D:\\softwares\\opencv\\opencv\\build\\x64\\vc16\\lib\\opencv_world480.lib")
12+
13+
14+
15+
16+
17+
18+

CMakeSettings.json

+27
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
{
2+
"configurations": [
3+
{
4+
"name": "x64-Debug",
5+
"generator": "Ninja",
6+
"configurationType": "Debug",
7+
"inheritEnvironments": [ "msvc_x64_x64" ],
8+
"buildRoot": "${projectDir}\\out\\build\\${name}",
9+
"installRoot": "${projectDir}\\out\\install\\${name}",
10+
"cmakeCommandArgs": "",
11+
"buildCommandArgs": "",
12+
"ctestCommandArgs": ""
13+
},
14+
{
15+
"name": "x64-Release",
16+
"generator": "Ninja",
17+
"configurationType": "RelWithDebInfo",
18+
"buildRoot": "${projectDir}\\out\\build\\${name}",
19+
"installRoot": "${projectDir}\\out\\install\\${name}",
20+
"cmakeCommandArgs": "",
21+
"buildCommandArgs": "",
22+
"ctestCommandArgs": "",
23+
"inheritEnvironments": [ "msvc_x64_x64" ],
24+
"variables": []
25+
}
26+
]
27+
}

coffeestory.cpp

+342
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,342 @@
1+
#define WIN32_LEAN_AND_MEAN
2+
#include <windows.h>
3+
#include <opencv2/opencv.hpp>
4+
#include <iostream>
5+
#include <thread>
6+
#include "coffeestory.h"
7+
8+
static void msleep(int s) {
9+
std::this_thread::sleep_for(std::chrono::milliseconds(s));
10+
}
11+
12+
13+
// Reset focus to maplestory
14+
HWND MAPLE_WIN;
15+
int LEFT, TOP, RIGHT, BOTTOM = 0;
16+
static void reset_maple() {
17+
MAPLE_WIN = FindWindow(NULL, "MapleStory");
18+
RECT rect;
19+
if (GetWindowRect(MAPLE_WIN, &rect)) {
20+
LEFT = rect.left;
21+
TOP = rect.top;
22+
RIGHT = rect.right;
23+
BOTTOM = rect.bottom;
24+
}
25+
try {
26+
// Show the window
27+
ShowWindow(MAPLE_WIN, SW_SHOWNORMAL);
28+
// Bring the window to the foreground
29+
SetForegroundWindow(MAPLE_WIN);
30+
}
31+
catch (const std::exception& e) {
32+
std::cerr << "Failed to reset focus: " << e.what() << std::endl;
33+
}
34+
}
35+
36+
37+
static void leftclick(int x, int y) {
38+
x += LEFT;
39+
y += TOP;
40+
SetCursorPos(x, y);
41+
msleep(100);
42+
POINT position;
43+
GetCursorPos(&position);
44+
mouse_event(MOUSEEVENTF_LEFTDOWN, position.x, position.y, 0, 0);
45+
mouse_event(MOUSEEVENTF_LEFTUP, position.x, position.y, 0, 0);
46+
msleep(500);
47+
}
48+
49+
void pressEnter() {
50+
keybd_event(VK_RETURN, 0, 0, 0);
51+
keybd_event(VK_RETURN, 0, KEYEVENTF_KEYUP, 0);
52+
msleep(500);
53+
}
54+
55+
56+
57+
58+
59+
// Capture the windows
60+
class ScreenshotCapture {
61+
private:
62+
HDC hdcWindow;
63+
HDC hdcMemDC;
64+
HBITMAP hbmScreen;
65+
HWND hwnd;
66+
67+
ScreenshotCapture() {
68+
hwnd = MAPLE_WIN;
69+
hdcWindow = GetDC(hwnd);
70+
hdcMemDC = CreateCompatibleDC(hdcWindow);
71+
RECT rect;
72+
GetClientRect(hwnd, &rect);
73+
int width = rect.right - rect.left;
74+
int height = rect.bottom - rect.top;
75+
hbmScreen = CreateCompatibleBitmap(hdcWindow, width, height);
76+
SelectObject(hdcMemDC, hbmScreen);
77+
}
78+
79+
ScreenshotCapture(const ScreenshotCapture&) = delete;
80+
ScreenshotCapture& operator=(const ScreenshotCapture&) = delete;
81+
82+
public:
83+
static ScreenshotCapture& Instance() {
84+
static ScreenshotCapture instance;
85+
return instance;
86+
}
87+
88+
89+
~ScreenshotCapture() {
90+
DeleteObject(hbmScreen);
91+
DeleteDC(hdcMemDC);
92+
ReleaseDC(NULL, hdcWindow);
93+
}
94+
95+
cv::Mat capture(std::vector<int>& region) {
96+
RECT rect;
97+
GetClientRect(hwnd, &rect);
98+
int width = rect.right - rect.left;
99+
int height = rect.bottom - rect.top;
100+
101+
PrintWindow(hwnd, hdcMemDC, PW_CLIENTONLY);
102+
103+
BITMAPINFO bmi = { 0 };
104+
bmi.bmiHeader.biSize = sizeof(BITMAPINFOHEADER);
105+
bmi.bmiHeader.biWidth = width;
106+
bmi.bmiHeader.biHeight = -height;
107+
bmi.bmiHeader.biPlanes = 1;
108+
bmi.bmiHeader.biBitCount = 32;
109+
bmi.bmiHeader.biCompression = BI_RGB;
110+
111+
cv::Mat mat(height, width, CV_8UC4);
112+
GetDIBits(hdcMemDC, hbmScreen, 0, height, mat.data, &bmi, DIB_RGB_COLORS);
113+
114+
if (region.size() != 0) {
115+
int left = region[0];
116+
int top = region[1];
117+
int right = region[2];
118+
int bottom = region[3];
119+
if (left < 0 || top < 0 || right > width || bottom > height) {
120+
std::cout << "Region out of bounds" << std::endl;
121+
return cv::Mat();
122+
}
123+
cv::Rect roi(left, top, right - left, bottom - top);
124+
cv::Mat croppedMat = mat(roi);
125+
cv::Mat resultMat;
126+
cv::cvtColor(croppedMat, resultMat, cv::COLOR_BGRA2BGR);
127+
return resultMat;
128+
}
129+
130+
cv::Mat resultMat;
131+
cv::cvtColor(mat, resultMat, cv::COLOR_BGRA2BGR);
132+
return resultMat;
133+
}
134+
};
135+
136+
137+
static std::vector<cv::Mat> blueCubePotential() {
138+
int left = 600 + 2;
139+
int right = 760;
140+
int top = 423;
141+
int bottom = 466;
142+
std::vector<int> region {left, top, right, bottom};
143+
ScreenshotCapture& capture = ScreenshotCapture::Instance();
144+
cv::Mat potential = capture.capture(region);
145+
146+
int interval = (bottom - top) / 3;
147+
cv::Mat line1 = potential(cv::Rect(0, 0, potential.cols, interval - 3)).clone();
148+
cv::Mat line2 = potential(cv::Rect(0, interval, potential.cols, interval - 1)).clone();
149+
cv::Mat line3 = potential(cv::Rect(0, interval * 2, potential.cols, potential.rows - interval * 2)).clone();
150+
151+
return std::vector<cv::Mat>{line1, line2, line3};
152+
}
153+
154+
static std::vector<cv::Mat> redCubePotential() {
155+
int left = 600 + 2;
156+
int right = 760;
157+
int top = 478 + 3;
158+
int bottom = 521;
159+
std::vector<int> region {left, top, right, bottom};
160+
ScreenshotCapture& capture = ScreenshotCapture::Instance();
161+
cv::Mat potential = capture.capture(region);
162+
163+
164+
int interval = (bottom - top) / 3;
165+
cv::Mat line1 = potential(cv::Rect(0, 0, potential.cols, interval)).clone();
166+
cv::Mat line2 = potential(cv::Rect(0, interval, potential.cols, interval)).clone();
167+
cv::Mat line3 = potential(cv::Rect(0, interval * 2, potential.cols, potential.rows - interval * 2)).clone();
168+
169+
return std::vector<cv::Mat>{line1, line2, line3};
170+
}
171+
172+
173+
174+
std::unordered_map<std::string, std::pair<std::string, int>> imageHub = {
175+
{"boss", {"potential/boss.png", 30}},
176+
{"crtdmg", {"potential/criticaldmg.png", 70}},
177+
{"ignore", {"potential/ignore.png", 50}},
178+
{"cd", {"potential/cd.png", 70}},
179+
{"att", {"potential/att.png", 30}},
180+
{"matt", {"potential/matt.png", 30}},
181+
{"dex", {"potential/dex.png", 30}},
182+
{"str", {"potential/str.png", 30}},
183+
{"int", {"potential/int.png", 30}},
184+
{"luk", {"potential/luk.png", 30}},
185+
{"all", {"potential/all.png", 50}},
186+
{"hp", {"potential/hp.png", 50}},
187+
{"drop", {"potential/drop.png", 50}},
188+
{"meso", {"potential/meso.png", 50}},
189+
{"dmg", {"potential/dmg.png", 50}}
190+
};
191+
192+
std::unordered_map<std::string, cv::Mat> LoadedHub;
193+
194+
static void load_hub() {
195+
for (const auto& pair : imageHub) {
196+
const std::string& key = pair.first;
197+
const std::string& path = pair.second.first;
198+
int width = pair.second.second;
199+
200+
cv::Mat im = cv::imread(path, cv::IMREAD_UNCHANGED);
201+
if (im.empty()) {
202+
std::cerr << "Error loading image: " << path << std::endl;
203+
continue;
204+
}
205+
206+
LoadedHub[key] = im(cv::Rect(0, 0, width, im.rows)); // Crop the image to the specified width
207+
}
208+
}
209+
210+
static bool img_match(const cv::Mat& templateImg, const cv::Mat& im) {
211+
cv::Mat result;
212+
cv::matchTemplate(im, templateImg, result, cv::TM_CCOEFF_NORMED);
213+
214+
double minVal, maxVal;
215+
cv::Point minLoc, maxLoc;
216+
cv::minMaxLoc(result, &minVal, &maxVal, &minLoc, &maxLoc);
217+
218+
maxVal = round(maxVal * 100.0) / 100.0; // Round to 2 decimal places
219+
double threshold = 0.93;
220+
if (maxVal >= threshold && (maxLoc.x + maxLoc.y) < 5) {
221+
return true;
222+
}
223+
224+
return false;
225+
}
226+
227+
static std::string img2text(const cv::Mat& im) {
228+
for (const auto& pair : LoadedHub) {
229+
const std::string& k = pair.first;
230+
const cv::Mat& templateImg = pair.second;
231+
232+
if (img_match(templateImg, im)) {
233+
return k;
234+
}
235+
}
236+
return "unknown";
237+
}
238+
239+
240+
static void rollRedCude() {
241+
leftclick(697, 604);
242+
pressEnter();
243+
pressEnter();
244+
pressEnter();
245+
pressEnter();
246+
msleep(2000);
247+
}
248+
249+
static void rollBlueCude() {
250+
leftclick(676, 539);
251+
pressEnter();
252+
pressEnter();
253+
pressEnter();
254+
msleep(2000);
255+
}
256+
257+
258+
static void match(int times, const std::vector<Rule>& groups, bool is_red = false) {
259+
auto potential = is_red ? redCubePotential : blueCubePotential;
260+
auto reroll = is_red ? rollRedCude : rollBlueCude;
261+
262+
for (int i = 0; i < times; ++i) {
263+
// got new lines
264+
std::vector<cv::Mat> lines = potential();
265+
std::vector<std::string> texts;
266+
267+
// Convert images to texts
268+
for (const auto& line : lines) {
269+
texts.push_back(img2text(line));
270+
}
271+
272+
printf("[%d/%d]: %s, %s, %s]\n", i+1, times, texts[0].c_str(), texts[1].c_str(), texts[2].c_str());
273+
274+
// Filter out "unknown" texts
275+
texts.erase(std::remove(texts.begin(), texts.end(), "unknown"), texts.end());
276+
277+
// Iterate through each group
278+
for (const auto& group : groups) {
279+
int hit = group.hit;
280+
const std::vector<std::string>& keys = group.key;
281+
int count = 0;
282+
283+
for (const std::string& text : texts) {
284+
if (std::find(keys.begin(), keys.end(), text) != keys.end()) {
285+
count++;
286+
}
287+
}
288+
289+
if (count >= hit) {
290+
std::cout << "match! ";
291+
for (const auto& key : keys) {
292+
std::cout << key << " ";
293+
}
294+
std::cout << std::endl;
295+
return;
296+
}
297+
}
298+
299+
// Call reset function
300+
reroll();
301+
}
302+
}
303+
304+
void helpinfo() {
305+
printf("Usage: \n");
306+
printf("coffeestory [red/blue] [times] [target] \n");
307+
}
308+
309+
310+
int main(int argc, char* argv[]) {
311+
if (argc == 1) {
312+
helpinfo();
313+
return 0;
314+
}
315+
reset_maple();
316+
// Load the image hub
317+
load_hub();
318+
319+
std::string kind = argv[1];
320+
bool is_red = false;
321+
if (kind == "red") {
322+
is_red = true;
323+
}
324+
else if (kind == "blue") {
325+
is_red = false;
326+
}
327+
else {
328+
helpinfo();
329+
return 0;
330+
}
331+
int times = std::stoi(argv[2]);
332+
std::string target = argv[3];
333+
if (auto p = Rules.find(target); p != Rules.end()) {
334+
match(times, p->second, is_red);
335+
}
336+
else {
337+
std::cout << "target not found! " << target << std::endl;
338+
}
339+
340+
return 0;
341+
}
342+

0 commit comments

Comments
 (0)