mirror of
				https://github.com/zenorogue/hyperrogue.git
				synced 2025-10-31 05:52:59 +00:00 
			
		
		
		
	
		
			
				
	
	
		
			2100 lines
		
	
	
		
			59 KiB
		
	
	
	
		
			C++
		
	
	
	
	
	
			
		
		
	
	
			2100 lines
		
	
	
		
			59 KiB
		
	
	
	
		
			C++
		
	
	
	
	
	
| // 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<bool(int sym, int uni)> hooks_handleKey;
 | |
| EX hookset<bool(cell *c, const shiftmatrix& V)> 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<fontdata*> 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<array<int,4>,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<pair<int, int> > auraspecials; 
 | |
| 
 | |
| int auramemo;
 | |
| 
 | |
| EX void clearaura() {
 | |
|   haveaura_cached = haveaura();
 | |
|   if(!haveaura_cached) return;
 | |
|   for(int a=0; a<AURA; a++) for(int b=0; b<4; b++) 
 | |
|     aurac[a][b] = 0;
 | |
|   auraspecials.clear();
 | |
|   auramemo = 128 * 128 / vid.aurastr;
 | |
|   }
 | |
| 
 | |
| void apply_joukowsky_aura(shiftpoint& h) {
 | |
|   if(haveaura_cached == 2)  {
 | |
|     hyperpoint ret;
 | |
|     applymodel(h, ret);
 | |
|     h.h = ret;
 | |
|     }
 | |
|   if(nonisotropic) {
 | |
|     h.h = lp_apply(inverse_exp(h, pfNO_DISTANCE));
 | |
|     }
 | |
|   }
 | |
| 
 | |
| EX void addauraspecial(shiftpoint h, color_t col, int dir) {
 | |
|   if(!haveaura_cached) return;
 | |
|   apply_joukowsky_aura(h);
 | |
|   int r = int(2*AURA + dir + atan2(h[1], h[0]) * AURA / TAU) % AURA;
 | |
|   auraspecials.emplace_back(r, col);
 | |
|   }
 | |
| 
 | |
| EX void addaura(shiftpoint h, color_t col, int fd) {
 | |
|   if(!haveaura_cached) return;
 | |
|   apply_joukowsky_aura(h);
 | |
| 
 | |
|   int r = gmod(atan2(h[1], h[0]) * AURA / TAU, AURA);
 | |
|   aurac[r][3] += auramemo << fd;
 | |
|   col = darkened(col);
 | |
|   aurac[r][0] += (col>>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; t++) auc[t] = aurac[t][v];
 | |
|   int val = 0;
 | |
|   if(vid.aurasmoothen < 1) vid.aurasmoothen = 1;
 | |
|   if(vid.aurasmoothen > AURA) vid.aurasmoothen = AURA;
 | |
|   int SMO = vid.aurasmoothen;
 | |
|   for(int t=0; t<SMO; t++) val += auc[t];
 | |
|   for(int t=0; t<AURA; t++) {
 | |
|     int tt = (t + SMO/2) % AURA;
 | |
|     aurac[tt][v] = val;
 | |
|     val -= auc[t];
 | |
|     val += auc[(t+SMO) % AURA];
 | |
|     }  
 | |
|   aurac[AURA][v] = aurac[0][v];
 | |
|   }
 | |
| 
 | |
| #if CAP_GL
 | |
| vector<glhr::colored_vertex> 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; y<vid.yres; y++)
 | |
|     for(int x=0; x<vid.xres; x++) {
 | |
| 
 | |
|       ld hx = (x * 1. - current_display->xcenter) / 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; r<AURA; r++) for(int z=0;z<10;z++) {
 | |
|     for(int c=0; c<6; c++) {
 | |
|       int br = (c == 1 || c == 3 || c == 5) ? r+1 : r;
 | |
|       int bz = (c == 2 || c == 4 || c == 5) ? z+1 : z;
 | |
|       auravertices.emplace_back(
 | |
|         cx[br][bz][0], cx[br][bz][1], cx[br][bz][2], cx[br][bz][3], cx[br][bz][4]
 | |
|         );
 | |
|       }
 | |
|     }
 | |
|   glflush();
 | |
|   current_display->next_shader_flags = GF_VARCOLOR;
 | |
|   dynamicval<eModel> 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; t<c->type; 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).deep<SIDE::SHALLOW) != (get_spatial_info(c2).deep<SIDE::SHALLOW) && c1->land != 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; i<isize(ptds); i++) {
 | |
|       auto pp = ptds[q++]->as_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<vector<hyperpoint>> 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<hyperpoint>& v) {
 | |
|   for(auto& cpoint: v) if((H|cpoint) < -threshold) return true;
 | |
|   return false;
 | |
|   }
 | |
| 
 | |
| EX bool clipped_by(const hyperpoint& H, const vector<vector<hyperpoint>>& 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<int> dw(grid_depth, 99);
 | |
|       for(ld a=0; a<ideal_limit; a+=precise_width)
 | |
|         gridline(V1, towards_inf(h1, h2, a), V1, towards_inf(h1, h2, a+precise_width), col, prec);
 | |
|       return;
 | |
|       }
 | |
|     else if(c1 <= 0) {
 | |
|       dynamicval<int> dw(grid_depth, 99);
 | |
|       for(ld a=0; a<ideal_limit; a+=precise_width)
 | |
|         gridline(V1, towards_inf(h2, h1, a), V1, towards_inf(h2, h1, a+precise_width), col, prec);
 | |
|       return;
 | |
|       }
 | |
|     else h = midz(h1, h2);
 | |
|     grid_depth++;
 | |
|     gridline(V1, h1, V1, h, col, prec); 
 | |
|     gridline(V1, h, V1, h2, col, prec); 
 | |
|     grid_depth--;
 | |
|     return;
 | |
|     }
 | |
| #if MAXMDIM >= 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<color_t> p(poly_outline, col);
 | |
|     int ofs = currentmap->wall_offset(c);
 | |
|     for(int i=0; i<c->type; i++) {
 | |
|       queuepolyat(V, cgi.shWireframe3D[ofs + i], 0, PPR::SUPERLINE);
 | |
|       }
 | |
|     return;
 | |
|     }    
 | |
|   if(spatial_graphics || GDIM == 3) {
 | |
|     vector<hyperpoint> corners(c->type+1);
 | |
|     for(int i=0; i<c->type; i++) corners[i] = get_corner_position(c, i, 3 / rad);
 | |
|     corners[c->type] = corners[0];
 | |
|     for(int i=0; i<c->type; 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<color_t> 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; i<YDIST; i++)
 | |
|           if(yi[yii].path[i]->cpdist <= get_sightrange_ambush()) {
 | |
|             keycell = yi[yii].path[i]; last_i = i;
 | |
|             }
 | |
|         if(keycell) {
 | |
|           for(int i = last_i+1; i<YDIST; i++) {
 | |
|             cell *c = yi[yii].path[i];
 | |
|             if(inscreenrange(c))
 | |
|               keycell = c;
 | |
|             }
 | |
|           shiftpoint H = tC0(ggmatrix(keycell));
 | |
|           #if CAP_QUEUE
 | |
|           queue_goal_text(H, 2, "X", 0x10101 * int(128 + 100 * sintick(150)));
 | |
|           int cd = celldistance(yi[yii].key(), cwt.at);
 | |
|           if(cd == DISTANCE_UNKNOWN) for(int i2 = 0; i2<YDIST; i2++) {
 | |
|             int cd2 = celldistance(cwt.at, yi[yii].path[i2]);
 | |
|             if(cd2 != DISTANCE_UNKNOWN) {
 | |
|               cd = cd2 + (YDIST-1-i2);
 | |
|               }
 | |
|             }
 | |
|           queue_goal_text(H, 1, its(cd), 0x10101 * int(128 - 100 * sintick(150)));
 | |
|           #endif
 | |
|           addauraspecial(H, iinf[itOrbYendor].color, 0);
 | |
|           addradar(ggmatrix(keycell), 'X', iinf[itKey].color, kind_outline(itKey), true);
 | |
|           }
 | |
|         }
 | |
|       }
 | |
|   
 | |
|     #if CAP_RACING
 | |
|     racing::markers();
 | |
|     #endif
 | |
|   
 | |
|     #if CAP_QUEUE        
 | |
|     if(lmouseover && vid.drawmousecircle && ok && DEFAULTCONTROL && MOBON && WDIM == 2) {
 | |
|       cell *at = lmouseover;
 | |
|       #if CAP_VR
 | |
|       if(vrhr::active() && vrhr::forward_cell)
 | |
|         at = vrhr::forward_cell;
 | |
|       #endif
 | |
|       queuecircleat(at, .8, darkena(lmouseover->cpdist > 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<numplayers(); i++) {
 | |
|       multi::cpid = i;
 | |
|       if(multi::players == 1) multi::player[i] = cwt;
 | |
|       cell *ctgt = multi::multiPlayerTarget(i);
 | |
|       queuecircleat(ctgt, .40 - .06 * sintick(200, i / numplayers()), getcs().uicolor);
 | |
|       }
 | |
|     #endif
 | |
| 
 | |
|     // process mouse
 | |
|     #if CAP_SHAPES
 | |
|     if((vid.axes >= 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<multi::players; p++) {
 | |
|         if(multi::playerActive(p) && (vid.axes >= 4 || !drawstaratvec(multi::mdx[p], multi::mdy[p]))) 
 | |
|         forCellIdAll(c2, d, multi::player[p].at) if(gmatrix.count(cwt.at)) {
 | |
|           multi::cpid = p;
 | |
|           dynamicval<shiftmatrix> ttm(cwtV, multi::whereis[p]);
 | |
|           dynamicval<cellwalker> 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; i<c->type; 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; i<mouseover->type; 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<hyperpoint>& 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; m<motypes; m++) if(isPrincess(eMonster(m))) 
 | |
|     minf[m].name = princessgender() ? "Princess" : "Prince";
 | |
|   
 | |
|   #if CAP_RAY
 | |
|   ray::in_use = ray::requested();
 | |
|   #endif
 | |
|   no_wall_rendering = ray::in_use;
 | |
|   // ray::comparison_mode = true;
 | |
|   if(ray::comparison_mode) no_wall_rendering = false;
 | |
|     
 | |
|   iinf[itSavedPrincess].name = minf[moPrincess].name;
 | |
| 
 | |
|   for(int i=0; i<NUM_GS; i++) {
 | |
|     genderswitch_t& g = genderswitch[i];
 | |
|     if(g.gender != princessgender()) continue;
 | |
|     minf[g.m].help = g.desc;
 | |
|     minf[g.m].name = g.name;
 | |
|     }
 | |
| 
 | |
|   if(mapeditor::autochoose) mapeditor::ew = mapeditor::ewsearch;
 | |
|   mapeditor::ewsearch.dist = 1e30;
 | |
|   modist = 1e20; mouseover = NULL; 
 | |
|   modist2 = 1e20; mouseover2 = NULL; 
 | |
| 
 | |
|   compute_graphical_distance();
 | |
| 
 | |
|   for(int i=0; i<multi::players; i++) {
 | |
|     multi::ccdist[i] = 1e20; multi::ccat[i] = NULL;
 | |
|     }
 | |
| 
 | |
|   downseek.reset();
 | |
| 
 | |
|   #if ISMOBILE
 | |
|   mouseovers = XLAT("No info about this...");
 | |
|   #endif
 | |
|   if(mouseout() && !mousepan) 
 | |
|     modist = -5;
 | |
|   playerfound = false;
 | |
|   // playerfoundL = false;
 | |
|   // playerfoundR = false;
 | |
|   
 | |
|   arrowtraps.clear();
 | |
| 
 | |
|   make_actual_view();
 | |
|   currentmap->draw_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<hyperpoint> pts;
 | |
|       for(int p=0; p<multi::players; p++) if(multi::playerActive(p))
 | |
|         pts.push_back(unshift(multi::whereis[p] * tile_center()));
 | |
|       center_multiplayer_map(pts);
 | |
|       }
 | |
|     }
 | |
|   
 | |
|   if(shmup::on) {
 | |
|     if(multi::split_screen)
 | |
|       cwtV = shmup::pc[subscreens::current_player]->pat;
 | |
|     else if(multi::players == 1)
 | |
|       cwtV = shmup::pc[0]->pat;
 | |
|     else if(multi::centerplayer != -1)
 | |
|       cwtV = shmup::pc[multi::centerplayer]->pat;
 | |
|     else {
 | |
|       vector<hyperpoint> pts;
 | |
|       for(int p=0; p<multi::players; p++)
 | |
|         pts.push_back(unshift(shmup::pc[p]->pat * 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<eGravity> 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<void()> 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<ls; t++) queueline(View * lines[t].P1, View * lines[t].P2, lines[t].col >> (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; i<multi::players; i++) if(multi::playerActive(i))
 | |
|           cwtV = multi::whereis[i], multi::cpid = i, drawmovestar(multi::mdx[i], multi::mdy[i]);
 | |
|         cwtV = bcwtV;
 | |
|         }
 | |
|       else if(multi::alwaysuse)
 | |
|         drawmovestar(multi::mdx[0], multi::mdy[0]);
 | |
|       else 
 | |
|         drawmovestar(0, 0);
 | |
|       }
 | |
| #if CAP_EDIT
 | |
|     if(cmode & sm::DRAW) mapeditor::drawGrid();
 | |
| #endif
 | |
|     }
 | |
| 
 | |
|   drawaura();
 | |
|   #if CAP_QUEUE
 | |
|   drawqueue();
 | |
|   #endif
 | |
|   }
 | |
| 
 | |
| #if ISMOBILE
 | |
| extern bool wclick;
 | |
| #endif
 | |
| 
 | |
| EX bool just_refreshing;
 | |
| 
 | |
| EX int menu_darkening = 2;
 | |
| EX bool centered_menus = false;
 | |
| 
 | |
| EX string menu_format = "";
 | |
| 
 | |
| EX void gamescreen() {
 | |
| 
 | |
|   if(cmode & sm::NOSCR) {
 | |
|     stillscreen = true;
 | |
|     emptyscreen();
 | |
|     return;
 | |
|     }
 | |
| 
 | |
|   if(just_refreshing) return;
 | |
| 
 | |
|   if(subscreens::split([=] () {
 | |
|     calcparam();
 | |
|     compute_graphical_distance();
 | |
|     gamescreen();
 | |
|     })) {
 | |
|     if(racing::on) return;
 | |
|     // create the gmatrix
 | |
|     View = subscreens::player_displays[0].view_matrix;
 | |
|     centerover = subscreens::player_displays[0].precise_center;
 | |
|     just_gmatrix = true;
 | |
|     currentmap->draw_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<void()> > screens = { normalscreen };
 | |
| 
 | |
| #if HDR
 | |
| template<class T> 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<cfunction>();
 | |
|   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; p<numplayers(); p++) {
 | |
|     mines[p] = 0;
 | |
|     cell *c = playerpos(p);
 | |
|     if(!c) continue;
 | |
|     for(cell *c2: adj_minefield_cells(c)) {
 | |
|       if(c2->land == 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
 | |
| 
 | |
| }
 | 
