// Hyperbolic Rogue -- heads-up display // Copyright (C) 2011-2018 Zeno Rogue, see 'hyper.cpp' for details namespace hr { purehookset hooks_stats; int monsterclass(eMonster m) { if(isFriendly(m) || m == moTortoise) return 1; else if(isMonsterPart(m)) return 2; else return 0; } int glyphclass(int i) { if(i < ittypes) { eItem it = eItem(i); return itemclass(it) == IC_TREASURE ? 0 : 1; } else { eMonster m = eMonster(i-ittypes); return monsterclass(m) == 0 ? 2 : 3; } } int subclass(int i) { if(i < ittypes) return itemclass(eItem(i)); else return monsterclass(eMonster(i-ittypes)); } #define GLYPH_MARKTODO 1 #define GLYPH_MARKOVER 2 #define GLYPH_LOCAL 4 #define GLYPH_IMPORTANT 8 #define GLYPH_NONUMBER 16 #define GLYPH_DEMON 32 #define GLYPH_RUNOUT 64 #define GLYPH_INPORTRAIT 128 #define GLYPH_LOCAL2 256 #define GLYPH_TARGET 512 #define GLYPH_INSQUARE 1024 eGlyphsortorder glyphsortorder; int zero = 0; int& ikmerge(int i) { if(i < ittypes) return items[i]; else if(i == ittypes) return zero; else return kills[i-ittypes]; } bool ikappear(int i) { if(i == itInventory && inv::on) return true; return ikmerge(i); } const int glyphs = ittypes + motypes; int gfirsttime[glyphs], glasttime[glyphs], gcopy[glyphs], ikland[glyphs]; int glyphorder[glyphs]; int glyphphase[glyphs]; int glyph_lastticks; void updatesort() { for(int i=0; i= ittypes) || i == itTerra)) { int a = gp; gp += (ticks - glyph_lastticks); if(a/500 != gp/500) gp = gp/500*500; } } glyph_lastticks = ticks; } void preparesort() { for(int i=0; i gfirsttime[j]; if(glyphsortorder == gsoLastTop) return glasttime[i] > glasttime[j]; if(glyphsortorder == gsoLastBottom) return glasttime[i] < glasttime[j]; if(glyphsortorder == gsoValue) return ikmerge(i) > ikmerge(j); if(glyphsortorder == gsoLand) return ikland[i] < ikland[j]; return 0; } int glyphflags(int gid) { int f = 0; if(gid < ittypes) { eItem i = eItem(gid); if(itemclass(i) == IC_NAI) f |= GLYPH_NONUMBER; if(isElementalShard(i)) { f |= GLYPH_LOCAL | GLYPH_INSQUARE; if(i == localshardof(cwt.at->land)) f |= GLYPH_LOCAL2; } if(i == treasureType(cwt.at->land) || daily::on) f |= GLYPH_LOCAL | GLYPH_LOCAL2 | GLYPH_IMPORTANT | GLYPH_INSQUARE; if(i == itHolyGrail) { if(items[i] >= 3 && !inv::on) f |= GLYPH_MARKOVER; } else if(itemclass(i) == IC_TREASURE) { if(items[i] >= 25 && items[i] < 100 && !inv::on) f |= GLYPH_MARKOVER; else if(items[i] < 10) f |= GLYPH_MARKTODO; } else { f |= GLYPH_IMPORTANT | GLYPH_INSQUARE; if(itemclass(i) == IC_ORB && items[i] < 10) f |= GLYPH_RUNOUT; } if(i == orbToTarget) f |= GLYPH_TARGET; f |= GLYPH_INPORTRAIT; } else { eMonster m = eMonster(gid-ittypes); if(m == moLesser) f |= GLYPH_IMPORTANT | GLYPH_DEMON | GLYPH_INPORTRAIT | GLYPH_INSQUARE; int isnat = isNative(cwt.at->land, m); if(isnat) f |= GLYPH_LOCAL | GLYPH_IMPORTANT | GLYPH_INPORTRAIT | GLYPH_INSQUARE; if(isnat == 2) f |= GLYPH_LOCAL2; if(m == monsterToSummon) f |= GLYPH_TARGET; } return f; } bool graphglyph() { return vid.graphglyph == 2 || (vid.graphglyph == 1 && vid.monmode); } bool displayglyph(int cx, int cy, int buttonsize, char glyph, color_t color, int qty, int flags, int id) { bool b = mousex >= cx && mousex < cx+buttonsize && mousey >= cy-buttonsize/2 && mousey <= cy-buttonsize/2+buttonsize; int glsize = buttonsize; if(glyph == '%' || glyph == 'M' || glyph == 'W') glsize = glsize*4/5; int d = ticks - glasttime[id]; double zoom = (d <= 250 && d >= 0) ? 1.25 - .001 * d : 1; glsize = int(glsize * zoom); if(graphglyph()) { initquickqueue(); if(id >= ittypes) { eMonster m = eMonster(id - ittypes); double bsize = buttonsize * 2/3; if(m == moKrakenH) bsize /= 3; if(m == moKrakenT || m == moDragonTail) bsize /= 2; if(m == moSlime) bsize = (2*bsize+1)/3; transmatrix V = atscreenpos(cx+buttonsize/2, cy, bsize*zoom); int mcol = color; mcol -= (color & 0xFCFCFC) >> 2; drawMonsterType(m, NULL, V, mcol, glyphphase[id]/500.0); } else { eItem it = eItem(id); double bsize = buttonsize / 2; if(glyph =='*') bsize *= 2; if(glyph == '$') bsize = (bsize*5+2)/3; if(glyph == 'o') bsize = (bsize*3+1)/2; if(glyph == 't') bsize = bsize*5/2; if(glyph == '(') bsize = bsize*2.5; if(it == itWarning) bsize *= 2; if(it == itBombEgg || it == itTrollEgg || it == itDodeca) bsize = bsize*3/2; int icol = color; icol -= (color & 0xFCFCFC) >> 2; int ic = itemclass(it); bsize = bsize * zoom; transmatrix V = atscreenpos(cx+buttonsize/2, cy, bsize); double t = (ic == IC_ORB || ic == IC_NAI) ? ticks*2 : ((glyph == 't' && qty%5) || it == itOrbYendor) ? ticks/2 : it == itTerra ? glyphphase[id] * 3 * M_PI + 900 * M_PI: glyphphase[id] * 2; drawItemType(it, NULL, V, icol, t, false); } quickqueue(); } else if(glyph == '*') displaychr(cx + buttonsize/2, cy+buttonsize/4, 0, glsize*3/2, glyph, darkenedby(color, b?0:1)); else displaychr(cx + buttonsize/2, cy, 0, glsize, glyph, darkenedby(color, b?0:1)); string fl = ""; string str = its(qty); if(flags & GLYPH_TARGET) fl += "!"; if(flags & GLYPH_LOCAL2) fl += "+"; else if(flags & GLYPH_LOCAL) fl += "-"; if(flags & GLYPH_DEMON) fl += "X"; if(flags & GLYPH_MARKOVER) str += "!"; if(fl != "") displaystr(cx + buttonsize, cy-buttonsize/2 + buttonsize/4, 0, buttonsize/2, fl, darkenedby(color, 0), 16); if(flags & GLYPH_NONUMBER) str = ""; int bsize = (qty < 10 && (flags & (GLYPH_MARKTODO | GLYPH_RUNOUT))) ? buttonsize*3/4 : qty < 100 ? buttonsize / 2 : buttonsize / 3; if(str != "") displayfr(cx + buttonsize, cy + buttonsize/2 - bsize/2, 1, bsize, str, color, 16); return b; } void displayglyph2(int cx, int cy, int buttonsize, int i) { char glyph = i < ittypes ? iinf[i].glyph : minf[i-ittypes].glyph; color_t color = i < ittypes ? iinf[i].color : minf[i-ittypes].color; int imp = glyphflags(i); if(displayglyph(cx, cy, buttonsize, glyph, color, ikmerge(i), imp, i)) { instat = true; getcstat = SDLK_F1; if(i < ittypes) { eItem it = eItem(i); int t = itemclass(it); if(t == IC_TREASURE) mouseovers = XLAT("treasure collected: %1", it); if(t == IC_OTHER) mouseovers = XLAT("objects found: %1", it); if(t == IC_NAI) mouseovers = XLATT1(it); if(t == IC_ORB) mouseovers = XLAT("orb power: %1", eItem(i)); if(it == itGreenStone) { mouseovers += XLAT(" (click to drop)"); getcstat = 'g'; } if(it == itStrongWind) { mouseovers += XLAT(" (click to use)"); getcstat = 't'; } if(it == itInventory) { mouseovers += XLAT(" (click to use)"); getcstat = 'i'; } if(imp & GLYPH_LOCAL) mouseovers += XLAT(" (local treasure)"); help = generateHelpForItem(it); } else { eMonster m = eMonster(i-ittypes); if(isMonsterPart(m)) mouseovers = s0 + XLAT("parts destroyed: %1", m); else if(isFriendly(m) && isNonliving(m)) mouseovers = s0 + XLAT("friends destroyed: %1", m); else if(isFriendly(m)) mouseovers = s0 + XLAT("friends killed: %1", m); else if(isNonliving(m)) mouseovers = s0 + XLAT("monsters destroyed: %1", m); else if(m == moTortoise) mouseovers = s0 + XLAT("animals killed: %1", m); else mouseovers = s0 + XLAT("monsters killed: %1", m); if(imp & GLYPH_LOCAL2) mouseovers += XLAT(" (killing increases treasure spawn)"); else if(imp & GLYPH_LOCAL) mouseovers += XLAT(" (appears here)"); help = generateHelpForMonster(m); } } } bool nohud, nomenukey; hookset *hooks_prestats; void drawMobileArrow(int i) { int dir = i; cell *c = cwt.at->move(i); if(!c) return; transmatrix T; if(!compute_relamatrix(c, cwt.at, i, T)) return; // color_t col = getcs().uicolor; // col -= (col & 0xFF) >> 1; bool invalid = !legalmoves[dir]; color_t col = cellcolor(c); if(col == OUTLINE_NONE) col = 0xC0C0C0FF; col -= (col & 0xFF) >> 1; if(invalid) col -= (col & 0xFF) >> 1; if(invalid) col -= (col & 0xFF) >> 1; poly_outline = OUTLINE_DEFAULT; // transmatrix m2 = Id; ld scale = vid.mobilecompasssize * (sphere ? 7 : euclid ? 6 : 5); // m2[0][0] = scale; m2[1][1] = scale; m2[2][2] = 1; transmatrix U = ggmatrix(cwt.at); hyperpoint H = sphereflip * tC0(U); transmatrix Centered = sphereflip * rgpushxto0(H); hyperpoint P = inverse(Centered) * U * T * C0; double alpha = atan2(P[1], P[0]); using namespace shmupballs; double dx = xmove + rad*(1+SKIPFAC-.2)/2 * cos(alpha); double dy = yb + rad*(1+SKIPFAC-.2)/2 * sin(alpha); queuepolyat(atscreenpos(dx, dy, scale) * spin(-alpha), shArrow, col, PPR::MOBILE_ARROW); } bool nofps = false; void drawStats() { if(nohud || stereo::mode == stereo::sLR) return; if(callhandlers(false, hooks_prestats)) return; if(viewdists && sidescreen) expansion.view_distances_dialog(); if(sidescreen) return; { dynamicval pm(pmodel, mdDisk); dynamicval v(vid, vid); vid.alpha = vid.scale = 1; calcparam(); stereo::set_projection(0); if(haveMobileCompass()) { initquickqueue(); using namespace shmupballs; calc(); queuecircle(xmove, yb, rad, 0xFF0000FF); queuecircle(xmove, yb, rad*SKIPFAC, legalmoves[MAX_EDGE] ? 0xFF0000FF : 0xFF000080 ); for(int i=0; itype; i++) drawMobileArrow(i); if(hypot(mousex-xmove, mousey-yb) <= rad) getcstat = '-'; quickqueue(); } if(vid.xres > vid.yres * 85/100 && vid.yres > vid.xres * 85/100) { int bycorner[4]; for(int u=0; u<4; u++) bycorner[u] = 0; for(int i=0; i rad) { spots++; } if(spots >= bycorner[cor] && spots >= 3) { int next = 0; vector glyphstoshow; for(int i=0; i rad) { if(next >= isize(glyphstoshow)) break; int cx = u; int cy = v + s/2; if(cor&1) cx = vid.xres-1-s-cx; if(cor&2) cy = vid.yres-1-cy; displayglyph2(cx, cy, s, glyphstoshow[next++]); } break; } } } } else { instat = false; bool portrait = vid.xres < vid.yres; int colspace = portrait ? (vid.yres - vid.xres - vid.fsize*3) : (vid.xres - vid.yres - 16) / 2; int rowspace = portrait ? vid.xres - 16 : vid.yres - vid.fsize * (vid.msgleft ? 9 : 4); int colid[4], rowid[4]; int maxbyclass[4]; for(int z=0; z<4; z++) maxbyclass[z] = 0; for(int i=0; i columns) { vid.killreduction++; continue; } coltaken = 0; } colid[z] = coltaken, rowid[z] = 0, coltaken += (maxbyclass[z] + rows-1) / rows; } if(coltaken > columns) { vid.killreduction++; continue; } break; } if(buttonsize <= vid.fsize*3/4) { imponly = true; buttonsize = minsize; rows = rowspace / buttonsize; if(!rows) return; colid[0] = 0; colid[2] = portrait ? 1 : 0; } updatesort(); stable_sort(glyphorder, glyphorder+glyphs, glyphsort); for(int i0=0; i0= rows) rowid[z] = 0, colid[z]++; displayglyph2(cx, cy, buttonsize, i); } } } calcparam(); stereo::set_projection(0); string s0; if(!peace::on) { if(displayButtonS(vid.xres - 8, vid.fsize, XLAT("score: %1", its(gold())), forecolor, 16, vid.fsize)) { mouseovers = XLAT("Your total wealth"), instat = true, getcstat = SDLK_F1, help = helptitle(XLAT("Your total wealth"), 0xFFD500) + XLAT( "The total value of the treasure you have collected.\n\n" "Every world type contains a specific type of treasure, worth 1 $$$; " "your goal is to collect as much treasure as possible, but every treasure you find " "causes more enemies to hunt you in its native land.\n\n" "Orbs of Yendor are worth 50 $$$ each.\n\n" ); } if(displayButtonS(8, vid.fsize, XLAT("kills: %1", its(tkills())), forecolor, 0, vid.fsize)) { instat = true, getcstat = SDLK_F1, mouseovers = XLAT("Your total kills")+": " + its(tkills()), help = helptitle(XLAT("Your total kills") + ": " + its(tkills()), 0x404040) + XLAT( "In most lands, more treasures are generated with each enemy native to this land you kill. " "Moreover, 100 kills is a requirement to enter the Graveyard and the Hive.\n\n" "Friendly creatures and parts of monsters (such as the Ivy) do appear in the list, " "but are not counted in the total kill count."); } } string vers = VER; if(!nofps) vers += XLAT(" fps: ") + its(calcfps()); if(displayButtonS(4, vid.yres - 4 - vid.fsize/2, vers, 0x202020, 0, vid.fsize/2)) { mouseovers = XLAT("frames per second"), getcstat = SDLK_F1, instat = true, help = helptitle(XLAT("frames per second"), 0xFF4040) + XLAT( "The higher the number, the smoother the animations in the game. " "If you find that animations are not smooth enough, you can try " "to change the options " ) + #if ISIOS XLAT( "(in the MENU). You can reduce the sight range, this should make " "the animations smoother."); #else XLAT( "(press v) and change the wall/monster mode to ASCII, or change " "the resolution."); #endif } achievement_display(); callhooks(hooks_stats); } }