1
+ package eu .darkbot .popcorn .def ;
2
+
3
+ import com .github .manolo8 .darkbot .Main ;
4
+ import com .github .manolo8 .darkbot .backpage .FlashResManager ;
5
+ import com .github .manolo8 .darkbot .core .entities .Box ;
6
+ import com .github .manolo8 .darkbot .core .itf .Behaviour ;
7
+ import com .github .manolo8 .darkbot .core .manager .HeroManager ;
8
+ import com .github .manolo8 .darkbot .core .utils .Drive ;
9
+ import com .github .manolo8 .darkbot .extensions .features .Feature ;
10
+ import com .github .manolo8 .darkbot .modules .TemporalModule ;
11
+
12
+ import java .util .ArrayList ;
13
+ import java .util .Arrays ;
14
+ import java .util .Comparator ;
15
+ import java .util .List ;
16
+ import java .util .Set ;
17
+ import java .util .function .Consumer ;
18
+ import java .util .regex .Matcher ;
19
+ import java .util .regex .Pattern ;
20
+ import java .util .stream .Collectors ;
21
+ import java .util .stream .Stream ;
22
+
23
+ @ Feature (name = "Captcha picker" , description = "Picks up captcha boxes when they appear" , enabledByDefault = true )
24
+ public class CaptchaPicker extends TemporalModule implements Behaviour {
25
+
26
+ private static final Pattern SPECIAL_REGEX = Pattern .compile ("[{}()\\ [\\ ].+*?^$\\ \\ |]" );
27
+
28
+ private static final Set <String > ALL_CAPTCHA_TYPES =
29
+ Arrays .stream (Captcha .values ()).map (c -> c .box ).collect (Collectors .toSet ());
30
+
31
+ private Main main ;
32
+ private HeroManager hero ;
33
+ private Drive drive ;
34
+
35
+ private FlashResManager flashResManager ;
36
+ private final Consumer <String > logConsumer = this ::onLogReceived ;
37
+ private final List <String > pastLogMessages = new ArrayList <>();
38
+
39
+ private Captcha captchaType ;
40
+ private List <Box > boxes , toCollect ;
41
+ private long waiting ;
42
+
43
+ @ Override
44
+ public void install (Main main ) {
45
+ if (!Arrays .equals (VerifierChecker .class .getSigners (), getClass ().getSigners ())) return ;
46
+ VerifierChecker .checkAuthenticity ();
47
+ super .install (main );
48
+
49
+ this .main = main ;
50
+ this .hero = main .hero ;
51
+ this .drive = main .hero .drive ;
52
+ this .flashResManager = main .featureRegistry .getFeature (FlashResManager .class )
53
+ .orElseThrow (IllegalStateException ::new );
54
+ this .boxes = main .mapManager .entities .boxes ;
55
+
56
+ this .toCollect = null ;
57
+
58
+ main .facadeManager .log .logs .add (logConsumer );
59
+ }
60
+
61
+ @ Override
62
+ public void uninstall () {
63
+ main .facadeManager .log .logs .remove2 (logConsumer );
64
+ }
65
+
66
+ private void onLogReceived (String log ) {
67
+ // Previous to flash resource manager initialization, translations may be null, if so, store messages.
68
+ if (flashResManager .getTranslation (Captcha .SOME_RED .key ) == null ) {
69
+ pastLogMessages .add (log );
70
+ return ;
71
+ }
72
+
73
+ for (Captcha captcha : Captcha .values ()) {
74
+ if (captcha .matches (log , flashResManager )) setCurrentCaptcha (captcha );
75
+ }
76
+ }
77
+
78
+ @ Override
79
+ public boolean canRefresh () {
80
+ return false ;
81
+ }
82
+
83
+ @ Override
84
+ public String status () {
85
+ return "Solving captcha: Collecting " + (captchaType == null ? "(waiting for log...)" :
86
+ (captchaType .hasAmount ? captchaType .amount : "all" ) + " " + captchaType .box + " box(es)" );
87
+ }
88
+
89
+ @ Override
90
+ public void tickBehaviour () {
91
+ // Translations finally loaded, process past message
92
+ if (!pastLogMessages .isEmpty () && flashResManager .getTranslation (Captcha .SOME_RED .key ) != null ) {
93
+ pastLogMessages .forEach (this ::onLogReceived );
94
+ pastLogMessages .clear ();
95
+ }
96
+
97
+ // Set module to work if there's any
98
+ if (main .module != this && hasAnyCaptchaBox ()) main .setModule (this );
99
+ }
100
+
101
+ @ Override
102
+ public void tick () {
103
+ if (isWaiting ()) return ;
104
+ if (!hasAnyCaptchaBox ()) goBack ();
105
+
106
+ drive .stop (false );
107
+
108
+ if (toCollect == null ) {
109
+ if (captchaType == null ) return ;
110
+
111
+ Stream <Box > boxStream = boxes .stream ()
112
+ .filter (captchaType ::matches )
113
+ .sorted (Comparator .comparingDouble (box -> hero .locationInfo .now .distance (box )));
114
+ if (captchaType .hasAmount ) boxStream = boxStream .limit (captchaType .amount );
115
+
116
+ toCollect = boxStream .collect (Collectors .toList ());
117
+ }
118
+
119
+ toCollect .stream ().filter (b -> !b .isCollected ())
120
+ .findFirst ().ifPresent (this ::collectBox );
121
+ }
122
+
123
+ private boolean hasAnyCaptchaBox () {
124
+ return boxes .stream ().map (b -> b .type )
125
+ .anyMatch (ALL_CAPTCHA_TYPES ::contains );
126
+ }
127
+
128
+ private void collectBox (Box box ) {
129
+ box .clickable .setRadius (800 );
130
+ drive .clickCenter (true , box .locationInfo .now );
131
+
132
+ box .setCollected ();
133
+ waiting = System .currentTimeMillis ()
134
+ + Math .min (1_000 , box .getRetries () * 100 ) // Add 100ms per retry, max 1 second
135
+ + hero .timeTo (hero .locationInfo .distance (box )) + 500 ;
136
+ }
137
+
138
+ public boolean isWaiting () {
139
+ return System .currentTimeMillis () < waiting ;
140
+ }
141
+
142
+ private void setCurrentCaptcha (Captcha captcha ) {
143
+ waiting = System .currentTimeMillis () + 500 ; // Wait for everything to be ready
144
+ captchaType = captcha ;
145
+ toCollect = null ;
146
+ }
147
+
148
+ @ Override
149
+ protected void goBack () {
150
+ this .toCollect = null ; // Make sure we let them GC
151
+
152
+ super .goBack ();
153
+ }
154
+
155
+ private enum Captcha {
156
+ SOME_BLACK ("POISON_PUSAT_BOX_BLACK" , "captcha_choose_some_black" ),
157
+ ALL_BLACK ("POISON_PUSAT_BOX_BLACK" , "captcha_choose_all_black" ),
158
+ SOME_RED ("BONUS_BOX_RED" , "captcha_choose_some_red" ),
159
+ ALL_RED ("BONUS_BOX_RED" , "captcha_choose_all_red" );
160
+
161
+ private final String box , key ;
162
+ private Pattern pattern ;
163
+ private boolean hasAmount ;
164
+ private int amount , time ;
165
+
166
+ Captcha (String name , String key ) {
167
+ this .box = name ;
168
+ this .key = key ;
169
+ }
170
+
171
+ public boolean matches (String log , FlashResManager resManager ) {
172
+ if (pattern == null ) {
173
+ if (resManager == null ) return false ;
174
+ String translation = resManager .getTranslation (key );
175
+ if (translation == null || translation .isEmpty ()) return false ;
176
+
177
+ this .hasAmount = translation .contains ("%AMOUNT%" );
178
+
179
+ pattern = Pattern .compile (escapeRegex (translation )
180
+ .replace ("%AMOUNT%" , "(?<amount>[0-9]+)" )
181
+ .replace ("%TIME%" , "(?<time>[0-9]+)" ));
182
+ }
183
+
184
+ Matcher m = pattern .matcher (log );
185
+ boolean matched = m .matches ();
186
+ if (hasAmount && matched ) {
187
+ amount = Integer .parseInt (m .group ("amount" ));
188
+ time = Integer .parseInt (m .group ("time" ));
189
+ }
190
+ return matched ;
191
+ }
192
+
193
+ public boolean matches (Box box ) {
194
+ return this .box .equals (box .type );
195
+ }
196
+
197
+ @ Override
198
+ public String toString () {
199
+ return name () + (hasAmount ? " " + amount : "" );
200
+ }
201
+ }
202
+
203
+ private static String escapeRegex (String str ) {
204
+ return SPECIAL_REGEX .matcher (str ).replaceAll ("\\ \\ $0" );
205
+ }
206
+
207
+ }
0 commit comments