// Hyperbolic Rogue -- main graphics file // Copyright (C) 2011-2019 Zeno Rogue, see 'hyper.cpp' for details /** \file graph.cpp * \brief Drawing cells, monsters, items, etc. */ #include "hyper.h" namespace hr { EX int last_firelimit; EX int firelimit; EX int inmirrorcount = 0; /** wall optimization: do not draw things beyond walls */ EX bool wallopt; EX bool in_wallopt() { return wallopt || racing::on; } EX bool spatial_graphics; EX bool wmspatial, wmescher, wmplain, wmblack, wmascii, wmascii3; EX bool mmspatial, mmhigh, mmmon, mmitem; EX int detaillevel = 0; EX bool first_cell_to_draw = true; EX bool zh_ascii = false; EX bool in_perspective() { return models::is_perspective(pconf.model); } EX bool in_perspective_v() { return models::is_perspective(vpconf.model); } EX bool hide_player() { return GDIM == 3 && playermoved && vid.yshift == 0 && vid.sspeed > -5 && in_perspective() && (first_cell_to_draw || elliptic) && (WDIM == 3 || vid.camera == 0) && !inmirrorcount #if CAP_RACING && !(racing::on && !racing::use_standard_centering() && !racing::player_relative) #endif ; } EX transmatrix ddspin180(cell *c, int dir) { return ddspin(c, dir, M_PI); } EX transmatrix iddspin180(cell *c, int dir) { return iddspin(c, dir, M_PI); } EX transmatrix lpispin() { return spin180(); } EX hookset hooks_handleKey; EX hookset hooks_drawcell; EX purehookset hooks_frame, hooks_markers; #define WOLNIEJ 1 #define BTOFF 0x404040 #define BTON 0xC0C000 // #define PANDORA int colorbar; EX bool inHighQual; // taking high quality screenshot EX bool auraNOGL; // aura without GL // int axestate; EX int ticks; EX int frameid; EX bool nomap; EX eItem orbToTarget; EX eMonster monsterToSummon; EX int sightrange_bonus = 0; EX string mouseovers; EX int darken = 0; EX bool doHighlight() { return mmhigh; } int dlit; ld spina(cell *c, int dir) { return TAU * dir / c->type; } /** @brief used to alternate colors depending on distance to something. In chessboard-patterned geometries, automatically a third step. * In some cases, we want to avoid a number of colors in the table -- set @param subtract to the number of such colors. */ EX color_t get_color_auto3(int f, const colortable& ctab, int subtract IS(0)) { int size = ctab.size() - subtract; if(size < 1) return 0; if(geosupport_chessboard() && size == 2) { f = gmod(f, 3); if(f == 1) return gradient(ctab[0], ctab[1], 0, 1, 2); return ctab[f/2]; } else return ctab[gmod(f, size)]; } EX ld cheilevel(ld v) { return cgi.FLOOR + (cgi.HEAD - cgi.FLOOR) * v; } EX transmatrix chei(const transmatrix V, int a, int b) { #if MAXMDIM >= 4 if(GDIM == 2) return V; return V * lzpush(cheilevel((a+.5) / b)); #else return V; #endif } EX shiftmatrix chei(const shiftmatrix V, int a, int b) { #if MAXMDIM >= 4 if(GDIM == 2) return V; return V * lzpush(cheilevel((a+.5) / b)); #else return V; #endif } EX bool ivoryz; /** Change the level of V. Takes ivoryz and all geometries into account */ EX transmatrix at_smart_lof(const transmatrix& V, ld lev) { if(!mmspatial) return V; if(ivoryz) return mzscale(V, lev); return orthogonal_move_fol(V, lev); } EX shiftmatrix at_smart_lof(const shiftmatrix& V, ld lev) { return shiftless(at_smart_lof(V.T, lev), V.shift); } EX color_t kind_outline(eItem it) { int k = itemclass(it); if(k == IC_TREASURE) return OUTLINE_TREASURE; else if(k == IC_ORB) return OUTLINE_ORB; else return OUTLINE_OTHER; } /** should objects fly slightly up and down in product/twisted product geometries */ EX bool bobbing = true; EX void draw_ascii(const shiftmatrix& V, const string& s, color_t col, ld size, ld size2) { int id = isize(ptds); if(WDIM == 2 && GDIM == 3) queuestrn(V * lzpush(cgi.FLOOR - cgi.scalefactor * size / 4), size * mapfontscale / 100, s, darkenedby(col, darken), 0); else queuestrn(V, size2 * mapfontscale / 100, s, darkenedby(col, darken), GDIM == 3 ? 0 : 2); while(id < isize(ptds)) ptds[id++]->prio = PPR::MONSTER_BODY; } EX void draw_ascii(const shiftmatrix& V, char glyph, color_t col, ld size) { draw_ascii(V, s0 + glyph, col, size, 1); } EX void draw_ascii_or_zh(const shiftmatrix& V, char glyph, const string& name, color_t col, ld size, ld zh_size) { #if CAP_TRANS if(zh_ascii) { auto p = XLAT1_acc(name, 8); if(p) { string chinese = p; chinese.resize(utfsize(chinese[0])); dynamicval df(cfont, cfont_chinese); draw_ascii(V, chinese, col, size, zh_size); return; } } #endif draw_ascii(V, glyph, col, size); } // push down the queue after q-th element, `down` absolute units down, // based on cell c and transmatrix V // do change the zoom factor? do change the priorities? EX int cellcolor(cell *c) { if(isPlayerOn(c) || isFriendly(c)) return OUTLINE_FRIEND; if(noHighlight(c->monst)) return OUTLINE_NONE; if(c->monst) return OUTLINE_ENEMY; if(c->wall == waMirror) return c->land == laMirror ? OUTLINE_TREASURE : OUTLINE_ORB; if(c->item && !itemHiddenFromSight(c)) { int k = itemclass(c->item); if(k == IC_TREASURE) return OUTLINE_TREASURE; else if(k == IC_ORB) return OUTLINE_ORB; else return OUTLINE_OTHER; } return OUTLINE_NONE; } #define AURA 180 array,AURA+1> aurac; int haveaura_cached; /** 0 = no aura, 1 = standard aura, 2 = Joukowsky aura */ EX int haveaura() { if(!(vid.aurastr>0 && !svg::in && (auraNOGL || vid.usingGL))) return 0; if(vrhr::active()) return 0; if(sphere && mdAzimuthalEqui()) return 0; if(among(pmodel, mdJoukowsky, mdJoukowskyInverted) && hyperbolic && pconf.model_transition < 1) return 2; if(among(pmodel, mdFisheye, mdFisheye2)) return 1; return pmodel == mdDisk && (!sphere || pconf.alpha > 10) && !euclid; } vector > auraspecials; int auramemo; EX void clearaura() { haveaura_cached = haveaura(); if(!haveaura_cached) return; for(int a=0; a>16)&255; aurac[r][1] += (col>>8)&255; aurac[r][2] += (col>>0)&255; } void sumaura(int v) { int auc[AURA]; for(int t=0; t AURA) vid.aurasmoothen = AURA; int SMO = vid.aurasmoothen; for(int t=0; t auravertices; #endif EX void drawaura() { DEBBI(DF_GRAPH, ("draw aura")); if(!haveaura()) return; if(vid.stereo_mode) return; double rad = current_display->radius; if(sphere && !mdAzimuthalEqui()) rad /= sqrt(pconf.alpha*pconf.alpha - 1); if(hyperbolic && pmodel == mdFisheye) { ld h = 1; h /= pconf.fisheye_param; ld nrad = h / sqrt(2 + h*h); rad *= nrad; } for(int v=0; v<4; v++) sumaura(v); for(auto& p: auraspecials) { int r = p.first; aurac[r][3] = auramemo; for(int k=0; k<3; k++) aurac[r][k] = (p.second >> (16-8*k)) & 255; } #if CAP_SDL || CAP_GL ld bak[3]; bak[0] = ((backcolor>>16)&255)/255.; bak[1] = ((backcolor>>8)&255)/255.; bak[2] = ((backcolor>>0)&255)/255.; #endif #if CAP_SDL if(!vid.usingGL) { SDL_LockSurface(s); for(int y=0; yxcenter) / rad; ld hy = (y * 1. - current_display->ycenter) / rad / pconf.stretch; if(!models::camera_straight) camrotate(hx, hy); ld fac = sqrt(hx*hx+hy*hy); if(fac < 1) continue; ld dd = log((fac - .99999) / .00001); ld cmul = 1 - dd/10.; if(cmul>1) cmul=1; if(cmul<0) cmul=0; ld alpha = AURA * atan2(hx,hy) / TAU; if(alpha<0) alpha += AURA; if(alpha >= AURA) alpha -= AURA; int rm = int(alpha); ld fr = alpha-rm; if(rm<0 || rm >= AURA) continue; color_t& p = qpixel(s, x, y); for(int c=0; c<3; c++) { ld c1 = aurac[rm][2-c] / (aurac[rm][3]+.1); ld c2 = aurac[rm+1][2-c] / (aurac[rm+1][3]+.1); const ld one = 1; part(p, c) = int(255 * min(one, bak[2-c] + cmul * ((c1 + fr * (c2-c1) - bak[2-c])))); } } SDL_UnlockSurface(s); return; } #endif #if CAP_GL float cx[AURA+1][11][5]; double facs[11] = {1, 1.01, 1.02, 1.04, 1.08, 1.70, 1.95, 1.5, 2, 6, 10}; double cmul[11] = {1, .8, .7, .6, .5, .16, .12, .08, .07, .06, 0}; double d2[11] = {0, 2, 4, 6.5, 7, 7.5, 8, 8.5, 9, 9.5, 10}; for(int d=0; d<11; d++) { double dd = d2[d]; cmul[d] = (1- dd/10.); facs[d] = .99999 + .00001 * exp(dd); } facs[10] = 10; cmul[1] = cmul[0]; bool inversion = pconf.alpha <= -1 || pmodel == mdJoukowsky; bool joukowsky = among(pmodel, mdJoukowskyInverted, mdJoukowsky) && hyperbolic && pconf.model_transition < 1; for(int r=0; r<=AURA; r++) for(int z=0; z<11; z++) { float rr = (TAU * r) / AURA; float rad0 = inversion ? rad / facs[z] : rad * facs[z]; int rm = r % AURA; ld c = cos(rr); ld s = sin(rr); if(joukowsky) { hyperpoint v(c, s, 0, 1); if(inversion) models::ori_to_scr(v); else models::scr_to_ori(v); ld c1 = v[0], s1 = v[1]; ld& mt = pconf.model_transition; ld mt2 = 1 - mt; ld m = sqrt(c1*c1 + s1*s1 / mt2 / mt2); m *= 2; if(inversion) rad0 /= m; else rad0 *= m; } ld x = rad0 * c; ld y = rad0 * s; if(!models::camera_straight) { hyperpoint p = hyperpoint(x, y, rad0, 1); p = rot_inverse(pconf.cam()) * p; x = p[0] * rad0 / p[2]; y = p[1] * rad0 / p[2]; } cx[r][z][0] = x; cx[r][z][1] = y * pconf.stretch; for(int u=0; u<3; u++) cx[r][z][u+2] = bak[u] + (aurac[rm][u] / (aurac[rm][3]+.1) - bak[u]) * cmul[z]; } auravertices.clear(); for(int r=0; rnext_shader_flags = GF_VARCOLOR; dynamicval m(pmodel, mdPixel); current_display->set_all(0, 0); glhr::id_modelview(); glhr::prepare(auravertices); glhr::set_depthtest(false); glDrawArrays(GL_TRIANGLES, 0, isize(auravertices)); #endif } // int fnt[100][7]; bool bugsNearby(cell *c, int dist = 2) { if(!(havewhat&HF_BUG)) return false; if(isBug(c)) return true; if(dist) for(int t=0; ttype; t++) if(c->move(t) && bugsNearby(c->move(t), dist-1)) return true; return false; } EX int celldistAltPlus(cell *c) { return 1000000 + celldistAlt(c); } EX bool drawstaratvec(double dx, double dy) { return dx*dx+dy*dy > .05; } ld wavefun(ld x) { return sin(x); /* x /= TAU; x -= (int) x; if(x > .5) return (x-.5) * 2; else return 0; */ } // does the current geometry allow nice duals EX bool has_nice_dual() { #if CAP_IRR if(IRREGULAR) return irr::bitruncations_performed > 0; #endif #if CAP_ARCM if(arcm::in()) return geosupport_football() >= 2; #endif if(bt::in()) return false; if(BITRUNCATED) return true; if(a4) return false; if((S7 & 1) == 0) return true; if(PURE) return false; #if CAP_GP return (gp::param.first + gp::param.second * 2) % 3 == 0; #else return false; #endif } // does the current geometry allow nice duals EX bool is_nice_dual(cell *c) { return c->land == laDual && has_nice_dual(); } EX bool use_swapped_duals() { return (euclid && !a4) || GOLDBERG; } EX bool openorsafe(cell *c) { #if CAP_COMPLEX2 return c->wall == waMineOpen || mine::marked_safe(c); #else return false; #endif } #define Dark(x) darkena(x,0,0xFF) EX color_t stdgridcolor = 0x202020FF; EX int gridcolor(cell *c1, cell *c2) { if(cmode & sm::DRAW && !mapeditor::drawing_tool) return Dark(forecolor); if(!c2) return 0x202020 >> darken; int rd1 = rosedist(c1), rd2 = rosedist(c2); if(rd1 != rd2) { int r = rd1+rd2; if(r == 1) return Dark(0x802020); if(r == 3) return Dark(0xC02020); if(r == 2) return Dark(0xF02020); } if((get_spatial_info(c1).deepland != laAsteroids && c2->land != laAsteroids) return Dark(0x808080); if(c1->land == laAlchemist && c2->land == laAlchemist && c1->wall != c2->wall && !c1->item && !c2->item) return Dark(0xC020C0); if((c1->land == laWhirlpool || c2->land == laWhirlpool) && (celldistAlt(c1) != celldistAlt(c2))) return Dark(0x2020A0); if(c1->land == laMinefield && c2->land == laMinefield && (openorsafe(c1) != openorsafe(c2))) return Dark(0xA0A0A0); if(!darken) return stdgridcolor; return Dark(0x202020); } #if CAP_SHAPES EX void pushdown(cell *c, int& q, const shiftmatrix &V, double down, bool rezoom, bool repriority) { #if MAXMDIM >= 4 if(GDIM == 3) { for(int i=q; ias_poly(); if(!pp) continue; auto& ptd = *pp; ptd.V = ptd.V * lzpush(+down); } return; } #endif // since we might be changing priorities, we have to make sure that we are sorting correctly if(down > 0 && repriority) { int qq = q+1; while(qq < isize(ptds)) if(qq > q && ptds[qq]->prio < ptds[qq-1]->prio) { swap(ptds[qq], ptds[qq-1]); qq--; } else qq++; } while(q < isize(ptds)) { auto pp = ptds[q++]->as_poly(); if(!pp) continue; auto& ptd = *pp; double z2; double z = zlevel(tC0(ptd.V.T)); double lev = geom3::factor_to_lev(z); double nlev = lev - down; double xyscale = rezoom ? geom3::scale_at_lev(lev) / geom3::scale_at_lev(nlev) : 1; z2 = geom3::lev_to_factor(nlev); double zscale = z2 / z; // xyscale = xyscale + (zscale-xyscale) * (1+sin(ticks / 1000.0)) / 2; ptd.V.T = xyzscale( V.T, xyscale*zscale, zscale) * z_inverse(V.T) * unshift(ptd.V, V.shift); if(!repriority) ; else if(nlev < -vid.lake_bottom-1e-3) { ptd.prio = PPR::DEEP_FALLANIM; if(c->wall != waChasm) ptd.color = 0; // disappear! } else if(nlev < -vid.lake_top-1e-3) ptd.prio = PPR::SHALLOW_FALLANIM; else if(nlev < 0) ptd.prio = PPR::FLOOR_FALLANIM; } } #endif bool allemptynear(cell *c) { if(c->wall) return false; forCellEx(c2, c) if(c2->wall) return false; return true; } EX bool bright; EX int canvasdark; // how much to darken EX int getfd(cell *c) { if(bright) return 0; if(among(c->land, laAlchemist, laHell, laVariant, laEclectic) && WDIM == 2 && GDIM == 3) return 0; switch(c->land) { case laCanvas: return min(2,max(0,canvasdark)); case laRedRock: case laReptile: return 0; case laSnakeNest: return realred(c->wall) ? 0 : 1; case laTerracotta: case laMercuryRiver: return (c->wall == waMercury && wmspatial) ? 0 : 1; case laKraken: case laDocks: case laBurial: case laIvoryTower: case laDungeon: case laMountain: case laEndorian: case laCaribbean: case laWhirlwind: case laRose: case laWarpSea: case laTortoise: case laDragon: case laHalloween: case laHunting: case laOcean: case laLivefjord: case laWhirlpool: case laAlchemist: case laIce: case laGraveyard: case laBlizzard: case laRlyeh: case laTemple: case laWineyard: case laDeadCaves: case laPalace: case laCA: case laDual: case laBrownian: return 1; case laVariant: if(isWateryOrBoat(c)) return 1; return 2; case laTrollheim: default: return 2; } } EX bool just_gmatrix; EX int colorhash(color_t i) { return (i * 0x471211 + i*i*0x124159 + i*i*i*0x982165) & 0xFFFFFF; } EX ld mousedist(shiftmatrix T) { if(GDIM == 2) return hdist(mouseh, tC0(T)); shiftpoint T1 = orthogonal_move_fol(T, cgi.FLOOR) * tile_center(); hyperpoint h1; applymodel(T1, h1); if(mouseaim_sensitivity) return sqhypot_d(2, h1) + (point_behind(T1) ? 1e10 : 0); h1 = h1 - point2((mousex - current_display->xcenter) / current_display->radius, (mousey - current_display->ycenter) / current_display->radius); return sqhypot_d(2, h1) + (point_behind(T1) ? 1e10 : 0); } EX vector> clipping_plane_sets; EX int noclipped; EX bool frustum_culling = true; EX ld threshold, xyz_threshold; EX bool clip_checked = false; EX bool other_stereo_mode() { return vid.stereo_mode != sOFF; } EX void make_clipping_planes() { #if MAXMDIM >= 4 clip_checked = false; if(!frustum_culling || PIU(sphere) || experimental || other_stereo_mode() || gproduct || embedded_plane) return; if(WDIM == 3 && pmodel == mdPerspective && !nonisotropic && !in_s2xe()) threshold = sin_auto(cgi.corner_bonus), xyz_threshold = 0, clip_checked = true; else if(pmodel == mdGeodesic && sn::in()) threshold = .6, xyz_threshold = 3, clip_checked = true; else if(pmodel == mdGeodesic && nil) threshold = 2, xyz_threshold = 3, clip_checked = true; else return; clipping_plane_sets.clear(); auto add_clipping_plane_txy = [] (transmatrix T, const transmatrix& nlp, ld x1, ld y1, ld x2, ld y2) { ld z1 = 1, z2 = 1; hyperpoint sx = point3(y1 * z2 - y2 * z1, z1 * x2 - z2 * x1, x1 * y2 - x2 * y1); sx /= hypot_d(3, sx); sx[3] = 0; sx = T * sx; if(nisot::local_perspective_used) sx = ortho_inverse(nlp) * sx; clipping_plane_sets.back().push_back(sx); }; #if CAP_VR auto add_clipping_plane_proj = [&] (transmatrix T, const transmatrix& nlp, const transmatrix& iproj, ld x1, ld y1, ld x2, ld y2) { hyperpoint h1 = iproj * point31(x1, y1, .5); hyperpoint h2 = iproj * point31(x2, y2, .5); h1 /= h1[2]; h2 /= h2[2]; add_clipping_plane_txy(T, nlp, h1[0], h1[1], h2[0], h2[1]); }; #endif auto clipping_planes_screen = [&] (const transmatrix& T, const transmatrix& nlp) { ld tx = current_display->tanfov; ld ty = tx * current_display->ysize / current_display->xsize; clipping_plane_sets.push_back({}); add_clipping_plane_txy(T, nlp, +tx, +ty, -tx, +ty); add_clipping_plane_txy(T, nlp, -tx, +ty, -tx, -ty); add_clipping_plane_txy(T, nlp, -tx, -ty, +tx, -ty); add_clipping_plane_txy(T, nlp, +tx, -ty, +tx, +ty); }; bool stdview = true; #if CAP_VR if(vrhr::active()) { for(auto p: vrhr::frusta) { if(p.screen) clipping_planes_screen(inverse(p.pre), p.nlp); else { auto iproj = inverse(p.proj); auto ipre = inverse(p.pre); clipping_plane_sets.push_back({}); add_clipping_plane_proj(ipre, p.nlp, iproj, 1, 1, 0, 1); add_clipping_plane_proj(ipre, p.nlp, iproj, 0, 1, 0, 0); add_clipping_plane_proj(ipre, p.nlp, iproj, 0, 0, 1, 0); add_clipping_plane_proj(ipre, p.nlp, iproj, 1, 0, 1, 1); } stdview = false; } } #endif if(stdview) clipping_planes_screen(Id, NLP); #endif } EX bool clipped_by(const hyperpoint& H, const vector& v) { for(auto& cpoint: v) if((H|cpoint) < -threshold) return true; return false; } EX bool clipped_by(const hyperpoint& H, const vector>& vv) { for(auto& cps: vv) if(!clipped_by(H, cps)) return false; return true; } bool celldrawer::cell_clipped() { if(!clip_checked) return false; hyperpoint H = unshift(tC0(V)); if(xyz_threshold && abs(H[0]) <= xyz_threshold && abs(H[1]) <= xyz_threshold && abs(H[2]) <= xyz_threshold) { noclipped++; return false; } if(clipped_by(H, clipping_plane_sets)) { drawcell_in_radar(); return true; } noclipped++; return false; } EX ld precise_width = .5; int grid_depth = 0; EX bool fat_edges = false; EX bool gridbelow; EX void gridline(const shiftmatrix& V1, const hyperpoint h1, const shiftmatrix& V2, const hyperpoint h2, color_t col, int prec) { transmatrix U2 = unshift(V2, V1.shift); int c1 = safe_classify_ideals(h1); int c2 = safe_classify_ideals(h2); ld d = (c1 <= 0 || c2 <= 0) ? 99 : hdist(V1.T*h1, U2*h2); #if MAXMDIM >= 4 if(GDIM == 3 && fat_edges) { if(nonisotropic) { auto nV1 = V1 * rgpushxto0(h1); hyperpoint U2 = inverse_shift(nV1, V2*rgpushxto0(h2)) * C0; auto& p = cgi.get_pipe_noniso(U2, vid.linewidth, ePipeEnd::ball); queuepoly(nV1, p, col); return; } shiftmatrix T = V1 * rgpushxto0(h1); transmatrix S = rspintox(inverse_shift(T, V2) * h2); transmatrix U = rspintoc(inverse_shift(T*S, shiftless(C0)), 2, 1); auto& p = queuepoly(T * S * U, cgi.get_pipe_iso(d, vid.linewidth, ePipeEnd::ball), col); p.intester = xpush0(d/2); return; } #endif while(d > precise_width && d < 100 && grid_depth < 10) { if(V1.shift != V2.shift || !eqmatrix(V1.T, V2.T, 1e-6)) { gridline(V1, h1, V1, inverse_shift(V1, V2) * h2, col, prec); return; } hyperpoint h; if(c1 <= 0 && c2 <= 0) { h = closest_to_zero(h1, h2); if(safe_classify_ideals(h) <= 0) return; h = normalize(h); } else if(c2 <= 0) { dynamicval dw(grid_depth, 99); for(ld a=0; a dw(grid_depth, 99); for(ld a=0; a= 4 if(WDIM == 2 && GDIM == 3) { ld eps = cgi.human_height/100; queueline(V1*orthogonal_move(h1,cgi.FLOOR+eps), V2*orthogonal_move(h2,cgi.FLOOR+eps), col, prec); queueline(V1*orthogonal_move(h1,cgi.WALL-eps), V2*orthogonal_move(h2,cgi.WALL-eps), col, prec); } else #endif queueline(V1*h1, V2*h2, col, prec, gridbelow ? PPR::FLOORd : PPR::LINE); } EX void gridline(const shiftmatrix& V, const hyperpoint h1, const hyperpoint h2, color_t col, int prec) { gridline(V, h1, V, h2, col, prec); } EX void set_detail_level(const shiftmatrix& V) { ld dist0 = hdist0(tC0(V)) - 1e-6; if(vid.use_smart_range) detaillevel = 2; else if(dist0 < vid.highdetail) detaillevel = 2; else if(dist0 < vid.middetail) detaillevel = 1; else detaillevel = 0; if((cmode & sm::NUMBER) && (dialog::editingDetail())) { color_t col = dist0 < vid.highdetail ? 0xFF80FF80 : dist0 >= vid.middetail ? 0xFFFF8080 : 0XFFFFFF80; queuepoly(V, cgi.shHeptaMarker, darkena(col & 0xFFFFFF, 0, 0xFF)); } } #if CAP_QUEUE EX void queuecircleat1(cell *c, const shiftmatrix& V, double rad, color_t col) { if(WDIM == 3) { dynamicval p(poly_outline, col); int ofs = currentmap->wall_offset(c); for(int i=0; itype; i++) { queuepolyat(V, cgi.shWireframe3D[ofs + i], 0, PPR::SUPERLINE); } return; } if(spatial_graphics || GDIM == 3) { vector corners(c->type+1); for(int i=0; itype; i++) corners[i] = get_corner_position(c, i, 3 / rad); corners[c->type] = corners[0]; for(int i=0; itype; i++) { queueline(V * orthogonal_move_fol(corners[i], cgi.FLOOR), V * orthogonal_move_fol(corners[i+1], cgi.FLOOR), col, 2, PPR::SUPERLINE); queueline(V * orthogonal_move_fol(corners[i], cgi.WALL), V * orthogonal_move_fol(corners[i+1], cgi.WALL), col, 2, PPR::SUPERLINE); queueline(V * orthogonal_move_fol(corners[i], cgi.FLOOR), V * orthogonal_move_fol(corners[i], cgi.WALL), col, 2, PPR::SUPERLINE); } return; } #if CAP_SHAPES if(vid.stereo_mode || sphere) { dynamicval p(poly_outline, col); queuepolyat(V * spintick(100), cgi.shGem[1], 0, PPR::LINE); return; } #endif queuecircle(V, rad, col); if(!wmspatial) return; auto si = get_spatial_info(c); if(si.top == SIDE::WALL) queuecircle(orthogonal_move_fol(V, cgi.WALL), rad, col); if(si.top == SIDE::RED1) queuecircle(orthogonal_move_fol(V, cgi.RED[1]), rad, col); if(si.top == SIDE::RED2) queuecircle(orthogonal_move_fol(V, cgi.RED[2]), rad, col); if(si.top == SIDE::RED3) queuecircle(orthogonal_move_fol(V, cgi.RED[3]), rad, col); if(si.top <= SIDE::WATERLEVEL) queuecircle(orthogonal_move_fol(V, cgi.WATERLEVEL), rad, col); } EX void queuecircleat(cell *c, double rad, color_t col) { if(!c) return; for (const shiftmatrix& V : hr::span_at(current_display->all_drawn_copies, c)) queuecircleat1(c, V, rad, col); } #endif #if ISMOBILE #define MOBON (clicked) #else #define MOBON true #endif EX cell *forwardcell() { #if CAP_VR if(vrhr::active()) { return vrhr::forward_cell; } #endif movedir md = vectodir(move_destination_vec(6)); cellwalker xc = cwt + md.d + wstep; return xc.at; } EX bool draw_centerover = true; EX bool should_draw_mouse_cursor() { if(!mousing || inHighQual) return false; if(outofmap(mouseh.h)) return false; if(rug::rugged && !rug::renderonce) return true; return false; } EX void drawMarkers() { shmup::draw_collision_debug(); if(!(cmode & sm::NORMAL)) return; if(should_draw_mouse_cursor()) { for(int i: player_indices()) { queueline(ggmatrix(playerpos(i)) * (WDIM == 2 && GDIM == 3 ? zpush0(cgi.WALL) : C0), mouseh, 0xFF00FF, grid_prec() + 1); } } callhooks(hooks_markers); #if CAP_SHAPES viewmat(); #endif #if CAP_QUEUE for(cell *c1: crush_now) queuecircleat(c1, .8, darkena(minf[moCrusher].color, 0, 0xFF)); #endif if(!inHighQual) { bool ok = !ISPANDORA || mousepressed; ignore(ok); #if CAP_QUEUE if(haveMount()) for (const shiftmatrix& V : hr::span_at(current_display->all_drawn_copies, dragon::target)) { queuestr(V, mapfontscale/100, "X", gradient(0, iinf[itOrbDomination].color, -1, sintick(dragon::whichturn == turncount ? 75 : 150), 1)); } #endif /* for(int i=0; i<12; i++) if(c->type == 5 && c->master == &dodecahedron[i]) queuestr(xc, yc, sc, 4*vid.fsize, s0+('A'+i), iinf[itOrbDomination].color); */ if(1) { using namespace yendor; if(yii < isize(yi) && !yi[yii].found) { cell *keycell = NULL; int last_i = 0; for(int i=0; icpdist <= get_sightrange_ambush()) { keycell = yi[yii].path[i]; last_i = i; } if(keycell) { for(int i = last_i+1; icpdist > 1 ? 0x00FFFF : 0xFF0000, 0, 0xFF)); } if(global_pushto && vid.drawmousecircle && ok && DEFAULTCONTROL && MOBON && WDIM == 2) { queuecircleat(global_pushto, .6, darkena(0xFFD500, 0, 0xFF)); } #endif #if CAP_SDLJOY && CAP_QUEUE if(joydir.d >= 0 && WDIM == 2) queuecircleat(cwt.at->modmove(joydir.d+cwt.spin), .78 - .02 * sintick(199), darkena(0x00FF00, 0, 0xFF)); #endif bool m = true; ignore(m); #if CAP_MODEL m = netgen::mode == 0; #endif #if CAP_QUEUE if(centerover && !playermoved && m && !anims::any_animation() && WDIM == 2 && draw_centerover) queuecircleat(centerover, .70 - .06 * sintick(200), darkena(int(175 + 25 * sintick(200)), 0, 0xFF)); if(multi::players > 1 || multi::alwaysuse) for(int i=0; i= 4 || (vid.axes == 1 && !mousing)) && !shmup::on && GDIM == 2) { if(multi::players == 1) { forCellIdAll(c2, d, cwt.at) if(gmatrix.count(cwt.at)) draw_movement_arrows(c2, unshift(gmatrix[cwt.at]) * currentmap->adj(cwt.at, d), d); } else if(multi::players > 1) for(int p=0; p= 4 || !drawstaratvec(multi::mdx[p], multi::mdy[p]))) forCellIdAll(c2, d, multi::player[p].at) if(gmatrix.count(cwt.at)) { multi::cpid = p; dynamicval ttm(cwtV, multi::whereis[p]); dynamicval tcw(cwt, multi::player[p]); draw_movement_arrows(c2, unshift(gmatrix[cwt.at]) * currentmap->adj(cwt.at, d), d); } } } if(GDIM == 3 && !inHighQual && !shmup::on && vid.axes3 && playermoved) { cell *c = forwardcell(); if(c) queuecircleat(c, .8, getcs().uicolor); } #endif if(mhybrid && !shmup::on) { using namespace sword; int& ang = sword::dir[multi::cpid].angle; ang %= sword_angles; int adj = 1 - ((sword_angles/cwt.at->type)&1); if(items[itOrbSword]) for (const shiftmatrix& V : hr::span_at(current_display->all_drawn_copies, cwt.at)) queuestr(V * spin(M_PI+(-adj-2*ang)*M_PI/sword_angles) * xpush0(cgi.sword_size), vid.fsize*2, "+", iinf[itOrbSword].color); if(items[itOrbSword2]) for (const shiftmatrix& V : hr::span_at(current_display->all_drawn_copies, cwt.at)) queuestr(V * spin((-adj-2*ang)*M_PI/sword_angles) * xpush0(-cgi.sword_size), vid.fsize*2, "+", iinf[itOrbSword2].color); } if(SWORDDIM == 3 && !shmup::on) { if(items[itOrbSword]) for (const shiftmatrix& V : hr::span_at(current_display->all_drawn_copies, cwt.at)) queuestr(V * sword::dir[multi::cpid].T * xpush0(cgi.sword_size), vid.fsize*2, "+", iinf[itOrbSword].color); if(items[itOrbSword2]) for (const shiftmatrix& V : hr::span_at(current_display->all_drawn_copies, cwt.at)) queuestr(V * sword::dir[multi::cpid].T * xpush0(-cgi.sword_size), vid.fsize*2, "+", iinf[itOrbSword2].color); } } monsterToSummon = moNone; orbToTarget = itNone; if(mouseover && targetclick) { multi::cpid = 0; orbToTarget = targetRangedOrb(mouseover, roCheck); #if CAP_QUEUE if(bow::fire_mode) { queuestr(mousex, mousey, 0, vid.fsize, "+", getcs().bowcolor >> 8); orbToTarget = itNone; } else if(orbToTarget == itOrbSummon) { monsterToSummon = summonedAt(mouseover); queuestr(mousex, mousey, 0, vid.fsize, s0+minf[monsterToSummon].glyph, minf[monsterToSummon].color); queuecircleat(mouseover, 0.6, darkena(minf[monsterToSummon].color, 0, 0xFF)); } else if(orbToTarget) { queuestr(mousex, mousey, 0, vid.fsize, "@", iinf[orbToTarget].color); queuecircleat(mouseover, 0.6, darkena(iinf[orbToTarget].color, 0, 0xFF)); } #endif #if CAP_SHAPES if(orbToTarget && rand() % 200 < ticks - lastt) { if(orbToTarget == itOrbDragon) drawFireParticles(mouseover, 2); else if(orbToTarget == itOrbSummon) { drawParticles(mouseover, iinf[orbToTarget].color, 1); drawParticles(mouseover, minf[monsterToSummon].color, 1); } else { drawParticles(mouseover, iinf[orbToTarget].color, 2); } } if(items[itOrbAir] && mouseover->cpdist > 1) { cell *c1 = mouseover; int dir = c1->monst == moVoidBeast ? -1 : 1; for(int it=0; it<10; it++) { int di; auto mib = blowoff_destination_dir(c1, di, dir); if(!mib.proper()) break; auto& c2 = mib.t; shiftmatrix T1 = ggmatrix(c1); shiftmatrix T2 = ggmatrix(c2); shiftmatrix T = T1 * lrspintox(inverse_shift(T1,T2*C0)) * xpush(hdist(T1*C0, T2*C0) * fractick(50, 0)); color_t aircol = (orbToTarget == itOrbAir ? 0x8080FF40 : 0x8080FF20); queuepoly(T, cgi.shDisk, aircol); c1 = c2; } } #endif } } EX bool allowIncreasedSight() { if(cheater || autocheat) return true; if(peace::on) return true; #if CAP_TOUR if(tour::on) return true; #endif if(randomPatternsMode) return true; if(racing::on) return true; if(quotient || !hyperbolic || arcm::in() || arb::in()) return true; if(WDIM == 3) return true; if(!canmove) return true; return false; } EX bool allowChangeRange() { if(cheater || peace::on || randomPatternsMode) return true; #if CAP_TOUR if(tour::on) return true; #endif if(racing::on) return true; if(arcm::in() || arb::in()) return true; if(WDIM == 3) return true; return false; } EX purehookset hooks_drawmap; EX transmatrix actual_view_transform; EX ld wall_radar(cell *c, transmatrix T, transmatrix LPe, ld max) { if(!in_perspective() || !vid.use_wall_radar) return max; transmatrix ori; if(gproduct) ori = ortho_inverse(LPe); ld step = max / 20; ld fixed_yshift = 0; for(int i=0; i<20; i++) { T = shift_object(T, ori, ztangent(-step), shift_method(smaWallRadar)); virtualRebase(c, T); color_t col; if(isWall3(c, col) || (WDIM == 2 && GDIM == 3 && tC0(T)[2] > cgi.FLOOR)) { T = shift_object(T, ori, ztangent(step), shift_method(smaWallRadar)); step /= 2; i = 17; if(step < 1e-3) break; } else fixed_yshift += step; } return fixed_yshift; } /** if this is set to ON, just transform non-isotropic spaces according to View, and apply NLP to view */ EX bool nonisotropic_weird_transforms; EX void decide_lpu() { nisot::local_perspective_used = gproduct; } EX void make_actual_view() { decide_lpu(); if(!nisot::local_perspective_used) NLP = Id; sphereflip = Id; sphere_flipped = flip_sphere(); if(sphere_flipped) sphereflip[LDIM][LDIM] = -1; actual_view_transform = sphereflip; if(vid.yshift && WDIM == 2) actual_view_transform = ypush(vid.yshift) * actual_view_transform; #if MAXMDIM >= 4 if(GDIM == 3) { ld max = WDIM == 2 ? vid.camera : vid.yshift; if(max) { transmatrix Start = view_inverse(actual_view_transform * View); ld d = wall_radar(centerover, Start, NLP, max); actual_view_transform = get_shift_view_of(ztangent(d), actual_view_transform * View) * view_inverse(View); } hyperpoint h = tC0(view_inverse(actual_view_transform * View)); camera_level = cgi.emb->get_logical_z(h); camera_sign = cgi.FLOOR > cgi.WALL; } if((nonisotropic || (hyperbolic && bt::in() && !nisot::geodesic_movement)) && !nonisotropic_weird_transforms) { transmatrix T = actual_view_transform * View; transmatrix T2 = eupush( tC0(view_inverse(T)) ); NLP = T * T2; actual_view_transform = ortho_inverse(NLP) * actual_view_transform; nisot::local_perspective_used = true; } #endif cgi.emb->set_radar_transform(); Viewbase = View; } EX shiftmatrix cview(ld base_shift IS(0)) { return shiftless(actual_view_transform * View, base_shift); } EX int point_direction; EX int through_wall(cell *c, hyperpoint at) { ld dist = hdist0(at); int nei = -1; for(int i=0; itype; i++) { ld dist1 = hdist0(currentmap->ray_iadj(c, i) * at); if(dist1 < dist) nei = i, dist = dist1; } return nei; } EX void precise_mouseover() { if(WDIM == 3 && (cmode & (sm::EDIT_INSIDE_WALLS | sm::EDIT_BEFORE_WALLS))) { transmatrix T = view_inverse(View); transmatrix ori = Id; if(gproduct) ori = ortho_inverse(NLP); ld step = 0.2; cell *c = centerover; for(int i=0; i<100; i++) { apply_shift_object(T, ori, ztangent(step)); int pd = through_wall(c, T * C0); if(pd != -1) { color_t col; cell *c1 = c->cmove(pd); if(isWall3(c1, col)) { mouseover = c; mouseover2 = c1; point_direction = pd; if(cmode & sm::EDIT_INSIDE_WALLS) { swap(mouseover, mouseover2); } else { point_direction =c->c.spin(pd); } return; } else { T = currentmap->iadj(c, pd) * T; c = c1; } } } } if(WDIM == 3) { mouseover2 = mouseover = centerover; ld best = HUGE_VAL; shiftpoint h = shiftless(direct_exp(lp_iapply(ztangent(0.01)))); point_direction = -1; shiftmatrix cov = ggmatrix(mouseover2); forCellIdEx(c1, i, mouseover2) { shiftpoint h1 = tC0(cov * currentmap->adj(mouseover2, i)); ld dist = geo_dist(h, h1) - geo_dist(shiftless(C0), h1); if(dist < best) mouseover = c1, best = dist, point_direction = i; } return; } if(!mouseover) return; if(GDIM == 3) return; cell *omouseover = mouseover; for(int loop = 0; loop < 10; loop++) { bool found = false; if(!gmatrix.count(mouseover)) return; hyperpoint r_mouseh = inverse_shift(gmatrix[mouseover], mouseh); for(int i=0; itype; i++) { hyperpoint h1 = get_corner_position(mouseover, gmod(i-1, mouseover->type)); hyperpoint h2 = get_corner_position(mouseover, i); if(det3(build_matrix(h1, h2, C0, C0)) * det3(build_matrix(h1, h2, r_mouseh, C0)) < 0) { mouseover2 = mouseover; mouseover = mouseover->move(i); found = true; break; } } if(!found) return; } // probably some error... just return the original mouseover = omouseover; } EX transmatrix Viewbase; EX bool no_wall_rendering; EX bool set_multi = false; EX hyperpoint multi_point; EX void center_multiplayer_map(const vector& hs) { hyperpoint h = Hypc; for(auto h1: hs) h += h1; h /= isize(hs); h = cgi.emb->normalize_flat(h); cwtV = shiftless(rgpushxto0(h)); if(isize(hs) == 2) { set_multi = true; multi_point = hs[1]; } } EX void drawthemap() { check_cgi(); cgi.require_shapes(); DEBBI(DF_GRAPH, ("draw the map")); last_firelimit = firelimit; firelimit = 0; make_clipping_planes(); current_display->radarpoints.clear(); current_display->radarlines.clear(); callhooks(hooks_drawmap); frameid++; cells_drawn = 0; cells_generated = 0; noclipped = 0; first_cell_to_draw = true; if(sightrange_bonus > 0 && !allowIncreasedSight()) sightrange_bonus = 0; swap(gmatrix0, gmatrix); gmatrix.clear(); current_display->all_drawn_copies.clear(); wmspatial = vid.wallmode == 4 || vid.wallmode == 5; wmescher = vid.wallmode == 3 || vid.wallmode == 5; wmplain = vid.wallmode == 2 || vid.wallmode == 4; wmascii = vid.wallmode == 0 || vid.wallmode == 6; wmascii3 = vid.wallmode == 6; wmblack = vid.wallmode == 1; mmitem = vid.monmode >= 1; mmmon = vid.monmode >= 2; mmspatial = vid.monmode >= 3; mmhigh = vid.highlightmode >= 1; if(hiliteclick) mmhigh = !mmhigh; spatial_graphics = wmspatial || mmspatial; spatial_graphics = spatial_graphics && GDIM == 2; #if CAP_RUG if(rug::rugged && !rug::spatial_rug) spatial_graphics = false; #endif if(non_spatial_model()) spatial_graphics = false; if(pmodel == mdDisk && abs(pconf.alpha) < 1e-6) spatial_graphics = false; if(!spatial_graphics) wmspatial = mmspatial = false; if(GDIM == 3) wmspatial = mmspatial = true; for(int m=0; mdraw_all(); drawWormSegments(); drawBlizzards(); drawArrowTraps(); precise_mouseover(); ivoryz = false; linepatterns::drawAll(); callhooks(hooks_frame); drawMarkers(); drawFlashes(); mapeditor::draw_dtshapes(); set_multi = false; if(multi::players > 1 && !shmup::on) { if(multi::split_screen) cwtV = multi::whereis[subscreens::current_player]; else if(multi::centerplayer != -1) cwtV = multi::whereis[multi::centerplayer]; else { vector pts; for(int p=0; ppat; else if(multi::players == 1) cwtV = shmup::pc[0]->pat; else if(multi::centerplayer != -1) cwtV = shmup::pc[multi::centerplayer]->pat; else { vector pts; for(int p=0; ppat * tile_center())); center_multiplayer_map(pts); } } #if CAP_SDL const sdl_keystate_type *keystate = SDL12_GetKeyState(NULL); lmouseover = mouseover; lmouseover_distant = lmouseover; bool useRangedOrb = (!(vid.shifttarget & 1) && haveRangedOrb() && lmouseover && lmouseover->cpdist > 1) || (keystate[SDL12(SDLK_RSHIFT, SDL_SCANCODE_RSHIFT)] | keystate[SDL12(SDLK_LSHIFT, SDL_SCANCODE_LSHIFT)]); if(!useRangedOrb && !(cmode & sm::MAP) && !(cmode & sm::DRAW) && DEFAULTCONTROL && !mouseout() && !dual::state) { dynamicval gs(gravity_state, gravity_state); calcMousedest(); cellwalker cw = cwt; bool f = flipplayer; items[itWarning]+=2; movepcto(mousedest.d, mousedest.subdir, true); items[itWarning] -= 2; if(cw.spin != cwt.spin) mirror::act(-mousedest.d, mirror::SPINSINGLE); cwt = cw; flipplayer = f; lmouseover = mousedest.d >= 0 ? cwt.at->modmove(cwt.spin + mousedest.d) : cwt.at; } #endif } // old style joystick control EX bool dronemode; purehookset hooks_calcparam; EX int corner_centering; EX bool permaside; EX bool old_center; EX ld min_scale = 1e-6; EX int forced_center_down = ISANDROID ? 2 : ISIOS ? 40 : 40; EX ld get_stereo_param() { if(among(vid.stereo_mode, sPanini, sStereographic)) return vid.stereo_param; return 0; } EX void calcparam() { DEBBI(DF_GRAPH, ("calc param")); auto cd = current_display; cd->xtop = vid.xres * cd->xmin; cd->ytop = vid.yres * cd->ymin; cd->xsize = vid.xres * (cd->xmax - cd->xmin); cd->ysize = vid.yres * (cd->ymax - cd->ymin); cd->xcenter = cd->xtop + cd->xsize / 2; cd->ycenter = cd->ytop + cd->ysize / 2; if(abs(pconf.scale) < min_scale) pconf.scale = 1; ld realradius = min(cd->xsize / 2, cd->ysize / 2); cd->scrsize = realradius; if(!inHighQual) cd->scrsize -= forced_center_down; current_display->sidescreen = permaside; if(vid.xres < vid.yres - 2 * vid.fsize && !inHighQual && (old_center || !in_perspective())) { cd->ycenter = lerp(vid.fsize + cd->scrsize, vid.yres - cd->scrsize - vid.fsize, .8); } else { bool ok = !vrhr::active(); if(vid.xres > vid.yres * 4/3+16 && (cmode & sm::SIDE) && ok && !((cmode & sm::MAYDARK) && centered_menus)) current_display->sidescreen = true; #if CAP_TOUR if(tour::on && (tour::slides[tour::currentslide].flags & tour::SIDESCREEN) && ok) current_display->sidescreen = true; #endif if((cmode & sm::DIALOG_OFFMAP) && !centered_menus && vid.xres > vid.yres * 11/10) current_display->sidescreen = true; if(current_display->sidescreen) cd->xcenter = vid.yres/2; } cd->radius = pconf.scale * cd->scrsize; if(GDIM == 3 && in_perspective()) cd->radius = cd->scrsize; realradius = min(realradius, cd->radius); ld aradius = sphere ? cd->radius / (pconf.alpha - 1) : cd->radius; #if MAXMDIM >= 4 if(euclid && hybrid::drawing_underlying) aradius *= 2.5; #endif if(dronemode) { cd->ycenter -= cd->radius; cd->ycenter += vid.fsize/2; cd->ycenter += vid.fsize/2; cd->radius *= 2; } if(corner_centering) { cd->ycenter = cd->ytop + cd->ysize - vid.fsize - aradius; if(corner_centering == 1) cd->xcenter = cd->xtop + vid.fsize + aradius; if(corner_centering == 2) cd->xcenter = cd->xtop + cd->xsize - vid.fsize - aradius; } cd->xcenter += cd->scrsize * pconf.xposition; cd->ycenter += cd->scrsize * pconf.yposition; ld fov = vid.fov * degree / 2; cd->tanfov = sin(fov) / (cos(fov) + get_stereo_param()); #if CAP_SDLTTF set_cfont(); #endif callhooks(hooks_calcparam); reset_projection(); } EX function wrap_drawfullmap = drawfullmap; bool force_sphere_outline = false; EX void drawfullmap() { DEBBI(DF_GRAPH, ("draw full map")); check_cgi(); cgi.require_shapes(); ptds.clear(); /* if(models::on) { char ch = 'A'; for(auto& v: history::v) { queuepoly(ggmatrix(v->base) * v->at, cgi.shTriangle, 0x306090C0); queuestr(ggmatrix(v->base) * v->at * C0, 10, s0+(ch++), 0xFF0000); } } */ #if CAP_QUEUE draw_boundary(0); draw_boundary(1); draw_model_elements(); #if MAXMDIM >= 4 && CAP_GL prepare_sky(); #endif #endif /* if(vid.wallmode < 2 && !euclid && !patterns::whichShape) { int ls = isize(lines); if(ISMOBILE) ls /= 10; for(int t=0; t> (darken+1)); } */ clearaura(); if(!nomap) drawthemap(); else callhooks(hooks_frame); if(!inHighQual) { if((cmode & sm::NORMAL) && !rug::rugged) { if(multi::players > 1) { auto bcwtV = cwtV; for(int i=0; idraw_all(); just_gmatrix = false; return; } stillscreen = false; auto gx = vid.xres; auto gy = vid.yres; if(dual::split([=] () { vid.xres = gx; vid.yres = gy; dual::in_subscreen([=] () { gamescreen(); }); })) { calcparam(); return; } calcparam(); darken = 0; if(!inHighQual && !vrhr::active()) { if((cmode & sm::MAYDARK) && !current_display->sidescreen) darken += menu_darkening; else if(cmode & sm::DARKEN) darken += menu_darkening; } if(vid.highlightmode == (hiliteclick ? 0 : 2)) darken++; if(darken >= 8) { emptyscreen(); return; } if(history::includeHistory) history::restore(); festive = festive_date && festive_option; old_shines = std::move(shines); shines.clear(); anims::apply(); #if CAP_RUG if(rug::rugged) { if(!nomap) rug::actDraw(); } else #endif wrap_drawfullmap(); anims::rollback(); if(history::includeHistory) history::restoreBack(); poly_outline = OUTLINE_DEFAULT; #if ISMOBILE buttonclicked = false; if((cmode & sm::NORMAL) && vid.stereo_mode != sLR && !inHighQual) { if(andmode == 0 && shmup::on) { using namespace shmupballs; calc(); drawCircle(xmove, yb, rad, OUTLINE_FORE); drawCircle(xmove, yb, rad/2, OUTLINE_FORE); drawCircle(xfire, yb, rad, 0xFF0000FF); drawCircle(xfire, yb, rad/2, 0xFF0000FF); } else { if(!haveMobileCompass()) displayabutton(-1, +1, andmode == 0 && useRangedOrb ? XLAT("FIRE") : andmode == 0 && WDIM == 3 && wclick ? XLAT("WAIT") : XLAT("MOVE"), andmode == 0 ? BTON : BTOFF); displayabutton(+1, +1, rug::rugged ? XLAT("RUG") :andmode == 1 ? XLAT("BACK") : GDIM == 3 ? XLAT("CAM") : XLAT("DRAG"), andmode == 1 ? BTON : BTOFF); } displayabutton(-1, -1, XLAT("INFO"), andmode == 12 ? BTON : BTOFF); displayabutton(+1, -1, XLAT("MENU"), andmode == 3 ? BTON : BTOFF); } #endif darken = 0; #if CAP_TEXTURE if(texture::config.tstate == texture::tsAdjusting) texture::config.drawRawTexture(); #endif #if CAP_VR vrhr::size_and_draw_ui_box(); #endif } EX void emptyscreen() { check_cgi(); cgi.require_shapes(); make_actual_view(); ptds.clear(); ray::in_use = false; drawqueue(); } EX int nohelp; EX bool no_find_player; EX void show_menu_button() { if(menu_format != "") displayButton(vid.xres-8, vid.yres-vid.fsize, eval_programmable_string(menu_format), 'v', 16); else if(nomenukey || ISMOBILE) ; #if CAP_TOUR else if(tour::on) displayButton(vid.xres-8, vid.yres-vid.fsize, XLAT("(ESC) tour menu"), SDLK_ESCAPE, 16); #endif else displayButton(vid.xres-8, vid.yres-vid.fsize, XLAT("(v) menu"), 'v', 16); } EX void normalscreen() { help = "@"; mouseovers = standard_help(); #if CAP_TOUR if(tour::on) mouseovers = (tour::slides[tour::currentslide].flags & tour::NOTITLE) ? "" : tour::tourhelp; #endif if(GDIM == 3 || !outofmap(mouseh.h)) getcstat = '-'; cmode = sm::NORMAL | sm::DOTOUR | sm::CENTER; if(viewdists && show_distance_lists) cmode |= sm::SIDE | sm::MAYDARK; gamescreen(); drawStats(); show_menu_button(); keyhandler = handleKeyNormal; dialog::key_actions.clear(); if(!playerfound && !anims::any_on() && !sphere && !no_find_player && mapeditor::drawplayer) displayButton(current_display->xcenter, current_display->ycenter, mousing ? XLAT("find the player") : XLAT("press SPACE to find the player"), ' ', 8); if(!mapeditor::drawplayer && playermoved && !no_find_player) displayButton(current_display->xcenter, current_display->ycenter, XLAT("move the camera with arrow keys and Home/End"), PSEUDOKEY_NOHINT, 8); describeMouseover(); } EX vector< function > screens = { normalscreen }; #if HDR template void pushScreen(const T& x) { screens.push_back(x); } inline void popScreen() { if(isize(screens)>1) screens.pop_back(); } inline void popScreenAll() { while(isize(screens)>1) popScreen(); } typedef void (*cfunction)(); #endif EX cfunction current_screen_cfunction() { auto tgt = screens.back().target(); if(!tgt) return nullptr; return *tgt; } #if HDR namespace sm { static constexpr int NORMAL = 1; static constexpr int MISSION = 2; static constexpr int HELP = 4; static constexpr int MAP = 8; static constexpr int DRAW = 16; static constexpr int NUMBER = 32; static constexpr int SHMUPCONFIG = 64; static constexpr int OVERVIEW = 128; static constexpr int SIDE = 256; static constexpr int DOTOUR = 512; static constexpr int CENTER = 1024; static constexpr int ZOOMABLE = 4096; static constexpr int TORUSCONFIG = 8192; static constexpr int MAYDARK = 16384; // use together with SIDE; if the screen is not wide or centered_menus is set, it will disable SIDE and instead darken the screen static constexpr int DIALOG_STRICT_X = 32768; // do not interpret dialog clicks outside of the X region static constexpr int EXPANSION = (1<<16); static constexpr int HEXEDIT = (1<<17); static constexpr int VR_MENU = (1<<18); // always show the menu in VR static constexpr int SHOWCURSOR = (1<<19); // despite MAP/DRAW always show the cursor, no panning static constexpr int PANNING = (1<<20); // smooth scrolling works static constexpr int DARKEN = (1<<21); // darken the game background static constexpr int NOSCR = (1<<22); // do not show the game background static constexpr int AUTO_VALUES = (1<<23); // automatic place for values static constexpr int NARROW_LINES = (1<<24); // do make the lines narrower if we needed to reduce width static constexpr int EDIT_BEFORE_WALLS = (1<<25); // mouseover targets before walls static constexpr int EDIT_INSIDE_WALLS = (1<<26); // mouseover targets inside walls static constexpr int DIALOG_WIDE = (1<<27); // make dialogs wide static constexpr int MOUSEAIM = (1<<28); // mouse aiming active here static constexpr int DIALOG_OFFMAP = (1<<29); // try hard to keep dialogs off the map } #endif EX int cmode; EX bool dont_display_minecount = false; EX color_t titlecolor; EX void drawscreen() { DEBBI(DF_GRAPH, ("drawscreen")); #if CAP_GL GLWRAP; #endif if(vid.xres == 0 || vid.yres == 0) return; calcparam(); // rug::setVidParam(); #if CAP_GL if(vid.usingGL) setGLProjection(); #endif #if CAP_XGD if(!vid.usingGL) { gdpush(5); gdpush(backcolor); } #endif #if CAP_VR vrhr::clear(); #endif #if CAP_SDL // SDL_LockSurface(s); // unsigned char *b = (unsigned char*) s->pixels; // int n = vid.xres * vid.yres * 4; // while(n) *b >>= 1, b++, n--; // memset(s->pixels, 0, vid.xres * vid.yres * 4); #if CAP_GL if(!vid.usingGL) #endif SDL_FillSurfaceRect(s, NULL, backcolor); #endif // displaynum(vx,100, 0, 24, 0xc0c0c0, celldist(cwt.at), ":"); lgetcstat = getcstat; getcstat = 0; inslider = false; mouseovers = " "; cmode = 0; reset_handlers(); if(!isize(screens)) pushScreen(normalscreen); screens.back()(); #if !ISMOBILE color_t col = linf[cwt.at->land].color; if(cwt.at->land == laRedRock) col = 0xC00000; if(titlecolor) col = titlecolor; if(nohelp != 1) { int size = vid.fsize; while(size > 3 && textwidth(size, mouseovers) > vid.xres) size--; poly_outline = (backcolor << 8) | 0xFF; displayfr(vid.xres/2, vid.fsize, 2, size, mouseovers, col, 8); } #endif drawmessages(); bool normal = cmode & sm::NORMAL; if((havewhat&HF_BUG) && darken == 0 && normal) if(hive::bugcount[0] || hive::bugcount[1] || hive::bugcount[2]) for(int k=0; k<3; k++) displayfr(vid.xres/2 + vid.fsize * 5 * (k-1), vid.fsize*2, 2, vid.fsize, its(hive::bugcount[k]), minf[moBug0+k].color, 8); bool minefieldNearby = false; int mines[MAXPLAYER], tmines=0; for(int p=0; pland == laMinefield) minefieldNearby = true; if(c2->wall == waMineMine) { bool ep = false; if(!ep) mines[p]++, tmines++; } } } if((minefieldNearby || tmines) && !items[itOrbAether] && !last_gravity_state && darken == 0 && normal) { string s; if(tmines > 9) tmines = 9; color_t col = minecolors[tmines]; if(tmines == 7) seenSevenMines = true; if(!dont_display_minecount) for(int p: player_indices()) displayfr(vid.xres * (p+.5) / numplayers(), current_display->ycenter - current_display->radius * 3/4, 2, vid.fsize, mines[p] > 7 ? its(mines[p]) : XLAT(minetexts[mines[p]]), minecolors[mines[p]], 8); if(minefieldNearby && !shmup::on && cwt.at->land != laMinefield && cwt.peek()->land != laMinefield && !dont_display_minecount) { displayfr(vid.xres/2, current_display->ycenter - current_display->radius * 3/4 - vid.fsize*3/2, 2, vid.fsize, XLAT("WARNING: you are entering a minefield!"), col, 8); } } // SDL_UnlockSurface(s); glflush(); DEBB(DF_GRAPH, ("swapbuffers")); #if CAP_VR vrhr::submit(); #endif #if CAP_SDL present_screen(); #endif #if CAP_VR vrhr::handoff(); #endif //printf("\ec"); } EX void restartGraph() { DEBBI(DF_INIT, ("restartGraph")); if(!autocheat) linepatterns::clearAll(); if(currentmap) resetview(); } auto graphcm = addHook(hooks_clearmemory, 0, [] () { DEBBI(DF_MEMORY, ("clear graph memory")); mouseover = centerover = lmouseover = NULL; gmatrix.clear(); gmatrix0.clear(); current_display->all_drawn_copies.clear(); clearAnimations(); }) + addHook(hooks_gamedata, 0, [] (gamedata* gd) { gd->store(mouseover); gd->store(lmouseover); gd->store(current_display->radar_transform); gd->store(actual_view_transform); }); EX void drawBug(const cellwalker& cw, color_t col) { #if CAP_SHAPES initquickqueue(); shiftmatrix V = ggmatrix(cw.at); if(cw.spin) V = V * ddspin180(cw.at, cw.spin); queuepoly(V, cgi.shBugBody, col); quickqueue(); #endif } EX bool inscreenrange_actual(cell *c) { if(GDIM == 3) return true; hyperpoint h1; applymodel(ggmatrix(c) * tile_center(), h1); if(invalid_point(h1)) return false; auto hscr = toscrcoord(h1); auto& x = hscr[0], y = hscr[1]; if(x > current_display->xtop + current_display->xsize) return false; if(x < current_display->xtop) return false; if(y > current_display->ytop + current_display->ysize) return false; if(y < current_display->ytop) return false; return true; } EX bool inscreenrange(cell *c) { if(sphere) return true; if(euclid) return celldistance(centerover, c) <= get_sightrange_ambush() && inscreenrange_actual(c); if(nonisotropic) return gmatrix.count(c); if(geometry == gCrystal344) return gmatrix.count(c) && inscreenrange_actual(c); auto hd = heptdistance(centerover, c); if(hd <= 1) return true; return hd <= 8 && inscreenrange_actual(c); } #if MAXMDIM >= 4 auto hooksw = addHook(hooks_swapdim, 100, [] { clearAnimations(); gmatrix.clear(); gmatrix0.clear(); current_display->all_drawn_copies.clear(); }); #endif }