Skip to content

Commit 1ceeaae

Browse files
committed
Add Recipe Purchaser
Add recipe purchaser to Pokopia Cloud Island Reset
1 parent 2f6d9fd commit 1ceeaae

7 files changed

Lines changed: 568 additions & 9 deletions

SerialPrograms/Source/PokemonPokopia/Inference/PokemonPokopia_PCDetection.cpp

Lines changed: 217 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,8 +8,12 @@
88
#include "Common/Cpp/Exceptions.h"
99
#include "Kernels/Waterfill/Kernels_Waterfill_Types.h"
1010
#include "CommonFramework/GlobalSettingsPanel.h"
11+
#include "CommonFramework/Tools/GlobalThreadPools.h"
1112
#include "CommonTools/ImageMatch/WaterfillTemplateMatcher.h"
1213
#include "CommonTools/Images/WaterfillUtilities.h"
14+
#include "CommonTools/Images/SolidColorTest.h"
15+
#include "CommonTools/OCR/OCR_NumberReader.h"
16+
#include "CommonTools/OCR/OCR_RawOCR.h"
1317
#include "PokemonPokopia_PCDetection.h"
1418

1519
namespace PokemonAutomation{
@@ -38,8 +42,6 @@ class InfoIconMatcher : public ImageMatch::WaterfillTemplateMatcher{
3842
}
3943
};
4044

41-
42-
4345
InfoIconDetector::InfoIconDetector(
4446
Color color,
4547
VideoOverlay* overlay,
@@ -209,6 +211,219 @@ bool StampDetector::detect(const ImageViewRGB32& screen){
209211
return found;
210212
}
211213

214+
class RecipeIconMatcher : public ImageMatch::WaterfillTemplateMatcher{
215+
public:
216+
// Match the yellow negative space for the recipe icon
217+
RecipeIconMatcher(const char* path)
218+
: WaterfillTemplateMatcher(
219+
path,
220+
Color(210, 180, 0), Color(255, 250, 160), 2100
221+
)
222+
{
223+
m_aspect_ratio_lower = 0.9;
224+
m_aspect_ratio_upper = 1.1;
225+
m_area_ratio_lower = 0.85;
226+
m_area_ratio_upper = 1.1;
227+
}
228+
229+
static const RecipeIconMatcher& single_matcher(){
230+
static RecipeIconMatcher matcher("PokemonPokopia/Recipe.png");
231+
return matcher;
232+
}
233+
static const RecipeIconMatcher& double_matcher(){
234+
static RecipeIconMatcher matcher("PokemonPokopia/DoubleRecipe.png");
235+
return matcher;
236+
}
237+
static const RecipeIconMatcher& triple_matcher(){
238+
static RecipeIconMatcher matcher("PokemonPokopia/TripleRecipe.png");
239+
return matcher;
240+
}
241+
static const RecipeIconMatcher& quad_matcher(){
242+
static RecipeIconMatcher matcher("PokemonPokopia/QuadRecipe.png");
243+
return matcher;
244+
}
245+
};
246+
247+
RecipeIconDetector::RecipeIconDetector(
248+
Color color,
249+
VideoOverlay* overlay,
250+
const ImageFloatBox& box
251+
)
252+
: m_color(color)
253+
, m_overlay(overlay)
254+
, m_arrow_box(box)
255+
{}
256+
void RecipeIconDetector::make_overlays(VideoOverlaySet& items) const{
257+
items.add(m_color, m_arrow_box);
258+
}
259+
bool RecipeIconDetector::detect(const ImageViewRGB32& screen){
260+
double screen_rel_size = (screen.height() / 1080.0);
261+
double screen_rel_size_2 = screen_rel_size * screen_rel_size;
262+
263+
double min_area_1080p = 500;
264+
double rmsd_threshold = 95;
265+
size_t min_area = size_t(screen_rel_size_2 * min_area_1080p);
266+
267+
const std::vector<std::pair<uint32_t, uint32_t>> FILTERS = {
268+
{0xffd2b400, 0xfffffaa0} // RGB(210, 180, 0), RGB(255, 250, 160)
269+
};
270+
271+
const std::vector<std::pair<const RecipeIconMatcher&, RecipeType>> matchers = {
272+
{RecipeIconMatcher::single_matcher(), RecipeType::SINGLE},
273+
{RecipeIconMatcher::double_matcher(), RecipeType::DOUBLE},
274+
{RecipeIconMatcher::triple_matcher(), RecipeType::TRIPLE},
275+
{RecipeIconMatcher::quad_matcher(), RecipeType::QUAD},
276+
};
277+
278+
bool found = false;
279+
m_recipe_type = RecipeType::NOT_RECIPE;
280+
281+
for (const auto& matcher_info : matchers) {
282+
found = match_template_by_waterfill(
283+
screen.size(),
284+
extract_box_reference(screen, m_arrow_box),
285+
matcher_info.first,
286+
FILTERS,
287+
{min_area, SIZE_MAX},
288+
rmsd_threshold,
289+
[&](Kernels::Waterfill::WaterfillObject& object) -> bool {
290+
m_last_detected = translate_to_parent(screen, m_arrow_box, object);
291+
return true;
292+
}
293+
);
294+
if (found) {
295+
m_recipe_type = matcher_info.second;
296+
break;
297+
}
298+
}
299+
300+
if (m_overlay){
301+
if (found){
302+
m_last_detected_box.emplace(*m_overlay, m_last_detected, COLOR_GREEN);
303+
}else{
304+
m_last_detected_box.reset();
305+
}
306+
}
307+
308+
return found;
309+
}
310+
311+
CoinCountDetector::CoinCountDetector(Logger& logger)
312+
: m_logger(logger)
313+
// location in the shop menu
314+
, m_coin_count_box{0.900000, 0.053000, 0.075000, 0.035000}
315+
{}
316+
317+
void CoinCountDetector::make_overlays(VideoOverlaySet& items) const{
318+
items.add(COLOR_WHITE, m_coin_count_box);
319+
}
320+
321+
bool CoinCountDetector::detect(const ImageViewRGB32& screen){
322+
const ImageViewRGB32 coin_image_crop = extract_box_reference(screen, m_coin_count_box);
323+
324+
const bool text_inside_range = true;
325+
const bool prioritize_numeric_only_results = true;
326+
const size_t width_max = SIZE_MAX;
327+
// The coin crop includes the "," in coin numbers like "1,000".
328+
// We have to use `min_digit_area` to filter out "," when doing OCR.
329+
// The min digit area computation is that any dot with size smaller than coin_image_crop.height()/5 is filtered out when OCR.
330+
const size_t min_digit_area = coin_image_crop.height()*coin_image_crop.height() / 25;
331+
m_coin_count = 0;
332+
const std::vector<std::pair<uint32_t, uint32_t>> filters = {
333+
{0xff808080, 0xffffffff},
334+
{0xffa0a0a0, 0xffffffff},
335+
{0xffc0c0c0, 0xffffffff},
336+
{0xffe0e0e0, 0xffffffff},
337+
{0xfff0f0f0, 0xffffffff},
338+
};
339+
int number = OCR::read_number_waterfill_multifilter(
340+
m_logger,
341+
GlobalThreadPools::computation_realtime(),
342+
coin_image_crop, filters,
343+
text_inside_range, prioritize_numeric_only_results, width_max, min_digit_area
344+
);
345+
if (number <= 0 || number > 999999){
346+
return false;
347+
}
348+
m_coin_count = static_cast<uint32_t>(number);
349+
return true;
350+
}
351+
352+
CoinCountWatcher::CoinCountWatcher(Logger& logger)
353+
: CoinCountDetector(logger), VisualInferenceCallback("CoinCountWatcher")
354+
{}
355+
356+
void CoinCountWatcher::make_overlays(VideoOverlaySet& items) const{
357+
CoinCountDetector::make_overlays(items);
358+
}
359+
360+
bool CoinCountWatcher::process_frame(const ImageViewRGB32& frame, WallClock timestamp){
361+
return detect(frame);
362+
}
363+
364+
class CoinIconMatcher : public ImageMatch::WaterfillTemplateMatcher{
365+
public:
366+
CoinIconMatcher()
367+
: WaterfillTemplateMatcher(
368+
"PokemonPokopia/Coin.png",
369+
Color(210, 150, 30), Color(255, 240, 140), 900
370+
)
371+
{
372+
m_aspect_ratio_lower = 0.9;
373+
m_aspect_ratio_upper = 1.1;
374+
m_area_ratio_lower = 0.85;
375+
m_area_ratio_upper = 1.1;
376+
}
377+
};
378+
379+
CoinIconDetector::CoinIconDetector(
380+
Color color,
381+
VideoOverlay* overlay,
382+
const ImageFloatBox& box
383+
)
384+
: m_color(color)
385+
, m_overlay(overlay)
386+
, m_arrow_box(box)
387+
{}
388+
void CoinIconDetector::make_overlays(VideoOverlaySet& items) const{
389+
items.add(m_color, m_arrow_box);
390+
}
391+
bool CoinIconDetector::detect(const ImageViewRGB32& screen){
392+
double screen_rel_size = (screen.height() / 1080.0);
393+
double screen_rel_size_2 = screen_rel_size * screen_rel_size;
394+
395+
double min_area_1080p = 200;
396+
double rmsd_threshold = 100;
397+
size_t min_area = size_t(screen_rel_size_2 * min_area_1080p);
398+
399+
const std::vector<std::pair<uint32_t, uint32_t>> FILTERS = {
400+
{0xffd2961e, 0xfffff08c} // RGB(210, 150, 30), RGB(255, 240, 140)
401+
};
402+
403+
bool found = match_template_by_waterfill(
404+
screen.size(),
405+
extract_box_reference(screen, m_arrow_box),
406+
CoinIconMatcher(),
407+
FILTERS,
408+
{min_area, SIZE_MAX},
409+
rmsd_threshold,
410+
[&](Kernels::Waterfill::WaterfillObject& object) -> bool {
411+
m_last_detected = translate_to_parent(screen, m_arrow_box, object);
412+
return true;
413+
}
414+
);
415+
416+
if (m_overlay){
417+
if (found){
418+
m_last_detected_box.emplace(*m_overlay, m_last_detected, COLOR_GREEN);
419+
}else{
420+
m_last_detected_box.reset();
421+
}
422+
}
423+
424+
return found;
425+
}
426+
212427

213428

214429
}

SerialPrograms/Source/PokemonPokopia/Inference/PokemonPokopia_PCDetection.h

Lines changed: 111 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -107,6 +107,117 @@ class StampWatcher : public DetectorToFinder<StampDetector>{
107107
{}
108108
};
109109

110+
enum class RecipeType{
111+
SINGLE,
112+
DOUBLE,
113+
TRIPLE,
114+
QUAD,
115+
NOT_RECIPE
116+
};
117+
118+
class RecipeIconDetector : public StaticScreenDetector{
119+
public:
120+
RecipeIconDetector(
121+
Color color,
122+
VideoOverlay* overlay,
123+
const ImageFloatBox& box
124+
);
125+
126+
const ImageFloatBox& last_detected() const { return m_last_detected; }
127+
const RecipeType& recipe_type() const { return m_recipe_type; }
128+
129+
virtual void make_overlays(VideoOverlaySet& items) const override;
130+
131+
// This is not const so that detectors can save/cache state.
132+
virtual bool detect(const ImageViewRGB32& screen) override;
133+
134+
private:
135+
friend class RecipeWatcher;
136+
137+
const Color m_color;
138+
VideoOverlay* m_overlay;
139+
const ImageFloatBox m_arrow_box;
140+
141+
RecipeType m_recipe_type;
142+
ImageFloatBox m_last_detected;
143+
std::optional<OverlayBoxScope> m_last_detected_box;
144+
};
145+
class RecipeIconWatcher : public DetectorToFinder<RecipeIconDetector>{
146+
public:
147+
RecipeIconWatcher(
148+
Color color,
149+
VideoOverlay* overlay,
150+
const ImageFloatBox& box,
151+
std::chrono::milliseconds hold_duration = std::chrono::milliseconds(250)
152+
)
153+
: DetectorToFinder("RecipeIconWatcher", hold_duration, color, overlay, box)
154+
{}
155+
};
156+
157+
class CoinCountDetector : public StaticScreenDetector{
158+
public:
159+
CoinCountDetector(Logger& logger);
160+
161+
virtual void make_overlays(VideoOverlaySet& items) const override;
162+
163+
// This is not const so that detectors can save/cache state.
164+
virtual bool detect(const ImageViewRGB32& screen) override;
165+
166+
uint32_t coin_count() const { return m_coin_count; }
167+
168+
private:
169+
Logger& m_logger;
170+
ImageFloatBox m_coin_count_box;
171+
172+
uint32_t m_coin_count = 0;
173+
};
174+
175+
class CoinCountWatcher : public CoinCountDetector, public VisualInferenceCallback{
176+
public:
177+
CoinCountWatcher(Logger& logger);
178+
179+
virtual void make_overlays(VideoOverlaySet& items) const override;
180+
181+
virtual bool process_frame(const ImageViewRGB32& frame, WallClock timestamp) override;
182+
};
183+
184+
class CoinIconDetector : public StaticScreenDetector{
185+
public:
186+
CoinIconDetector(
187+
Color color,
188+
VideoOverlay* overlay,
189+
const ImageFloatBox& box
190+
);
191+
192+
const ImageFloatBox& last_detected() const { return m_last_detected; }
193+
194+
virtual void make_overlays(VideoOverlaySet& items) const override;
195+
196+
// This is not const so that detectors can save/cache state.
197+
virtual bool detect(const ImageViewRGB32& screen) override;
198+
199+
private:
200+
friend class CoinIconWatcher;
201+
202+
const Color m_color;
203+
VideoOverlay* m_overlay;
204+
const ImageFloatBox m_arrow_box;
205+
206+
ImageFloatBox m_last_detected;
207+
std::optional<OverlayBoxScope> m_last_detected_box;
208+
};
209+
class CoinIconWatcher : public DetectorToFinder<CoinIconDetector>{
210+
public:
211+
CoinIconWatcher(
212+
Color color,
213+
VideoOverlay* overlay,
214+
const ImageFloatBox& box,
215+
std::chrono::milliseconds hold_duration = std::chrono::milliseconds(250)
216+
)
217+
: DetectorToFinder("CoinIconWatcher", hold_duration, color, overlay, box)
218+
{}
219+
};
220+
110221

111222

112223
}

SerialPrograms/Source/PokemonPokopia/Inference/PokemonPokopia_SelectionArrowDetector.cpp

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -20,8 +20,8 @@ class SelectionArrowMatcher : public ImageMatch::WaterfillTemplateMatcher{
2020
public:
2121
SelectionArrowMatcher(const char* path)
2222
: WaterfillTemplateMatcher(
23-
path,
24-
Color(230, 100, 30), Color(255, 190, 190),
23+
path,
24+
Color(240, 170, 0), Color(255, 255, 200),
2525
100
2626
)
2727
{
@@ -76,8 +76,7 @@ bool SelectionArrowDetector::detect(const ImageViewRGB32& screen){
7676
size_t min_area = size_t(screen_rel_size_2 * min_area_1080p);
7777

7878
const std::vector<std::pair<uint32_t, uint32_t>> FILTERS = {
79-
{0xffe6641e, 0xffffbebe}, // RGB(230, 100, 30), RGB(255, 190, 190)
80-
{0xffe65a46, 0xffff9696} // RGB(230, 90, 70), RGB(255, 150, 150)
79+
{0xfff0aa00, 0xffffffc8} // RGB(240, 170, 0), RGB(255, 255, 200)
8180
};
8281

8382
bool found = match_template_by_waterfill(

0 commit comments

Comments
 (0)