// Hyperbolic Rogue -- debugging routines // Copyright (C) 2011-2018 Zeno Rogue, see 'hyper.cpp' for details /** \file debug.cpp * \brief Debugging and cheating */ #include "hyper.h" namespace hr { EX int steplimit = 0; EX int cstep; EX bool buggyGeneration = false; EX bool debug_cellnames = false; EX vector buggycells; #if HDR template void limitgen(T... args) { if(steplimit) { cstep++; printf("%6d ", cstep); printf(args...); if(cstep == steplimit) buggyGeneration = true; } } #endif EX cell *pathTowards(cell *pf, cell *pt) { while(celldist(pt) > celldist(pf)) { if(isNeighbor(pf, pt)) return pt; cell *pn = NULL; forCellEx(pn2, pt) if(celldist(pn2) < celldist(pt)) pn = pn2; pt = pn; } if(isNeighbor(pf, pt)) return pt; forCellEx(pn2, pt) if(celldist(pn2) < celldist(pt)) return pn2; return NULL; } bool errorReported = false; EX void describeCell(cell *c) { if(!c) { printf("NULL\n"); return; } printf("describe %p: ", hr::voidp(c)); printf("%-15s", linf[c->land].name); printf("%-15s", winf[c->wall].name); printf("%-15s", iinf[c->item].name); printf("%-15s", minf[c->monst].name); printf("LP%08x", c->landparam); printf("D%3d", c->mpdist); printf("MON%3d", c->mondir); printf("\n"); } static int orbid = 0; eItem nextOrb() { orbid++; eItem i = eItem(orbid % ittypes); if(itemclass(i) == IC_ORB) return i; else return nextOrb(); } eItem randomTreasure() { eItem i = eItem(hrand(ittypes)); if(itemclass(i) == IC_TREASURE) return i; else return randomTreasure(); } eItem randomTreasure2(int cv) { int bq = 60000, cq = 0; eItem best = itDiamond; eItem lt = localTreasureType(); for(int a=1; aland)) q -= 8; if(a == itElixir && isCrossroads(cwt.at->land)) q -= 7; if(a == itIvory && isCrossroads(cwt.at->land)) q -= 6; if(a == itPalace && isCrossroads(cwt.at->land)) q -= 5; if(a == itIvory && cwt.at->land == laJungle) q -= 5; if(a == itIvory && cwt.at->land == laPalace) q -= 5; if(q < bq) bq = q, cq = 0; if(q == bq) { cq++; if(hrand(cq) == 0) best = i; } } return best; } EX eLand cheatdest; EX void cheatMoveTo(eLand l) { cheatdest = l; if(l == laCrossroads5) l = laCrossroads; activateSafety(l); cheatdest = laNone; } EX bool applyCheat(char u, cell *c IS(NULL)) { if(u == 'L') { do { if(firstland == eLand(landtypes-1)) firstland = eLand(2); else firstland = eLand(firstland+1); } while(isTechnicalLand(firstland) || isCyclic(firstland)); specialland = firstland; cheater++; addMessage(XLAT("You will now start your games in %1", firstland)); return true; } if(u == 'C') { cheater++; cheatMoveTo(laCrossroads); addMessage(XLAT("Activated the Hyperstone Quest!")); for(int t=1; ttype; i++) if(passable(cwt.at->move(i), NULL, 0)) { eItem it = nextOrb(); cwt.at->move(i)->item = it; } return true; } if(u == 'F') { if(hardcore && !canmove) { canmove = true; addMessage(XLAT("Revived!")); } else { items[itOrbFlash] += 1; items[itOrbTeleport] += 1; items[itOrbLightning] += 1; items[itOrbSpeed] += 1; items[itOrbShield] += 1; kills[moPlayer] = 0; cheater++; addMessage(XLAT("Orb power gained!")); canmove = true; } return true; } if(u == 'D') { items[itGreenStone] += 10; cheater++; addMessage(XLAT("Dead orbs gained!")); return true; } if(u == 'R'-64) buildRosemap(); #if CAP_EDIT if(u == 'A') { lastexplore = turncount; pushScreen(mapeditor::showMapEditor); return true; } if(u == 'A'-64) { mapeditor::drawcell = mouseover ? mouseover : cwt.at; pushScreen(mapeditor::showDrawEditor); return true; } #elif CAP_SHOT if(u == 'A') { pushScreen(shot::menu); return true; } #endif if(u == 'T') { items[randomTreasure2(10)] += 10; cheater++; addMessage(XLAT("Treasure gained!")); return true; } if(u == 'T'-64) { items[randomTreasure2(100)] += 100; cheater++; addMessage(XLAT("Lots of treasure gained!")); return true; } if(u == 'I'-64) { items[randomTreasure2(10)] += 25; cheater++; addMessage(XLAT("Treasure gained!")); return true; } if(u == 'U'-64) { items[randomTreasure2(10)] += 50; cheater++; addMessage(XLAT("Treasure gained!")); return true; } if(u == 'Z') { if (flipplayer) { cwt += cwt.at->type/2; flipplayer = false; } cwt++; mirror::act(1, mirror::SPINSINGLE); cwt.at->mondir++; cwt.at->mondir %= cwt.at->type; if(shmup::on) shmup::pc[0]->at = Id; return true; } if(u == 'J') { if(items[localTreasureType()] > 0) items[localTreasureType()] = 0; else for(int i=1; iitem == itKey) y.path[YDIST-1]->item = itNone; if(!y.found) items[itKey]++; y.found = true; } cheater++; addMessage(XLAT("Collected the keys!")); } if(u == 'Y'-64) { yendor::collected(cwt.at); cheater++; } if(u == 'P') { items[itSavedPrincess]++; princess::saved = true; princess::everSaved = true; if(inv::on && !princess::reviveAt) princess::reviveAt = gold(NO_LOVE); cheater++; addMessage(XLAT("Saved the Princess!")); } if(u == 'S') { canmove = true; cheatMoveTo(cwt.at->land); items[itOrbSafety] += 3; cheater++; addMessage(XLAT("Activated Orb of Safety!")); return true; } if(u == 'U') { canmove = true; cheatMoveTo(firstland); cheater++; addMessage(XLAT("Teleported to %1!", firstland)); return true; } if(u == 'W'-64) { pushScreen(linepatterns::showMenu); return true; } if(u == 'G'-64) { timerghost = !timerghost; cheater++; addMessage(XLAT("turn count = %1 last exploration = %2 ghost timer = %3", its(turncount), its(lastexplore), ONOFF(timerghost))); return true; } if(u == 'L'-64) { cell *c = mouseover; describeCell(c); printf("Neighbors:"); for(int i=0; itype; i++) printf("%p ", hr::voidp(c->move(i))); printf("Barrier: dir=%d left=%d right=%d\n", c->bardir, c->barleft, c->barright); return true; } if(u == 'C'-64) { cblind = !cblind; return true; } if(u == 'G') { push_debug_screen(); return true; } if(u == 'P'-64) peace::on = !peace::on; #ifdef CHEAT_DISABLE_ALLOWED if(u == 'D'-64) { cheater = 0; autocheat = 0; return true; } #endif return false; } template string dnameof2(T x) { string s = dnameof(x); return s + " (" + its(x) + ")"; } template string dnameof2(T x, int p) { string s = dnameof(x); return s + " (" + its(x) + "/" + its(p) + ")"; } EX vector > drawbugs; bool debugmode = false; // static apparently does not work in old compilers int bitfield_v; template void bitfield_editor(int val, T setter, string help = "") { bitfield_v = val; dialog::editNumber(bitfield_v, 0, 100, 1, bitfield_v, help, ""); dialog::reaction = [setter] () { setter(bitfield_v); }; } struct debugScreen { cell *debugged_cell; bool show_debug_data; debugScreen() { debugged_cell = NULL; show_debug_data = false; } void operator () () { cmode = sm::SIDE | sm::DIALOG_STRICT_X; gamescreen(0); getcstat = '-'; dialog::init(show_debug_data ? XLAT("debug values") : XLAT("internal details")); for(auto& p: drawbugs) drawBug(p.first, p.second); cell *what = debugged_cell; if(!what && current_display->sidescreen) what = mouseover; if(what) { #if CAP_SHAPES queuepoly(gmatrix[what], cgi.shAsymmetric, 0x80808080); #endif char buf[200]; sprintf(buf, "%p", hr::voidp(what)); dialog::addSelItem("mpdist", its(what->mpdist), 'd'); dialog::add_action([what] () { bitfield_editor(what->mpdist, [what] (int i) { what->mpdist = 0; }, "generation level"); }); dialog::addSelItem("land", dnameof2(what->land), 0); dialog::addSelItem("land param (int)", its(what->landparam), 'p'); dialog::add_action([what] () { dialog::editNumber(what->landparam, 0, 100, 1, what->landparam, "landparam", "Extra value that is important in some lands. The specific meaning depends on the land."); }); dialog::addSelItem("land param (hex)", itsh8(what->landparam), 0); dialog::addSelItem("land param (heat)", fts(HEAT(what)), 't'); dialog::addSelItem("cdata", its(getCdata(what, 0))+"/"+its(getCdata(what,1))+"/"+its(getCdata(what,2))+"/"+its(getCdata(what,3))+"/"+itsh(getBits(what)), 't'); dialog::add_action([what] () { static ld d = HEAT(what); dialog::editNumber(d, -2, 2, 0.1, d, "landparam", "Extra value that is important in some lands. The specific meaning depends on the land."); dialog::reaction = [what] () { HEAT(what) = d; }; }); dialog::addSelItem("land flags", its(what->landflags)+"/"+itsh2(what->landflags), 'f'); dialog::add_action([what] () { bitfield_editor(what->landflags, [what] (int i) { what->landflags = i; }, "Rarely used."); }); dialog::addSelItem("barrier dir", its(what->bardir), 'b'); dialog::add_action([what] () { bitfield_editor(what->bardir, [what] (int i) { what->bardir = i; }); }); dialog::addSelItem("barrier left", dnameof2(what->barleft), 0); dialog::addSelItem("barrier right", dnameof2(what->barright), 0); if(what->item == itBabyTortoise) { dialog::addSelItem(XLAT("baby Tortoise flags"), itsh(tortoise::babymap[what]), 'B'); dialog::add_action([what] () { dialog::editNumber(tortoise::babymap[what], 0, (1<<21)-1, 1, getBits(what), "", ""); dialog::use_hexeditor(); }); } if(what->monst == moTortoise) { dialog::addSelItem(XLAT("adult Tortoise flags"), itsh(tortoise::emap[what]), 'A'); dialog::add_action([what] () { tortoise::emap[what] = tortoise::getb(what); dialog::editNumber(tortoise::emap[what], 0, (1<<21)-1, 1, getBits(what), "", ""); dialog::use_hexeditor(); }); } #if CAP_COMPLEX2 if(dice::on(what)) { dialog::addSelItem(XLAT("die shape"), dice::die_name(dice::data[what].which), 'A'); dialog::add_action_push([what] { dialog::init("die shape"); char key = 'a'; for(auto shape: dice::die_list) { dialog::addItem(dice::die_name(shape), key++); dialog::add_action([what, shape] { dice::data[what].which = shape; dice::data[what].val = 0; popScreen(); }); } dialog::display(); }); dialog::addSelItem(XLAT("die face"), its(dice::data[what].val), 'B'); dialog::add_action([what] { auto& dd = dice::data[what]; int maxv = shape_faces(dd.which)-1; dialog::editNumber(dd.val, 0, maxv, 1, 0, XLAT("die face"), ""); dialog::bound_low(0); dialog::bound_up(maxv); }); dialog::addSelItem(XLAT("die direction"), its(dice::data[what].dir), 'C'); dialog::add_action([what] { auto& dd = dice::data[what]; dialog::editNumber(dd.dir, 0, what->type-1, 1, dd.dir, XLAT("die direction"), ""); dialog::bound_low(0); dialog::bound_up(what->type-1); }); dialog::addBoolItem_action(XLAT("die mirror status"), dice::data[what].mirrored, 'D'); } #endif dialog::addBreak(50); if(show_debug_data) { dialog::addSelItem("pointer", s0+buf+"/"+index_pointer(what), 0); dialog::addSelItem("cpdist", its(what->cpdist), 0); dialog::addSelItem("celldist", its(celldist(what)), 0); dialog::addSelItem("celldistance", its(celldistance(cwt.at, what)), 0); dialog::addSelItem("pathdist", its(what->pathdist), 0); dialog::addSelItem("celldistAlt", eubinary ? its(celldistAlt(what)) : "--", 0); dialog::addSelItem("temporary", its(what->listindex), 0); #if CAP_GP if(GOLDBERG) dialog::addSelItem("whirl", sprint(gp::get_local_info(what).relative), 0); #endif #if CAP_RACING if(racing::on) racing::add_debug(what); #endif } else { dialog::addSelItem("wall", dnameof2(what->wall, what->wparam), 'w'); dialog::add_action([what] () { bitfield_editor(what->wparam, [what] (int i) { what->wparam = i; }, "wall parameter"); }); dialog::addSelItem("item", dnameof(what->item), 0); #if CAP_ARCM if(arcm::in()) dialog::addSelItem("ID", its(arcm::id_of(what->master)), 0); #endif dialog::addBreak(50); dialog::addSelItem("monster", dnameof2(what->monst, what->mondir), 'm'); dialog::add_action([what] () { bitfield_editor(what->mondir, [what] (int i) { what->mondir = i; }, "monster direction"); dialog::extra_options = [what] () { dialog::addBoolItem(XLAT("mirrored"), what->monmirror, 'M'); }; }); dialog::addSelItem("stuntime", its(what->stuntime), 's'); dialog::add_action([what] () { bitfield_editor(what->stuntime, [what] (int i) { what->stuntime = i; }); }); dialog::addSelItem("hitpoints", its(what->hitpoints), 'h'); dialog::add_action([what] () { bitfield_editor(what->hitpoints, [what] (int i) { what->hitpoints = i; }); }); dialog::addBreak(50); dialog::addBreak(50); dialog::addItem("show debug data", 'x'); dialog::add_action([this] () { show_debug_data = true; }); if(!debugged_cell) dialog::addItem("click a cell to edit it", 0); } } else { dialog::addItem(XLAT("click a cell to view its data"), 0); dialog::addBreak(1000); } dialog::addBack(); dialog::display(); keyhandler = [this, what] (int sym, int uni) { handlePanning(sym, uni); dialog::handleNavigation(sym, uni); if(applyCheat(uni, what)) ; else if(sym == PSEUDOKEY_WHEELUP || sym == PSEUDOKEY_WHEELDOWN) ; else if(sym == '-') debugged_cell = mouseover; else if(doexiton(sym, uni)) { popScreen(); if(debugmode) quitmainloop = true; } }; } }; EX void push_debug_screen() { debugScreen ds; pushScreen(ds); } /** show the cheat menu */ EX void showCheatMenu() { gamescreen(1); dialog::init("cheat menu"); dialog::addItem(XLAT("gain orb powers"), 'F'); dialog::addItem(XLAT("summon treasure"), 'T'); dialog::addItem(XLAT("summon dead orbs"), 'D'); dialog::addItem(XLAT("lose all treasure"), 'J'); dialog::addItem(XLAT("gain kills"), 'K'); dialog::addItem(XLAT("Hyperstone Quest"), 'C'); dialog::addItem(XLAT("summon orbs"), 'O'); dialog::addItem(XLAT("summon lots of treasure"), 'T'-64); dialog::addItem(XLAT("Safety (quick save)"), 'S'); dialog::addItem(XLAT("Select the land ---"), 'L'); dialog::addItem(XLAT("--- and teleport there"), 'U'); dialog::addItem(XLAT("rotate the character"), 'Z'); dialog::addItem(XLAT("deplete orb powers"), 'M'); dialog::addItem(XLAT("save a Princess"), 'P'); dialog::addItem(XLAT("unlock Orbs of Yendor"), 'Y'); dialog::addItem(XLAT("gain Orb of Yendor"), 'Y'-64); dialog::addItem(XLAT("switch ghost timer"), 'G'-64); dialog::addItem(XLAT("switch web display"), 'W'-64); dialog::addItem(XLAT("peaceful mode"), 'P'-64); dialog::addBreak(50); dialog::addBack(); dialog::display(); keyhandler = [] (int sym, int uni) { dialog::handleNavigation(sym, uni); if(uni != 0) { applyCheat(uni); if(uni == 'F' || uni == 'C' || uni == 'O' || uni == 'S' || uni == 'U' || uni == 'G' || uni == 'W' || uni == 'I' || uni == 'E' || uni == 'H' || uni == 'B' || uni == 'M' || uni == 'M' || uni == 'Y'-64 || uni == 'G'-64 || uni == ' ' || uni == 8 || uni == 13 || uni == SDLK_ESCAPE || uni == 'q' || uni == 'v' || sym == SDLK_ESCAPE || sym == SDLK_F10) popScreen(); } }; } /** view all the monsters and items */ EX void viewall() { celllister cl(cwt.at, 20, 2000, NULL); vector all_monsters; for(int i=0; imonst || isPlayerOn(c2)) can_put_monster = false; if(can_put_monster) { for(int k=0; kmonst = all_monsters[k]; all_monsters[k] = all_monsters.back(); all_monsters.pop_back(); } } } vector itemcells; for(cell *c: cl.lst) { if(isPlayerOn(c) || c->monst || c->item) continue; itemcells.push_back(c); } int id = 0; for(int it=1; it= isize(itemcells)) break; itemcells[id++]->item = eItem(it); } } #if CAP_COMMANDLINE /** perform a move for the -cmove command */ int cheat_move_gen = 7; void cheat_move(char c) { using arg::cheat; if(c >= '0' && c <= '9' && cheat_move_gen == -1) cheat_move_gen = (c - '0'); else if(c >= '0' && c <= '9') cheat(), cwt += (c - '0'); else if(c == 's') { cheat(); cwt += wstep; playermoved = false; setdist(cwt.at, cheat_move_gen, cwt.peek()); if(geometry_supports_cdata()) getCdata(cwt.at, 0); } else if(c == 'r') cheat(), cwt += rev; else if(c == 'm') cheat(), cwt += wmirror; else if(c == 'z') cheat(), cwt.spin = 0, cwt.mirrored = false; else if(c == 'F') centering = eCentering::face, fullcenter(); else if(c == 'E') centering = eCentering::edge, fullcenter(); else if(c == 'V') centering = eCentering::vertex, fullcenter(); else if(c == 'a') cheat(), history::save_end(); else if(c == 'g') cheat_move_gen = -1; else println(hlog, "unknown move command: ", c); } #endif /** launch a debugging screen, and continue normal working only after this screen is closed */ EX void modalDebug(cell *c) { centerover = c; View = Id; if(noGUI) { fprintf(stderr, "fatal: modalDebug called on %p without GUI\n", hr::voidp(c)); exit(1); } push_debug_screen(); debugmode = true; mainloop(); debugmode = false; quitmainloop = false; } void test_distances(int max) { int ok = 0, bad = 0; celllister cl(cwt.at, max, 100000, NULL); for(cell *c: cl.lst) { bool is_ok = cl.getdist(c) == celldistance(c, cwt.at); if(is_ok) ok++; else bad++; } println(hlog, "ok=", ok, " bad=", bad); } EX void raiseBuggyGeneration(cell *c, const char *s) { printf("procgen error (%p): %s\n", hr::voidp(c), s); if(!errorReported) { addMessage(string("something strange happened in: ") + s); errorReported = true; } #ifdef BACKTRACE void *array[1000]; size_t size; // get void*'s for all entries on the stack size = backtrace(array, 1000); // print out all the frames to stderr backtrace_symbols_fd(array, size, STDERR_FILENO); #endif // return; if(cheater || autocheat) { drawbugs.emplace_back(cellwalker(c,0), 0xFF000080); modalDebug(c); drawbugs.pop_back(); } else c->item = itBuggy; } #if CAP_COMMANDLINE int read_cheat_args() { using namespace arg; if(argis("-ch")) { cheat(); } else if(argis("-rch")) { PHASEFROM(2); cheat(); reptilecheat = true; } // cheats else if(argis("-g")) { /* debugging mode */ if(curphase == 1) { /* use a separate score file */ scorefile = "xx"; /* set seed for reproducible results */ if(!fixseed) { fixseed = true; autocheat = true; startseed = 1; } } PHASE(2); /* causes problems in gdb */ mouseaim_sensitivity = 0; /* do not any play sounds while debugging */ effvolume = 0; musicvolume = 0; } else if(argis("-WS")) { PHASE(3); shift(); activateSafety(readland(args())); cheat(); } else if(argis("-WT")) { PHASE(3); shift(); teleportToLand(readland(args()), false); cheat(); } else if(argis("-W2")) { shift(); cheatdest = readland(args()); cheat(); showstartmenu = false; } else if(argis("-I")) { PHASE(3) cheat(); shift(); eItem i = readItem(args()); shift(); items[i] = argi(); } else if(argis("-IP")) { PHASE(3) cheat(); shift(); eItem i = readItem(args()); shift(); int q = argi(); placeItems(q, i); } else if(argis("-SM")) { PHASEFROM(2); shift(); vid.stereo_mode = eStereo(argi()); } else if(argis("-cmove")) { PHASE(3); shift(); for(char c: args()) cheat_move(c); } else if(argis("-ipd")) { PHASEFROM(2); shift_arg_formula(vid.ipd); } #if CAP_INV else if(argis("-IU")) { PHASE(3) cheat(); shift(); eItem i = readItem(args()); shift(); inv::usedup[i] += argi(); inv::compute(); } else if(argis("-IX")) { PHASE(3) cheat(); shift(); eItem i = readItem(args()); shift(); inv::extra_orbs[i] += argi(); inv::compute(); } #endif #if CAP_COMPLEX2 else if(argis("-ambush")) { // make all ambushes use the given number of dogs // example: hyper -W Hunt -IP Shield 1 -ambush 60 PHASE(3) cheat(); shift(); ambush::fixed_size = argi(); } #endif else if(argis("-testdistances")) { PHASE(3); shift(); test_distances(argi()); } else if(argis("-M")) { PHASE(3) cheat(); start_game(); if(WDIM == 3) { drawthemap(); bfs(); } shift(); eMonster m = readMonster(args()); shift(); int q = argi(); printf("m = %s q = %d\n", dnameof(m).c_str(), q); restoreGolems(q, m, 7); } else if(argis("-MK")) { PHASE(3) cheat(); shift(); eMonster m = readMonster(args()); shift(); kills[m] += argi(); } else if(argis("-killeach")) { PHASEFROM(2); start_game(); shift(); int q = argi(); cheat(); for(int m=0; m= '1' && c <= '9') dialog::queue_key(SDLK_F1 + c - '1'); else if(c == 'e') dialog::queue_key(SDLK_ESCAPE); else if(c == 'r') dialog::queue_key(SDLK_RETURN); else if(c == 't') dialog::queue_key(SDLK_TAB); else if(c == 'b') dialog::queue_key(SDLK_BACKSPACE); else if(c == 'R') dialog::queue_key(SDLK_RIGHT); else if(c == 'L') dialog::queue_key(SDLK_LEFT); else if(c == 'U') dialog::queue_key(SDLK_UP); else if(c == 'D') dialog::queue_key(SDLK_DOWN); else if(c == 'H') dialog::queue_key(SDLK_HOME); else if(c == 'E') dialog::queue_key(SDLK_END); else if(c == 'P') dialog::queue_key(SDLK_PAGEUP); else if(c == 'Q') dialog::queue_key(SDLK_PAGEDOWN); } else if(c == '\\') quote = true; else dialog::queue_key(c); } else if(argis("-hroll")) { shift(); int i = argi(); while(i>0) i--, hrand(10); } else if(argis("-W")) { PHASEFROM(2); shift(); firstland0 = firstland = specialland = readland(args()); if (!landUnlocked(firstland)) cheat(); stop_game_and_switch_mode(rg::nothing); showstartmenu = false; } else return 1; return 0; } auto ah_cheat = addHook(hooks_args, 0, read_cheat_args); #endif EX bool ldebug = false; EX void breakhere() { exit(1); } }