1 // This addon plays HyperRogue by itself.
2 // Well, not really -- it performs illegal moves, and it gets tons of treasure, orbs, and kills out of nowhere.
3 // Useful for debugging.
4
5 #include "../hyper.h"
6
7 namespace hr {
8
9 bool doAutoplay;
10 eLand autoplayLand;
11
12 namespace prairie { extern cell *enter; }
13
sameland(eLand ll,eLand ln)14 bool sameland(eLand ll, eLand ln) {
15 if(ln == laBarrier || ln == laOceanWall)
16 return true;
17 if(ln == ll) return true;
18 if(isElemental(ln) && isElemental(ll)) return true;
19 if(isHaunted(ln) && isHaunted(ll)) return true;
20 return false;
21 }
22
randomCheat()23 void randomCheat() {
24 int croll = hrand(50);
25 if (croll < 25) {
26 eItem b = (eItem) hrand(ittypes);
27 printf("Gain item: %s\n", iinf[b].name);
28 items[b] = (1 << hrand(11)) - 1;
29 items[itOrbYendor] &= 15;
30 reduceOrbPowers(); // in particlar, cancel out slaying+weakness, since the combination may confuse shadow
31 } else if (croll == 25) {
32 printf("Gain kills\n");
33 kills[hrand(motypes)] = (1 << hrand(11)) - 1;
34 } else if (croll == 26) {
35 printf("Princess soon\n");
36 princess::saved = true;
37 princess::everSaved = true;
38 items[itSavedPrincess]++;
39 items[itOrbLove] = 1;
40 items[itOrbTime] = 0;
41 } else if (croll == 27) {
42 printf("Gain allies\n");
43 forCellEx(cz, cwt.at)
44 if (!cz->monst)
45 cz->monst = pick(moMouse, moFriendlyGhost, moGolem, moTameBomberbird, moKnight);
46 } else if (croll == 28) {
47 printf("Place orbs with pickup effects\n");
48 forCellEx(cz, cwt.at)
49 if (!cz->item)
50 cz->item = pick(itOrbLife, itOrbFriend, itOrbSpeed, itOrbShield, itOrbChaos, itOrbPurity);
51 } else if (croll == 29) {
52 printf("Place fun walls\n");
53 forCellEx(cz, cwt.at)
54 if (!cz->wall && !cz->monst)
55 cz->wall = pick(waExplosiveBarrel, waBigStatue, waThumperOff, waBonfireOff, waCloud, waMirror);
56 } else if (croll == 30) {
57 cell *ct = dcal[hrand(isize(dcal))];
58 if (!isPlayerOn(ct) && !ct->monst && !ct->wall) {
59 eWall hazard = pick(waRose, waFireTrap, waMineMine, waTrapdoor, waChasm, waCavewall);
60 printf("Spam a hazard: %s\n", winf[hazard].name);
61 ct->wall = hazard;
62 }
63 } else if (croll == 31 && !memory_saving_mode) {
64 //printf("Saving memory\n");
65 //memory_saving_mode = true;
66 //save_memory();
67 //memory_saving_mode = false;
68 } else if (croll == 33) {
69 cell *ct = dcal[hrand(isize(dcal))];
70 if (!isPlayerOn(ct) && !ct->monst && !ct->wall) {
71 printf("Spam some slime\n");
72 ct->item = itNone;
73 ct->wall = hrand(2) ? waFloorA : waFloorB;
74 switch(hrand(4)) {
75 case 0: ct->monst = moSlime; break;
76 case 1: ct->item = itGreenStone; break;
77 default: ;
78 }
79 }
80 } else if (croll == 37) {
81 cell *ct = dcal[hrand(isize(dcal))];
82 if (!isPlayerOn(ct) && !ct->monst && !ct->wall) {
83 ct->monst = pick(moRagingBull, moTroll, moAcidBird, moMiner, moReptile, moVineBeast, moBug0, moBug1);
84 printf("Spam a monster: %s\n", minf[ct->monst].name);
85 }
86 // todo: dice
87 } else if (croll == 38) {
88 forCellEx(cz, cwt.at) {
89 if (cz->monst == moPrincessArmed) {
90 printf("Disarming a princess\n");
91 cz->monst = moPrincess;
92 }
93 }
94 } else if (croll == 39) {
95 //forCellEx(cz, cwt.at) {
96 // if (!cz->monst) {
97 // printf("Summoning an unarmed princess incorrectly\n");
98 // cz->monst = moPrincess;
99 // break;
100 // }
101 //}
102 } else if (croll == 40) {
103 //forCellEx(cz, cwt.at) {
104 // if (!cz->monst) {
105 // printf("Summoning an armed princess incorrectly\n");
106 // cz->monst = moPrincessArmed;
107 // break;
108 // }
109 //}
110 } else if (croll == 41) {
111 cell *ct = dcal[hrand(isize(dcal))];
112 if (among(ct->wall, waNone, waVinePlant, waFloorA, waFloorB, waTrapdoor, waChasm, waBigStatue)) {
113 // Set wparam on a cell where it shouldn't matter, so that if this wall is later converted
114 // to a walltype that does care by some code that assumes wparam was 0,
115 // we can find out if that causes bugs.
116 printf("Randomizing wparam on %s at %p\n", winf[ct->wall].name, (void *)ct);
117 ct->wparam = (unsigned char) hrand(256);
118 }
119 } else if (croll == 42) {
120 vid.wallmode = hrand(7);
121 printf("Set vid.wallmode to %d: %s\n", vid.wallmode, wdmodes[vid.wallmode]);
122 } else if (croll == 43) {
123 vid.monmode = hrand(4);
124 printf("Set vid.monmode to %d: %s\n", vid.monmode, mdmodes[vid.monmode]);
125 }
126 }
127
cellToTarget()128 cell *cellToTarget() {
129 if (isCrossroads(cwt.at->land)) {
130 for (cell *c: dcal) {
131 if (c->land == laCamelot && c->wall == waNone) {
132 printf("Trying to teleport into Camelot\n");
133 items[itOrbTeleport] += 1;
134 return c;
135 }
136 }
137 }
138
139 if(cwt.at->land == laCamelot) {
140 int oldDist = celldistAltRelative(cwt.at);
141 if (oldDist > 3) {
142 for (cell *c: dcal) {
143 if (c->land == laCamelot) {
144 int dist = celldistAltRelative(c);
145 if (-1 <= dist && dist <= 1 && hrand(10) == 0) {
146 printf("Trying to teleport near the round table (%d to %d)\n", oldDist, dist);
147 items[itOrbTeleport] += 1;
148 return c;
149 }
150 }
151 }
152 }
153 }
154
155 return dcal[hrand(isize(dcal))];
156 }
157
randomMove()158 void randomMove()
159 {
160 // don't show warning dialogs
161 items[itWarning] = 1;
162
163 int roll = hrand(50);
164 if (roll == 0) {
165 // drop dead orb
166 bool res = movepcto(MD_DROP, 1);
167 printf("DROP: %d\n", res);
168 } else if (roll < 5) {
169 // skip turn
170 bool res = movepcto(MD_WAIT, 1);
171 printf("WAIT: %d\n", res);
172 } else if (roll < 42) {
173 // move to or attack a neighbor cell
174 int i = hrand(cwt.at->type);
175 cell *c2 = cwt.at->move(i);
176 cwt.spin = 0;
177 int d = neighborId(cwt.at, c2);
178 if (d >= 0) {
179 int subdir = (roll%2==0)?1:-1;
180 string c2info = dnameof(c2->wall) + "; " + dnameof(c2->monst) + "; " + dnameof(c2->item);
181 bool res = movepcto(d, subdir, false);
182 printf("MOVE %d [%s] sub %d: %d\n", d, c2info.c_str(), subdir, res);
183 if (!res && c2->monst) {
184 printf("clearing the monster (%s)\n", minf[c2->monst].name);
185 killMonster(c2, moNone);
186 }
187 } else {
188 printf("MOVE CONFUSED %d\n", d);
189 }
190 } else {
191 // try to use a ranged orb
192 cell *ct = cellToTarget();
193 eItem ti = targetRangedOrb(ct, roMouseForce);
194 const char *tm = (ti == eItem(-1)) ? "orb cannot be used (see message log)" : iinf[ti].name;
195 printf("TARGET %p: %s\n", (void*)ct, tm);
196 }
197 }
198
noteUnusualSituations()199 void noteUnusualSituations()
200 {
201 if(cwt.at->monst && !isMultitile(cwt.at->monst)) {
202 // This is possible in multiple ways
203 printf("on a non-multitile monster: %s\n", minf[cwt.at->monst].name);
204 }
205 else if(isDie(cwt.at->wall)) {
206 // This is possible with aether + teleport
207 printf("on a wall-type die: %s\n", winf[cwt.at->wall].name);
208 }
209 }
210
isAnythingWrong()211 bool isAnythingWrong()
212 {
213 uintptr_t ienter = (uintptr_t) prairie::enter;
214 if(ienter && ienter < 100000) {
215 printf("ERROR: prairie::enter has incorrect value\n");
216 return true;
217 }
218
219 if(buggyGeneration || isize(buggycells)) {
220 println(hlog, "ERROR: buggy generation");
221 return true;
222 }
223
224 if(isIcyLand(cwt.at)) {
225 float heat = HEAT(cwt.at);
226 // Checking for extreme values as well as NaNs and infinities
227 if (!(-1e10 < heat && heat < 1e10)) {
228 printf("ERROR: Heat is out of expected range\n");
229 return true;
230 }
231 }
232
233 if (cwt.at->land == laCamelot) {
234 for(int i=0; i<isize(dcal); i++) {
235 cell *c = dcal[i];
236 if(c->land == laCamelot && celldistAltRelative(c) == 0 && c->wall != waRoundTable) {
237 printf("ERROR: The round table of camelot is interrupted by a cell of %s\n", winf[c->wall].name);
238 return true;
239 }
240 }
241 }
242
243 for(int i=0; i<isize(dcal); i++) {
244 cell *c = dcal[i];
245 (void)isChild(c, NULL);
246 if(childbug) return true;
247 if(c->land == laNone) {
248 printf("ERROR: no-land found\n");
249 return true;
250 }
251 if(c->item == itBuggy || c->item == itBuggy2) {
252 printf("ERROR: buggy item found\n");
253 return true;
254 }
255 if(!euclid && isPrincess(c->monst) && princess::getPrincessInfo(c) == nullptr) {
256 printf("ERROR: missing princess info\n");
257 return true;
258 }
259 if(dice::on(c)) {
260 if(dice::data.count(c) == 0) {
261 c->item = itBuggy;
262 printf("ERROR: missing dice::data[%p]\n", (void *)c);
263 return true;
264 }
265 else if(!dice::data[c].which) {
266 // we might get here instead if someone already tried to do data[c], which creates a new element out of nothing
267 c->item = itBuggy;
268 printf("ERROR: missing dice::data[%p].which\n", (void *)c);
269 return true;
270 }
271 }
272 }
273
274 return false;
275 }
276
stopIfBug()277 void stopIfBug()
278 {
279 if(isAnythingWrong()) {
280 if(noGUI) {
281 exit(1);
282 }
283 else {
284 kills[moPlayer] = 0;
285 canmove = true;
286 doAutoplay = false;
287 }
288 }
289 }
290
showAutoplayStats()291 void showAutoplayStats()
292 {
293 printf("cells travelled: %d\n", celldist(cwt.at));
294
295 printf("\n");
296
297 for(int i=0; i<ittypes; i++) if(items[i])
298 printf("%4dx %s\n", items[i], iinf[i].name);
299
300 printf("\n");
301
302 for(int i=1; i<motypes; i++) if(kills[i])
303 printf("%4dx %s <%d>\n", kills[i], minf[i].name, i);
304
305 printf("\n\n\n");
306 }
307
resetIfNeeded(int * gcount)308 void resetIfNeeded(int *gcount)
309 {
310 if(hrand(5000) == 0 || (isGravityLand(cwt.at->land) && coastvalEdge(cwt.at) >= 100) || *gcount > 2000 || cellcount >= 20000000) {
311 printf("RESET\n");
312 *gcount = 0;
313 cellcount = 0;
314 activateSafety(autoplayLand ? autoplayLand : landlist[hrand(isize(landlist))]);
315 if (cellcount < 0) {
316 //printf("How did cellcount become negative?\n");
317 cellcount = 1;
318 }
319 }
320
321 if(cwt.at->land == laWestWall && cwt.at->landparam >= 30) {
322 printf("Safety generated\n");
323 forCellEx(c2, cwt.at) c2->item = itOrbSafety;
324 }
325 }
326
327 /* auto a3 = addHook(hooks_nextland, 100, [] (eLand l) {
328 eLand l2;
329 do { l2 = pick(laRuins, laTerracotta, laPrairie); } while(l2 == l);
330 return l2;
331 }); */
332
autoplay(int num_moves=1000000000)333 void autoplay(int num_moves = 1000000000) {
334 // drawMesh();
335 // exit(0);
336
337 doAutoplay = true;
338
339 cheater = 1;
340 eLand lland = laIce;
341 eLand lland2 = laIce;
342 int lcount = 0;
343 int gcount = 0;
344 int lastgold = 0;
345
346 // landlist = { laRuins, laTerracotta, laPrairie };
347
348 generateLandList(isLandIngame);
349
350 #ifndef NOSDL
351 int lastdraw = 0;
352 #endif
353 while(doAutoplay) {
354
355 if(gold() > lastgold) {
356 lastgold = gold();
357 gcount = 0;
358 }
359 else gcount++;
360
361 if(false && sameland(lland, cwt.at->land)) lcount++;
362 else {
363 lcount = 0; lland2 = lland; lland = cwt.at->land;
364 printf("%10dcc %5dt %5de %5d$ %5dK %5dgc %-30s H%d\n", cellcount, turncount, celldist(cwt.at), gold(), tkills(), gcount, dnameof(cwt.at->land).c_str(), hrand(1000000));
365 fflush(stdout);
366 #ifndef NOSDL
367 if(int(SDL_GetTicks()) > lastdraw + 3000) {
368 lastdraw = SDL_GetTicks();
369 fullcenter();
370 msgs.clear();
371 popScreenAll();
372 drawscreen();
373 clearAnimations();
374 }
375 #endif
376 //mainloop();
377 }
378
379 /* if(gcount < 500) for(int i=1; i<isize(dcal); i++) {
380 c2 = dcal[i];
381 if(lcount >= 50 && !sameland(lland, c2->land) && !sameland(lland2, c2->land)) break;
382 else if(lcount < 50 && c2->item && c2->item != itOrbSafety) break;
383 } */
384
385 randomCheat();
386 randomMove();
387 if(false) if(turncount % 5000 == 0) showAutoplayStats();
388 resetIfNeeded(&gcount);
389 noteUnusualSituations();
390 stopIfBug();
391
392 if(turncount >= num_moves) return;
393 }
394 }
395
readArgs()396 int readArgs() {
397 using namespace arg;
398
399 if(0) ;
400 else if(argis("-autoplayW")) {
401 // Start in this land and reset to this land
402 PHASE(3);
403 shift();
404 autoplayLand = readland(args());
405 activateSafety(autoplayLand);
406 }
407 else if(argis("-autoplay")) {
408 PHASE(3);
409 autoplay();
410 }
411 else if(argis("-autoplayN")) {
412 PHASE(3);
413 shift();
414 autoplay(argi());
415 }
416
417 else return 1;
418 return 0;
419 }
420
421 auto hook = addHook(hooks_args, 100, readArgs);
422
423 }
424