1 // Hyperbolic Rogue -- main graphics file
2 // Copyright (C) 2011-2019 Zeno Rogue, see 'hyper.cpp' for details
3 
4 /** \file graph.cpp
5  *  \brief Drawing cells, monsters, items, etc.
6  */
7 
8 #include "hyper.h"
9 namespace hr {
10 
11 EX int last_firelimit;
12 EX int firelimit;
13 
14 EX int inmirrorcount = 0;
15 
16 /** wall optimization: do not draw things beyond walls */
17 EX bool wallopt;
18 
in_wallopt()19 EX bool in_wallopt() { return wallopt || racing::on; }
20 
21 EX bool spatial_graphics;
22 EX bool wmspatial, wmescher, wmplain, wmblack, wmascii, wmascii3;
23 EX bool mmspatial, mmhigh, mmmon, mmitem;
24 
25 EX ld panini_alpha = 0;
26 EX ld stereo_alpha = 0;
27 
28 EX int detaillevel = 0;
29 
30 EX bool first_cell_to_draw = true;
31 
in_perspective()32 EX bool in_perspective() {
33   return among(pconf.model, mdPerspective, mdGeodesic);
34   }
35 
in_perspective_v()36 EX bool in_perspective_v() {
37   return among(vpconf.model, mdPerspective, mdGeodesic);
38   }
39 
hide_player()40 EX bool hide_player() {
41   return GDIM == 3 && playermoved && vid.yshift == 0 && vid.sspeed > -5 && in_perspective() && (first_cell_to_draw || elliptic) && (WDIM == 3 || vid.camera == 0) && !inmirrorcount
42 #if CAP_RACING
43    && !(racing::on && !racing::use_standard_centering() && !racing::player_relative)
44 #endif
45      ;
46   }
47 
48 template<class T>
49 class span {
50   T *begin_ = nullptr;
51   T *end_ = nullptr;
52 
53   public:
54   explicit span() = default;
span(T * p,int n)55   explicit span(T *p, int n) : begin_(p), end_(p + n) {}
begin() const56   T *begin() const { return begin_; }
end() const57   T *end() const { return end_; }
58   };
59 
60 template<class Map, class Key>
span_at(const Map & map,const Key & key)61 hr::span<const shiftmatrix> span_at(const Map& map, const Key& key) {
62   auto it = map.find(key);
63   return (it == map.end()) ? hr::span<const shiftmatrix>() : hr::span<const shiftmatrix>(it->second.data(), it->second.size());
64   }
65 
66 EX hookset<bool(int sym, int uni)> hooks_handleKey;
67 EX hookset<bool(cell *c, const shiftmatrix& V)> hooks_drawcell;
68 EX purehookset hooks_frame, hooks_markers;
69 
70 EX ld animation_factor = 1;
71 EX int animation_lcm = 0;
72 
73 EX ld ptick(int period, ld phase IS(0)) {
74   if(animation_lcm) animation_lcm = animation_lcm * (period / gcd(animation_lcm, period));
75   return (ticks * animation_factor) / period + phase * 2 * M_PI;
76   }
77 
78 EX ld fractick(int period, ld phase IS(0)) {
79   ld t = ptick(period, phase) / 2 / M_PI;
80   t -= floor(t);
81   if(t<0) t++;
82   return t;
83   }
84 
85 EX ld sintick(int period, ld phase IS(0)) {
86   return sin(ptick(period, phase));
87   }
88 
89 EX transmatrix spintick(int period, ld phase IS(0)) {
90   return spin(ptick(period, phase));
91   }
92 
93 #define WOLNIEJ 1
94 #define BTOFF 0x404040
95 #define BTON  0xC0C000
96 
97 // #define PANDORA
98 
99 int colorbar;
100 
101 EX bool inHighQual; // taking high quality screenshot
102 EX bool auraNOGL;    // aura without GL
103 
104 //
105 int axestate;
106 
107 EX int ticks;
108 EX int frameid;
109 
110 EX bool camelotcheat;
111 EX bool nomap;
112 
113 EX eItem orbToTarget;
114 EX eMonster monsterToSummon;
115 
116 EX int sightrange_bonus = 0;
117 
118 EX string mouseovers;
119 
120 EX int darken = 0;
121 
122 struct fallanim {
123   int t_mon, t_floor, pid;
124   eWall walltype;
125   eMonster m;
fallanimhr::fallanim126   fallanim() { t_floor = 0; t_mon = 0; pid = 0; walltype = waNone; }
127   };
128 
129 map<cell*, fallanim> fallanims;
130 
doHighlight()131 EX bool doHighlight() {
132   return mmhigh;
133   }
134 
135 int dlit;
136 
spina(cell * c,int dir)137 ld spina(cell *c, int dir) {
138   return 2 * M_PI * dir / c->type;
139   }
140 
141 /** @brief used to alternate colors depending on distance to something. In chessboard-patterned geometries, also use a third step */
142 
flip_dark(int f,int a0,int a1)143 EX int flip_dark(int f, int a0, int a1) {
144   if(geosupport_chessboard()) {
145     f = gmod(f, 3);
146     return a0 + (a1-a0) * f / 2;
147     }
148   else
149     return (f&1) ? a1 : a0;
150   }
151 
fc(int ph,color_t col,int z)152 color_t fc(int ph, color_t col, int z) {
153   if(items[itOrbFire]) col = darkena(firecolor(ph), 0, 0xFF);
154   if(items[itOrbAether]) col = (col &~0XFF) | (col&0xFF) / 2;
155   for(cell *pc: player_positions())
156     if(items[itOrbFish] && isWatery(pc) && z != 3) return watercolor(ph);
157   if(invismove)
158     col =
159       shmup::on ?
160         (col &~0XFF) | (int((col&0xFF) * .25))
161       : (col &~0XFF) | (int((col&0xFF) * (100+100*sintick(500)))/200);
162   return col;
163   }
164 
165 EX int lightat, safetyat;
drawLightning()166 EX void drawLightning() { lightat = ticks; }
drawSafety()167 EX void drawSafety() { safetyat = ticks; }
168 
drawShield(const shiftmatrix & V,eItem it)169 void drawShield(const shiftmatrix& V, eItem it) {
170 #if CAP_CURVE
171   float ds = ptick(300);
172   color_t col = iinf[it].color;
173   if(it == itOrbShield && items[itOrbTime] && !orbused[it])
174     col = (col & 0xFEFEFE) / 2;
175   if(sphere && cwt.at->land == laHalloween && !wmblack && !wmascii)
176     col = 0;
177   double d = it == itOrbShield ? cgi.hexf : cgi.hexf - .1;
178   int mt = sphere ? 7 : 5;
179 #if MAXMDIM >= 4
180   if(GDIM == 3)
181     queueball(V * zpush(cgi.GROIN1), cgi.human_height / 2, darkena(col, 0, 0xFF), itOrbShield);
182 #else
183   if(1) ;
184 #endif
185   else {
186     for(ld a=0; a<=cgi.S84*mt+1e-6; a+=pow(.5, vid.linequality))
187       curvepoint(xspinpush0(a * M_PI/cgi.S42, d + sin(ds + M_PI*2*a/4/mt)*.1));
188     queuecurve(V, darkena(col, 0, 0xFF), 0x8080808, PPR::LINE);
189     }
190 #endif
191   }
192 
drawSpeed(const shiftmatrix & V)193 void drawSpeed(const shiftmatrix& V) {
194 #if CAP_CURVE
195   ld ds = ptick(10);
196   color_t col = darkena(iinf[itOrbSpeed].color, 0, 0xFF);
197 #if MAXMDIM >= 4
198   if(GDIM == 3) queueball(V * zpush(cgi.GROIN1), cgi.human_height * 0.55, col, itOrbSpeed);
199   else
200 #endif
201   for(int b=0; b<cgi.S84; b+=cgi.S14) {
202     PRING(a)
203       curvepoint(xspinpush0((ds+b+a) * M_PI/cgi.S42, cgi.hexf*a/cgi.S84));
204     queuecurve(V, col, 0x8080808, PPR::LINE);
205     }
206 #endif
207   }
208 
drawSafety(const shiftmatrix & V,int ct)209 void drawSafety(const shiftmatrix& V, int ct) {
210 #if CAP_QUEUE
211   ld ds = ptick(50);
212   color_t col = darkena(iinf[itOrbSafety].color, 0, 0xFF);
213   #if MAXMDIM >= 4
214   if(GDIM == 3) {
215     queueball(V * zpush(cgi.GROIN1), 2*cgi.hexf, col, itOrbSafety);
216     return;
217     }
218   #endif
219   for(int a=0; a<ct; a++)
220     queueline(V*xspinpush0((ds+a*cgi.S84/ct) * M_PI/cgi.S42, 2*cgi.hexf), V*xspinpush0((ds+(a+(ct-1)/2)*cgi.S84/ct) * M_PI / cgi.S42, 2*cgi.hexf), col, vid.linequality);
221 #endif
222   }
223 
drawFlash(const shiftmatrix & V)224 void drawFlash(const shiftmatrix& V) {
225 #if CAP_CURVE
226   float ds = ptick(300);
227   color_t col = darkena(iinf[itOrbFlash].color, 0, 0xFF);
228   col &= ~1;
229   for(int u=0; u<5; u++) {
230     ld rad = cgi.hexf * (2.5 + .5 * sin(ds+u*.3));
231     #if MAXMDIM >= 4
232     if(GDIM == 3) {
233       queueball(V * zpush(cgi.GROIN1), rad, col, itOrbFlash);
234       }
235     #else
236     if(1) ;
237     #endif
238     else {
239       PRING(a) curvepoint(xspinpush0(a * M_PI / cgi.S42, rad));
240       queuecurve(V, col, 0x8080808, PPR::LINE);
241       }
242     }
243 #endif
244   }
245 
cheilevel(ld v)246 EX ld cheilevel(ld v) {
247   return cgi.FLOOR + (cgi.HEAD - cgi.FLOOR) * v;
248   }
249 
chei(const transmatrix V,int a,int b)250 EX transmatrix chei(const transmatrix V, int a, int b) {
251 #if MAXMDIM >= 4
252   if(GDIM == 2) return V;
253   return V * zpush(cheilevel((a+.5) / b));
254 #else
255   return V;
256 #endif
257   }
258 
chei(const shiftmatrix V,int a,int b)259 EX shiftmatrix chei(const shiftmatrix V, int a, int b) {
260 #if MAXMDIM >= 4
261   if(GDIM == 2) return V;
262   return V * zpush(cheilevel((a+.5) / b));
263 #else
264   return V;
265 #endif
266   }
267 
drawLove(const shiftmatrix & V,int hdir)268 void drawLove(const shiftmatrix& V, int hdir) {
269 #if CAP_CURVE
270   float ds = ptick(300);
271   color_t col = darkena(iinf[itOrbLove].color, 0, 0xFF);
272   col &= ~1;
273   for(int u=0; u<5; u++) {
274     shiftmatrix V1 = chei(V, u, 5);
275     PRING(a) {
276       double d = (1 + cos(a * M_PI/cgi.S42)) / 2;
277       double z = a; if(z>cgi.S42) z = cgi.S84-z;
278       if(z <= 10) d += (10-z) * (10-z) * (10-z) / 3000.;
279 
280       ld rad = cgi.hexf * (2.5 + .5 * sin(ds+u*.3)) * d;
281       curvepoint(xspinpush0((cgi.S42+hdir+a-1) * M_PI/cgi.S42, rad));
282       }
283     queuecurve(V1, col, 0x8080808, PPR::LINE);
284     }
285 #endif
286   }
287 
drawWinter(const shiftmatrix & V,ld hdir,color_t col)288 void drawWinter(const shiftmatrix& V, ld hdir, color_t col) {
289 #if CAP_QUEUE
290   float ds = ptick(300);
291   col = darkena(col, 0, 0xFF);
292   for(int u=0; u<20; u++) {
293     ld rad = sin(ds+u * 2 * M_PI / 20) * M_PI / S7;
294     shiftmatrix V1 = chei(V, u, 20);
295     queueline(V1*xspinpush0(M_PI+hdir+rad, cgi.hexf*.5), V1*xspinpush0(M_PI+hdir+rad, cgi.hexf*3), col, 2 + vid.linequality);
296     }
297 #endif
298   }
299 
drawLightning(const shiftmatrix & V)300 void drawLightning(const shiftmatrix& V) {
301 #if CAP_QUEUE
302   color_t col = darkena(iinf[itOrbLightning].color, 0, 0xFF);
303   for(int u=0; u<20; u++) {
304     ld leng = 0.5 / (0.1 + (rand() % 100) / 100.0);
305     ld rad = rand() % 1000;
306     shiftmatrix V1 = chei(V, u, 20);
307     queueline(V1*xspinpush0(rad, cgi.hexf*0.3), V1*xspinpush0(rad, cgi.hexf*leng), col, 2 + vid.linequality);
308     }
309 #endif
310   }
311 
drawCurse(const shiftmatrix & V,color_t col)312 void drawCurse(const shiftmatrix& V, color_t col) {
313 #if CAP_QUEUE
314   col = darkena(col, 0, 0xFF);
315   for(int u=0; u<20; u++) {
316     ld leng = 0.6 + 0.3 * randd();
317     ld rad = rand() % 1000;
318     shiftmatrix V1 = chei(V, u, 20);
319     queueline(V1*xspinpush0(rad, cgi.hexf*0.3), V1*xspinpush0(rad, cgi.hexf*leng), col, 2 + vid.linequality);
320     }
321 #endif
322   }
323 
324 #define UNTRANS (GDIM == 3 ? 0x000000FF : 0)
325 
drawPlayerEffects(const shiftmatrix & V,cell * c,bool onplayer)326 EX void drawPlayerEffects(const shiftmatrix& V, cell *c, bool onplayer) {
327   if(!onplayer && !items[itOrbEmpathy]) return;
328   if(items[itOrbShield] > (shmup::on ? 0 : ORBBASE)) drawShield(V, itOrbShield);
329   if(items[itOrbShell] > (shmup::on ? 0 : ORBBASE)) drawShield(V, itOrbShell);
330 
331   if(items[itOrbSpeed]) drawSpeed(V);
332   if(items[itCurseGluttony]) drawCurse(V, iinf[itCurseGluttony].color);
333   if(items[itCurseRepulsion]) drawCurse(V, iinf[itCurseRepulsion].color);
334 
335   if(onplayer && (items[itOrbSword] || items[itOrbSword2])) {
336     using namespace sword;
337 
338     if(shmup::on && SWORDDIM == 2) {
339 #if CAP_SHAPES
340       if(items[itOrbSword])
341         queuepoly(V*spin(shmup::pc[multi::cpid]->swordangle), (peace::on ? cgi.shMagicShovel : cgi.shMagicSword), darkena(iinf[itOrbSword].color, 0, 0xC0 + 0x30 * sintick(200)));
342 
343       if(items[itOrbSword2])
344         queuepoly(V*spin(shmup::pc[multi::cpid]->swordangle+M_PI), (peace::on ? cgi.shMagicShovel : cgi.shMagicSword), darkena(iinf[itOrbSword2].color, 0, 0xC0 + 0x30 * sintick(200)));
345 #endif
346       }
347 
348     else if(SWORDDIM == 3) {
349 #if CAP_SHAPES
350       shiftmatrix Vsword =
351         shmup::on ? V * shmup::swordmatrix[multi::cpid] * cspin(2, 0, M_PI/2)
352                   : gmatrix[c] * rgpushxto0(inverse_shift(gmatrix[c], tC0(V))) * sword::dir[multi::cpid].T;
353 
354       if(items[itOrbSword])
355         queuepoly(Vsword * cspin(1,2, ticks / 150.), (peace::on ? cgi.shMagicShovel : cgi.shMagicSword), darkena(iinf[itOrbSword].color, 0, 0xC0 + 0x30 * sintick(200)));
356 
357       if(items[itOrbSword2])
358         queuepoly(Vsword * pispin * cspin(1,2, ticks / 150.), (peace::on ? cgi.shMagicShovel : cgi.shMagicSword), darkena(iinf[itOrbSword2].color, 0, 0xC0 + 0x30 * sintick(200)));
359 #endif
360       }
361 
362     else {
363       int& ang = sword::dir[multi::cpid].angle;
364       ang %= sword::sword_angles;
365 
366 #if CAP_QUEUE || CAP_SHAPES
367       shiftmatrix Vnow = gmatrix[c] * rgpushxto0(inverse_shift(gmatrix[c], tC0(V))) * ddspin(c,0,M_PI);
368 #endif
369 
370       int adj = 1 - ((sword_angles/cwt.at->type)&1);
371 
372 #if CAP_QUEUE
373       if(!euclid && !hybri) for(int a=0; a<sword_angles; a++) {
374         if(a == ang && items[itOrbSword]) continue;
375         if((a+sword_angles/2)%sword_angles == ang && items[itOrbSword2]) continue;
376         bool longer = sword::pos2(cwt.at, a-1) != sword::pos2(cwt.at, a+1);
377         if(sword_angles > 48 && !longer) continue;
378         color_t col = darkena(0xC0C0C0, 0, 0xFF);
379         ld l0 = PURE ? 0.6 * cgi.scalefactor : longer ? 0.36 : 0.4;
380         ld l1 = PURE ? 0.7 * cgi.scalefactor : longer ? 0.44 : 0.42;
381 #if MAXMDIM >= 4
382         hyperpoint h0 = GDIM == 3 ? xpush(l0) * zpush(cgi.FLOOR - cgi.human_height/50) * C0 : xpush0(l0);
383         hyperpoint h1 = GDIM == 3 ? xpush(l1) * zpush(cgi.FLOOR - cgi.human_height/50) * C0 : xpush0(l1);
384 #else
385         hyperpoint h0 = xpush0(l0);
386         hyperpoint h1 = xpush0(l1);
387 #endif
388         shiftmatrix T = Vnow*spin((sword_angles + (-adj-2*a)) * M_PI / sword_angles);
389         queueline(T*h0, T*h1, col, 1, PPR::SUPERLINE);
390         }
391 #endif
392 
393 #if CAP_SHAPES
394       if(items[itOrbSword])
395         queuepoly(Vnow*spin(M_PI+(-adj-2*ang)*M_PI/sword_angles), (peace::on ? cgi.shMagicShovel : cgi.shMagicSword), darkena(iinf[itOrbSword].color, 0, 0x80 + 0x70 * sintick(200)));
396 
397       if(items[itOrbSword2])
398         queuepoly(Vnow*spin((-adj-2*ang)*M_PI/sword_angles), (peace::on ? cgi.shMagicShovel : cgi.shMagicSword), darkena(iinf[itOrbSword2].color, 0, 0x80 + 0x70 * sintick(200)));
399 #endif
400       }
401     }
402 
403   if(onplayer && items[itOrbSafety]) drawSafety(V, c->type);
404 
405   if(onplayer && items[itOrbFlash]) drawFlash(V);
406   if(onplayer && items[itOrbLove]) drawLove(V, 0); // displaydir(c, cwt.spin));
407 
408   if(items[itOrbWinter])
409     drawWinter(V, 0, iinf[itOrbWinter].color); // displaydir(c, cwt.spin));
410 
411   if(items[itOrbFire])
412     drawWinter(V, 0, iinf[itOrbFire].color); // displaydir(c, cwt.spin));
413 
414   if(items[itCurseWater])
415     drawWinter(V, 0, iinf[itCurseWater].color); // displaydir(c, cwt.spin));
416 
417   if(onplayer && items[itOrbLightning]) drawLightning(V);
418 
419   if(safetyat > 0) {
420     int tim = ticks - safetyat;
421     if(tim > 2500) safetyat = 0;
422     for(int u=tim; u<=2500; u++) {
423       if((u-tim)%250) continue;
424       ld rad = cgi.hexf * u / 250;
425       color_t col = darkena(iinf[itOrbSafety].color, 0, 0xFF);
426       PRING(a)
427         curvepoint(xspinpush0(a * M_PI / cgi.S42, rad));
428       queuecurve(V, col, 0, PPR::LINE);
429       }
430     }
431   }
432 
drawStunStars(const shiftmatrix & V,int t)433 void drawStunStars(const shiftmatrix& V, int t) {
434 #if CAP_SHAPES
435   for(int i=0; i<3*t; i++) {
436     shiftmatrix V2 = V * spin(M_PI * 2 * i / (3*t) + ptick(200));
437 #if MAXMDIM >= 4
438     if(GDIM == 3) V2 = V2 * zpush(cgi.HEAD);
439 #endif
440     queuepolyat(V2, cgi.shFlailBall, 0xFFFFFFFF, PPR::STUNSTARS);
441     }
442 #endif
443   }
444 
445 EX namespace tortoise {
446 
447   // small is 0 or 2
draw(const shiftmatrix & V,int bits,int small,int stuntime)448   void draw(const shiftmatrix& V, int bits, int small, int stuntime) {
449 
450 #if CAP_SHAPES
451     color_t eyecolor = getBit(bits, tfEyeHue) ? 0xFF0000 : 0xC0C0C0;
452     color_t shellcolor = getBit(bits, tfShellHue) ? 0x00C040 : 0xA06000;
453     color_t scutecolor = getBit(bits, tfScuteHue) ? 0x00C040 : 0xA06000;
454     color_t skincolor = getBit(bits, tfSkinHue) ? 0x00C040 : 0xA06000;
455     if(getBit(bits, tfShellSat)) shellcolor = gradient(shellcolor, 0xB0B0B0, 0, .5, 1);
456     if(getBit(bits, tfScuteSat)) scutecolor = gradient(scutecolor, 0xB0B0B0, 0, .5, 1);
457     if(getBit(bits, tfSkinSat)) skincolor = gradient(skincolor, 0xB0B0B0, 0, .5, 1);
458     if(getBit(bits, tfShellDark)) shellcolor = gradient(shellcolor, 0, 0, .5, 1);
459     if(getBit(bits, tfSkinDark)) skincolor = gradient(skincolor, 0, 0, .5, 1);
460 
461     if(bits < 0) {
462       skincolor = 0xC00060;
463       shellcolor = 0xFF00FF;
464       scutecolor = 0x6000C0;
465       eyecolor = 0xFFFFFF;
466       }
467 
468     for(int i=0; i<12; i++) {
469       color_t col =
470         i == 0 ? shellcolor:
471         i <  8 ? scutecolor :
472         skincolor;
473       int b = getBit(bits, i);
474       int d = darkena(col, 0, 0xFF);
475       if(i >= 1 && i <= 7) if(b) { d = darkena(col, 1, 0xFF); b = 0; }
476 
477       if(i >= 8 && i <= 11 && stuntime >= 3) continue;
478 
479       queuepoly(V, cgi.shTortoise[i][b+small], d);
480       if((i >= 5 && i <= 7) || (i >= 9 && i <= 10))
481         queuepoly(V * Mirror, cgi.shTortoise[i][b+small], d);
482 
483       if(i == 8) {
484         for(int k=0; k<stuntime; k++) {
485           eyecolor &= 0xFEFEFE;
486           eyecolor >>= 1;
487           }
488         queuepoly(V, cgi.shTortoise[12][b+small], darkena(eyecolor, 0, 0xFF));
489         queuepoly(V * Mirror, cgi.shTortoise[12][b+small], darkena(eyecolor, 0, 0xFF));
490         }
491       }
492 #endif
493     }
494 
getMatchColor(int bits)495   EX int getMatchColor(int bits) {
496     int mcol = 1;
497     double d = tortoise::getScent(bits);
498 
499     if(d > 0) mcol = 0xFFFFFF;
500     else if(d < 0) mcol = 0;
501 
502       int dd = 0xFF * (atan(fabs(d)/2) / (M_PI/2));
503 
504     return gradient(0x487830, mcol, 0, dd, 0xFF);
505     }
506   EX }
507 
footfun(double d)508 double footfun(double d) {
509   d -= floor(d);
510   return
511     d < .25 ? d :
512     d < .75 ? .5-d :
513     d-1;
514   }
515 
516 EX bool ivoryz;
517 
animallegs(const shiftmatrix & V,eMonster mo,color_t col,double footphase)518 void animallegs(const shiftmatrix& V, eMonster mo, color_t col, double footphase) {
519 #if CAP_SHAPES
520   footphase /= SCALE;
521 
522   bool dog = mo == moRunDog;
523   bool bug = mo == moBug0 || mo == moMetalBeast;
524 
525   if(bug) footphase *= 2.5;
526 
527   double rightfoot = footfun(footphase / .4 / 2) / 4 * 2;
528   double leftfoot = footfun(footphase / .4 / 2 - (bug ? .5 : dog ? .1 : .25)) / 4 * 2;
529 
530   if(bug) rightfoot /= 2.5, leftfoot /= 2.5;
531 
532   rightfoot *= SCALE;
533   leftfoot *= SCALE;
534 
535   if(!footphase) rightfoot = leftfoot = 0;
536 
537   hpcshape* sh[6][4] = {
538     {&cgi.shDogFrontPaw, &cgi.shDogRearPaw, &cgi.shDogFrontLeg, &cgi.shDogRearLeg},
539     {&cgi.shWolfFrontPaw, &cgi.shWolfRearPaw, &cgi.shWolfFrontLeg, &cgi.shWolfRearLeg},
540     {&cgi.shReptileFrontFoot, &cgi.shReptileRearFoot, &cgi.shReptileFrontLeg, &cgi.shReptileRearLeg},
541     {&cgi.shBugLeg, NULL, NULL, NULL},
542     {&cgi.shTrylobiteFrontClaw, &cgi.shTrylobiteRearClaw, &cgi.shTrylobiteFrontLeg, &cgi.shTrylobiteRearLeg},
543     {&cgi.shBullFrontHoof, &cgi.shBullRearHoof, &cgi.shBullFrontHoof, &cgi.shBullRearHoof},
544     };
545 
546   hpcshape **x = sh[mo == moRagingBull ? 5 : mo == moBug0 ? 3 : mo == moMetalBeast ? 4 : mo == moRunDog ? 0 : mo == moReptile ? 2 : 1];
547 
548 #if MAXMDIM >= 4
549   if(GDIM == 3) {
550     if(x[0]) queuepolyat(V * front_leg_move * cspin(0, 2, rightfoot / leg_length) * front_leg_move_inverse, *x[0], col, PPR::MONSTER_FOOT);
551     if(x[0]) queuepolyat(V * Mirror * front_leg_move * cspin(0, 2, leftfoot / leg_length) * front_leg_move_inverse, *x[0], col, PPR::MONSTER_FOOT);
552     if(x[1]) queuepolyat(V * rear_leg_move * cspin(0, 2, -rightfoot / leg_length) * rear_leg_move_inverse, *x[1], col, PPR::MONSTER_FOOT);
553     if(x[1]) queuepolyat(V * Mirror * rear_leg_move * cspin(0, 2, -leftfoot / leg_length) * rear_leg_move_inverse, *x[1], col, PPR::MONSTER_FOOT);
554     return;
555     }
556 #endif
557 
558   const shiftmatrix VL = mmscale(V, cgi.ALEG0);
559   const shiftmatrix VAML = mmscale(V, cgi.ALEG);
560 
561   if(x[0]) queuepolyat(VL * xpush(rightfoot), *x[0], col, PPR::MONSTER_FOOT);
562   if(x[0]) queuepolyat(VL * Mirror * xpush(leftfoot), *x[0], col, PPR::MONSTER_FOOT);
563   if(x[1]) queuepolyat(VL * xpush(-rightfoot), *x[1], col, PPR::MONSTER_FOOT);
564   if(x[1]) queuepolyat(VL * Mirror * xpush(-leftfoot), *x[1], col, PPR::MONSTER_FOOT);
565 
566   if(x[2]) queuepolyat(VAML * xpush(rightfoot/2), *x[2], col, PPR::MONSTER_FOOT);
567   if(x[2]) queuepolyat(VAML * Mirror * xpush(leftfoot/2), *x[2], col, PPR::MONSTER_FOOT);
568   if(x[3]) queuepolyat(VAML * xpush(-rightfoot/2), *x[3], col, PPR::MONSTER_FOOT);
569   if(x[3]) queuepolyat(VAML * Mirror * xpush(-leftfoot/2), *x[3], col, PPR::MONSTER_FOOT);
570 #endif
571   }
572 
573 EX bool noshadow;
574 
575 #if CAP_SHAPES
ShadowV(const shiftmatrix & V,const hpcshape & bp,PPR prio IS (PPR::MONSTER_SHADOW))576 EX void ShadowV(const shiftmatrix& V, const hpcshape& bp, PPR prio IS(PPR::MONSTER_SHADOW)) {
577   if(WDIM == 2 && GDIM == 3 && bp.shs != bp.she) {
578     auto& p = queuepolyat(V, bp, 0x18, PPR::TRANSPARENT_SHADOW);
579     p.outline = 0;
580     p.subprio = -100;
581     p.offset = bp.shs;
582     p.cnt = bp.she - bp.shs;
583     p.flags &=~ POLY_TRIANGLES;
584     p.tinf = NULL;
585     return;
586     }
587   if(mmspatial) {
588     if(model_needs_depth() || noshadow)
589       return; // shadows break the depth testing
590     dynamicval<color_t> p(poly_outline, OUTLINE_TRANS);
591     queuepolyat(V, bp, SHADOW_MON, prio);
592     }
593   }
594 #endif
595 
596 
597 #if CAP_SHAPES
otherbodyparts(const shiftmatrix & V,color_t col,eMonster who,double footphase)598 transmatrix otherbodyparts(const shiftmatrix& V, color_t col, eMonster who, double footphase) {
599 
600 #define VFOOT ((GDIM == 2 || hybri) ? V : mmscale(V, cgi.LEG0))
601 #define VLEG mmscale(V, cgi.LEG)
602 #define VGROIN mmscale(V, cgi.GROIN)
603 #define VBODY mmscale(V, cgi.BODY)
604 #define VBODY1 mmscale(V, cgi.BODY1)
605 #define VBODY2 mmscale(V, cgi.BODY2)
606 #define VBODY3 mmscale(V, cgi.BODY3)
607 #define VNECK mmscale(V, cgi.NECK)
608 #define VHEAD  mmscale(V, cgi.HEAD)
609 #define VHEAD1 mmscale(V, cgi.HEAD1)
610 #define VHEAD2 mmscale(V, cgi.HEAD2)
611 #define VHEAD3 mmscale(V, cgi.HEAD3)
612 
613 #define VALEGS V
614 #define VABODY mmscale(V, cgi.ABODY)
615 #define VAHEAD mmscale(V, cgi.AHEAD)
616 
617 #define VFISH V
618 #define VBIRD  ((GDIM == 3 || (where && bird_disruption(where))) ? (WDIM == 2 ? mmscale(V, cgi.BIRD) : V) : mmscale(V, cgi.BIRD + .05 * sintick(1000, static_cast<int>(reinterpret_cast<size_t>(where))/1000.)))
619 #define VGHOST  mmscale(V, cgi.GHOST)
620 
621 #define VSLIMEEYE  mscale(V, cgi.FLATEYE)
622 
623   // if(!mmspatial && !footphase && who != moSkeleton) return;
624 
625   footphase /= SCALE;
626   double rightfoot = footfun(footphase / .4 / 2.5) / 4 * 2.5 * SCALE;
627 
628   const double wobble = -1;
629 
630   // todo
631 
632   if(detaillevel >= 2 && GDIM == 2) {
633     shiftmatrix VL = mmscale(V, cgi.LEG1);
634     queuepoly(VL * xpush(rightfoot*3/4), cgi.shHumanLeg, col);
635     queuepoly(VL * Mirror * xpush(-rightfoot*3/4), cgi.shHumanLeg, col);
636     }
637 
638   if(GDIM == 2) {
639     shiftmatrix VL = mmscale(V, cgi.LEG);
640     queuepoly(VL * xpush(rightfoot/2), cgi.shHumanLeg, col);
641     queuepoly(VL * Mirror * xpush(-rightfoot/2), cgi.shHumanLeg, col);
642     }
643 
644   if(detaillevel >= 2 && GDIM == 2) {
645     shiftmatrix VL = mmscale(V, cgi.LEG3);
646     queuepoly(VL * xpush(rightfoot/4), cgi.shHumanLeg, col);
647     queuepoly(VL * Mirror * xpush(-rightfoot/4), cgi.shHumanLeg, col);
648     }
649 
650   shiftmatrix Tright, Tleft;
651 
652   if(GDIM == 2 || hybri) {
653     Tright = VFOOT * xpush(rightfoot);
654     Tleft = VFOOT * Mirror * xpush(-rightfoot);
655     }
656   #if MAXMDIM >= 4
657   else {
658     shiftmatrix V1 = V;
659     if(WDIM == 2) V1 = V1 * zpush(cgi.GROIN);
660     Tright = V1 * cspin(0, 2, rightfoot/ leg_length);
661     Tleft  = V1 * Mirror * cspin(2, 0, rightfoot / leg_length);
662     if(WDIM == 2) Tleft = Tleft * zpush(-cgi.GROIN), Tright = Tright * zpush(-cgi.GROIN);
663     }
664   #endif
665 
666   if(who == moWaterElemental && GDIM == 2) {
667     double fishtail = footfun(footphase / .4) / 4 * 1.5;
668     queuepoly(VFOOT * xpush(fishtail), cgi.shFishTail, watercolor(100));
669     }
670   else if(who == moSkeleton) {
671     queuepoly(Tright, cgi.shSkeletalFoot, col);
672     queuepoly(Tleft, cgi.shSkeletalFoot, col);
673     return spin(rightfoot * wobble);
674     }
675   else if(isTroll(who) || who == moMonkey || who == moYeti || who == moRatling || who == moRatlingAvenger || who == moGoblin) {
676     queuepoly(Tright, cgi.shYetiFoot, col);
677     queuepoly(Tleft, cgi.shYetiFoot, col);
678     }
679   else {
680     queuepoly(Tright, cgi.shHumanFoot, col);
681     queuepoly(Tleft, cgi.shHumanFoot, col);
682     }
683 
684   if(GDIM == 3 || !mmspatial) return spin(rightfoot * wobble);
685 
686   if(detaillevel >= 2 && who != moZombie)
687     queuepoly(mmscale(V, cgi.NECK1), cgi.shHumanNeck, col);
688   if(detaillevel >= 1) {
689     queuepoly(VGROIN, cgi.shHumanGroin, col);
690     if(who != moZombie) queuepoly(VNECK, cgi.shHumanNeck, col);
691     }
692   if(detaillevel >= 2) {
693     queuepoly(mmscale(V, cgi.GROIN1), cgi.shHumanGroin, col);
694     if(who != moZombie) queuepoly(mmscale(V, cgi.NECK3), cgi.shHumanNeck, col);
695     }
696 
697   return spin(rightfoot * wobble);
698   }
699 #endif
700 
drawstar(cell * c)701 EX bool drawstar(cell *c) {
702   for(int t=0; t<c->type; t++)
703     if(c->move(t) && c->move(t)->wall != waSulphur && c->move(t)->wall != waSulphurC &&
704      c->move(t)->wall != waBarrier)
705        return false;
706   return true;
707   }
708 
drawing_usershape_on(cell * c,mapeditor::eShapegroup sg)709 EX bool drawing_usershape_on(cell *c, mapeditor::eShapegroup sg) {
710 #if CAP_EDIT
711   return c && c == mapeditor::drawcell && mapeditor::drawcellShapeGroup() == sg;
712 #else
713   return false;
714 #endif
715   }
716 
kind_outline(eItem it)717 EX color_t kind_outline(eItem it) {
718   int k = itemclass(it);
719   if(k == IC_TREASURE)
720     return OUTLINE_TREASURE;
721   else if(k == IC_ORB)
722     return OUTLINE_ORB;
723   else
724     return OUTLINE_OTHER;
725   }
726 
face_the_player(const shiftmatrix V)727 EX shiftmatrix face_the_player(const shiftmatrix V) {
728   if(GDIM == 2) return V;
729   if(prod) return mscale(V, cos(ptick(750)) * cgi.plevel / 16);
730   if(hybri) return V * zpush(cos(ptick(750)) * cgi.plevel / 16);
731   transmatrix dummy; /* used only in prod anyways */
732   if(nonisotropic) return shiftless(spin_towards(unshift(V), dummy, C0, 2, 0));
733   #if CAP_VR
734   if(vrhr::enabled) {
735     shiftpoint h = tC0(V);
736     hyperpoint uh = unshift(h);
737     return shiftless(cspin(1, 2, 90*degree) * rspintox(cspin(2, 1, 90*degree) * uh) * xpush(hdist0(uh)) * cspin(0, 2, 90*degree) * cspin(1, 0, 90*degree));
738     }
739   #endif
740   return rgpushxto0(tC0(V));
741   }
742 
orbshape(eOrbshape s)743 EX hpcshape& orbshape(eOrbshape s) {
744   switch(s) {
745      case osLove: return cgi.shLoveRing;
746      case osRanged: return cgi.shTargetRing;
747      case osOffensive: return cgi.shSawRing;
748      case osFriend: return cgi.shPeaceRing;
749      case osUtility: return cgi.shGearRing;
750      case osPowerUtility: return cgi.shPowerGearRing;
751      case osDirectional: return cgi.shSpearRing;
752      case osWarping: return cgi.shHeptaRing;
753      case osFrog: return cgi.shFrogRing;
754      case osProtective: return cgi.shProtectiveRing;
755      case osTerraform: return cgi.shTerraRing;
756      case osMovement: return cgi.shMoveRing;
757      default: return cgi.shRing;
758      }
759   }
760 
queue_ring(const shiftmatrix & V,hpcshape & sh,color_t col,PPR p)761 void queue_ring(const shiftmatrix& V, hpcshape& sh, color_t col, PPR p) {
762   queuepolyat(V, sh, col, p).outline = 0;
763 
764   auto& p1 = queuepolyat(V, sh, col, p);
765   p1.cnt = cgi.orb_inner_ring;
766   p1.color = 0;
767 
768   auto& p2 = queuepolyat(V, sh, col, p);
769   p2.color = 0;
770   p2.offset += cgi.orb_inner_ring;
771   p2.cnt -= cgi.orb_inner_ring + 1;
772   }
773 
orb_auxiliary_color(eItem it)774 EX color_t orb_auxiliary_color(eItem it) {
775   if(it == itOrbFire) return firecolor(200);
776   if(it == itOrbFriend || it == itOrbDiscord) return 0xC0C0C0;
777   if(it == itOrbFrog) return 0xFF0000;
778   if(it == itOrbImpact) return 0xFF0000;
779   if(it == itOrbPhasing) return 0xFF0000;
780   if(it == itOrbDash) return 0xFF0000;
781   if(it == itOrbFreedom) return 0xC0FF00;
782   if(it == itOrbPlague) return 0x409040;
783   if(it == itOrbChaos) return 0xFF00FF;
784   if(it == itOrbAir) return 0xFFFFFF;
785   if(it == itOrbUndeath) return minf[moFriendlyGhost].color;
786   if(it == itOrbRecall) return 0x101010;
787   if(it == itOrbSlaying) return 0xFF0000;
788   return iinf[it].color;
789   }
790 
draw_ascii(const shiftmatrix & V,char glyph,color_t col,ld size)791 EX void draw_ascii(const shiftmatrix& V, char glyph, color_t col, ld size) {
792   string s = s0 + glyph;
793   int id = isize(ptds);
794   if(WDIM == 2 && GDIM == 3)
795     queuestrn(V * zpush(cgi.FLOOR - cgi.scalefactor * size / 4), size, s, darkenedby(col, darken), 0);
796   else
797     queuestrn(V, 1, s, darkenedby(col, darken), GDIM == 3 ? 0 : 2);
798   while(id < isize(ptds)) ptds[id++]->prio = PPR::MONSTER_BODY;
799   }
800 
drawItemType(eItem it,cell * c,const shiftmatrix & V,color_t icol,int pticks,bool hidden)801 EX bool drawItemType(eItem it, cell *c, const shiftmatrix& V, color_t icol, int pticks, bool hidden) {
802   if(!it) return false;
803   char xch = iinf[it].glyph;
804 
805 #if MAXMDIM >= 4
806   if(c && GDIM == 3)
807     addradar(V, xch, icol, kind_outline(it));
808 #endif
809 
810   if(WDIM == 3 && c == centerover && in_perspective() && hdist0(tC0(V)) < cgi.orbsize * 0.25) return false;
811 
812   if(!mmitem || !CAP_SHAPES) {
813     draw_ascii(V, iinf[it].glyph, icol, 1);
814     return true;
815     }
816 
817 #if CAP_SHAPES
818   auto sinptick = [c, pticks] (int period) { return c ? sintick(period) : sin(animation_factor * pticks / period);};
819   auto spinptick = [c, pticks] (int period, ld phase) { return c ? spintick(period, phase) : spin((animation_factor * pticks + phase) / period); };
820   int ct6 = c ? ctof(c) : 1;
821   hpcshape *xsh =
822     (it == itPirate || it == itKraken) ? &cgi.shPirateX :
823     (it == itBuggy || it == itBuggy2) ? &cgi.shPirateX :
824     it == itHolyGrail ? &cgi.shGrail :
825     isElementalShard(it) ? &cgi.shElementalShard :
826     (it == itBombEgg || it == itTrollEgg || it == itCursed) ? &cgi.shEgg :
827     it == itFrog ? &cgi.shDisk :
828     it == itHunting ? &cgi.shTriangle :
829     (it == itDodeca || it == itDice) ? &cgi.shDodeca :
830     xch == '*' ? &cgi.shGem[ct6] :
831     xch == '(' ? &cgi.shKnife :
832     it == itShard ? &cgi.shMFloor.b[0] :
833     it == itTreat ? &cgi.shTreat :
834     it == itSlime ? &cgi.shEgg :
835     xch == '%' ? &cgi.shDaisy : xch == '$' ? &cgi.shStar : xch == ';' ? &cgi.shTriangle :
836     xch == '!' ? &cgi.shTriangle : it == itBone ? &cgi.shNecro : it == itStatue ? &cgi.shStatue :
837     among(it, itIvory, itEclectic) ? &cgi.shFigurine :
838     xch == '?' ? &cgi.shBookCover :
839     it == itKey ? &cgi.shKey :
840     it == itRevolver ? &cgi.shGun :
841     NULL;
842 
843   if(c && doHighlight())
844     poly_outline = kind_outline(it);
845 
846   shiftmatrix Vit = V;
847   if(GDIM == 3 && WDIM == 2 && c && it != itBabyTortoise) Vit = mscale(V, cgi.STUFF);
848   if(c && prod)
849     Vit = mscale(Vit, sin(ptick(750)) * cgi.plevel / 4);
850   else if(c && sl2)
851     Vit = Vit * zpush(sin(ptick(750)) * cgi.plevel / 4);
852   else
853     if(GDIM == 3 && c && it != itBabyTortoise) Vit = face_the_player(Vit);
854   // V * cspin(0, 2, ptick(618, 0));
855 
856 #if CAP_SHAPES
857   if(mapeditor::drawUserShape(Vit, mapeditor::sgItem, it, darkena(icol, 0, 0xFF), c)) return true;
858 #endif
859 
860   if(c && history::includeHistory && history::infindhistory.count(c)) poly_outline = OUTLINE_DEAD;
861 
862   else if(it == itSavedPrincess) {
863     drawMonsterType(moPrincess, c, V, icol, 0, icol);
864     return true;
865     }
866 
867   else if(it == itStrongWind) {
868     queuepoly(Vit * spinptick(750, 0), cgi.shFan, darkena(icol, 0, 255));
869     }
870 
871   else if(it == itFatigue) {
872     queuepoly(Vit * spinptick(750, 0), cgi.shFan, darkena(icol, 0, 255));
873     }
874 
875   else if(it == itWarning) {
876     queuepoly(Vit * spinptick(750, 0), cgi.shTriangle, darkena(icol, 0, 255));
877     }
878 
879   else if(it == itBabyTortoise) {
880     int bits = c ? tortoise::babymap[c] : tortoise::last;
881     int over = c && c->monst == moTortoise;
882     tortoise::draw(Vit * spinptick(5000, 0) * ypush(cgi.crossf*.15), bits, over ? 4 : 2, 0);
883     // queuepoly(Vit, cgi.shHeptaMarker, darkena(tortoise::getMatchColor(bits), 0, 0xC0));
884     }
885 
886   else if(it == itCompass) {
887     shiftmatrix V2;
888     #if CAP_CRYSTAL
889     if(cryst) {
890       if(crystal::compass_probability <= 0) return true;
891       if(cwt.at->land == laCamelot && celldistAltRelative(cwt.at) < 0) crystal::used_compass_inside = true;
892       V2 = V * spin(crystal::compass_angle() + M_PI);
893       }
894     else
895     #endif
896     if(1) {
897       cell *c1 = c ? findcompass(c) : NULL;
898       if(c1) {
899         shiftmatrix P = ggmatrix(c1);
900         shiftpoint P1 = tC0(P);
901 
902         if(isPlayerOn(c)) {
903           queuestr(P1, 2*vid.fsize, "X", 0x10100 * int(128 + 100 * sintick(150)));
904     //      queuestr(V, 1, its(compassDist(c)), 0x10101 * int(128 - 100 * sin(ticks / 150.)), 1);
905           queuestr(P1, vid.fsize, its(-compassDist(c)), 0x10101 * int(128 - 100 * sintick(150)));
906           addauraspecial(P1, 0xFF0000, 0);
907           }
908 
909         V2 = V * rspintox(inverse_shift(V, P1));
910         }
911       else V2 = V;
912       }
913     if(GDIM == 3) {
914       queue_ring(Vit, cgi.shRing, 0xFFFFFFFF, PPR::ITEM);
915       if(WDIM == 2) V2 = mscale(V2, cgi.STUFF);
916       V2 = V2 * cspin(1, 2, M_PI * sintick(100) / 39);
917       queuepoly(V2, cgi.shCompass3, 0xFF0000FF);
918       queuepoly(V2 * pispin, cgi.shCompass3, 0x000000FF);
919       }
920     else {
921       if(c) V2 = V2 * spin(M_PI * sintick(100) / 30);
922       color_t hider = hidden ? 0xFFFFFF20 : 0xFFFFFFFF;
923       queuepoly(V2, cgi.shCompass1, 0xFF8080FF & hider);
924       queuepoly(V2, cgi.shCompass2, 0xFFFFFFFF & hider);
925       queuepoly(V2, cgi.shCompass3, 0xFF0000FF & hider);
926       queuepoly(V2 * pispin, cgi.shCompass3, 0x000000FF & hider);
927       }
928     xsh = NULL;
929     }
930 
931   else if(it == itPalace) {
932     #if MAXMDIM >= 4
933     if(GDIM == 3 && WDIM == 2) {
934       ld h = cgi.human_height;
935       dynamicval<qfloorinfo> qfi2(qfi, qfi);
936       shiftmatrix V2 = V * spin(ticks / 1500.);
937       /* divisors should be higher than in plate renderer */
938       qfi.fshape = &cgi.shMFloor2;
939       draw_shapevec(c, V2 * zpush(-h/30), qfi.fshape->levels[0], 0xFFD500FF, PPR::WALL);
940 
941       qfi.fshape = &cgi.shMFloor3;
942       draw_shapevec(c, V2 * zpush(-h/25), qfi.fshape->levels[0], darkena(icol, 0, 0xFF), PPR::WALL);
943 
944       qfi.fshape = &cgi.shMFloor4;
945       draw_shapevec(c, V2 * zpush(-h/20), qfi.fshape->levels[0], 0xFFD500FF, PPR::WALL);
946       }
947     else if(WDIM == 3 && c) {
948       ld h = cgi.human_height;
949       shiftmatrix V2 = Vit * spin(ticks / 1500.);
950       draw_floorshape(c, V2 * zpush(h/100), cgi.shMFloor3, 0xFFD500FF);
951       draw_floorshape(c, V2 * zpush(h/50), cgi.shMFloor4, darkena(icol, 0, 0xFF));
952       queuepoly(V2, cgi.shGem[ct6], 0xFFD500FF);
953       }
954     else if(WDIM == 3 && !c) {
955       queuepoly(Vit, cgi.shGem[ct6], 0xFFD500FF);
956       }
957     else
958     #endif
959     {
960       color_t hider = hidden ? 0xFFFFFF20 : 0xFFFFFFFF;
961       shiftmatrix V2 = Vit * spin(ticks / 1500.);
962       draw_floorshape(c, V2, cgi.shMFloor3, 0xFFD500FF & hider);
963       draw_floorshape(c, V2, cgi.shMFloor4, darkena(icol, 0, 0xFF) & hider);
964       queuepoly(V2, cgi.shGem[ct6], 0xFFD500FF & hider);
965       }
966     xsh = NULL;
967     }
968 
969   else if(it == itRose) {
970     for(int u=0; u<4; u++)
971       queuepoly(Vit * spinptick(1500, 0) * spin(2*M_PI / 3 / 4 * u), cgi.shRoseItem, darkena(icol, 0, hidden ? 0x30 : 0xA0));
972     }
973 
974   else if(it == itBarrow && c) {
975     for(int i = 0; i<c->landparam; i++)
976       queuepolyat(Vit * spin(2 * M_PI * i / c->landparam) * xpush(.15) * spinptick(1500, 0), *xsh, darkena(icol, 0, hidden ? 0x40 :
977         (highwall(c) && wmspatial) ? 0x60 : 0xFF),
978         PPR::HIDDEN);
979 
980 //      queuepoly(Vit*spin(M_PI+(1-2*ang)*2*M_PI/cgi.S84), cgi.shMagicSword, darkena(0xC00000, 0, 0x80 + 0x70 * sin(ticks / 200.0)));
981     }
982 
983   else if(xsh) {
984     if(it == itFireShard) icol = firecolor(100);
985     if(it == itWaterShard) icol = watercolor(100) >> 8;
986 
987     if(it == itZebra) icol = 0xFFFFFF;
988     if(it == itLotus) icol = 0x101010;
989     if(it == itSwitch) icol = minf[active_switch()].color;
990 
991     shiftmatrix V2 = Vit * spinptick(1500, 0);
992 
993     if(xsh == &cgi.shBookCover && mmitem) {
994       if(GDIM == 3)
995         queuepoly(V2 * cpush(2, 1e-3), cgi.shBook, 0x805020FF);
996       else
997         queuepoly(V2, cgi.shBook, 0x805020FF);
998       }
999 
1000     PPR pr = PPR::ITEM;
1001     int alpha = hidden ? (it == itKraken ? 0xC0 : 0x40) : 0xF0;
1002     if(c && c->wall == waIcewall) pr = PPR::HIDDEN, alpha = 0x80;
1003 
1004     queuepolyat(V2, *xsh, darkena(icol, 0, alpha), pr);
1005 
1006     if(it == itZebra) {
1007       shiftmatrix Vx = Vit * spinptick(1500, .5/(ct6+6));
1008       if(GDIM == 3)
1009         Vx = Vx * cpush(2, -1e-3);
1010       queuepolyat(Vx, *xsh, darkena(0x202020, 0, hidden ? 0x40 : 0xF0), PPR::ITEMb);
1011       }
1012     }
1013 
1014   else if(xch == 'o' || xch == 'c' || it == itInventory) {
1015     if(it == itOrbFire) icol = firecolor(100);
1016     PPR prio = PPR::ITEM;
1017     bool inice = c && c->wall == waIcewall;
1018     if(inice) prio = PPR::HIDDEN;
1019 
1020     color_t icol1 = icol;
1021     icol = orb_auxiliary_color(it);
1022     color_t col = darkena(icol, 0, int(0x80 + 0x70 * sinptick(300)));
1023 
1024     if(it == itOrbFish)
1025       queuepolyat(Vit * spinptick(1500, 0), cgi.shFishTail, col, PPR::ITEM_BELOW);
1026 
1027     if(xch == 'c')
1028       queuepolyat(Vit * spinptick(500, 0), cgi.shMoonDisk, darkena(0x801080, 0, hidden ? 0x20 : 0xC0), prio);
1029     else
1030       queuepolyat(Vit, cgi.shDisk, darkena(icol1, 0, inice ? 0x80 : hidden ? 0x20 : 0xC0), prio);
1031 
1032     queue_ring(Vit * spinptick(1500, 0), orbshape(iinf[it].orbshape), col, prio);
1033     }
1034 
1035   else {
1036     draw_ascii(V, xch, icol, 1);
1037     }
1038 
1039   return true;
1040 #endif
1041   }
1042 
1043 #if CAP_SHAPES
1044 color_t skincolor = 0xD0C080FF;
1045 
humanoid_eyes(const shiftmatrix & V,color_t ecol,color_t hcol=skincolor)1046 void humanoid_eyes(const shiftmatrix& V, color_t ecol, color_t hcol = skincolor) {
1047   if(GDIM == 3) {
1048     queuepoly(VHEAD, cgi.shPHeadOnly, hcol);
1049     queuepoly(VHEAD, cgi.shSkullEyes, ecol);
1050     }
1051   }
1052 
drawTerraWarrior(const shiftmatrix & V,int t,int hp,double footphase)1053 EX void drawTerraWarrior(const shiftmatrix& V, int t, int hp, double footphase) {
1054   if(!mmmon) {
1055     draw_ascii(V, 'T', gradient(0x202020, 0xFFFFFF, 0, t, 6), 1.5);
1056     return;
1057     }
1058   ShadowV(V, cgi.shPBody);
1059   color_t col = linf[laTerracotta].color;
1060   int bcol = darkena(false ? 0xC0B23E : col, 0, 0xFF);
1061   const transmatrix VBS = otherbodyparts(V, bcol, moDesertman, footphase);
1062   queuepoly(VBODY * VBS, cgi.shPBody, bcol);
1063   if(!peace::on) queuepoly(VBODY * VBS * Mirror, cgi.shPSword, darkena(0xC0C0C0, 0, 0xFF));
1064   queuepoly(VBODY1 * VBS, cgi.shTerraArmor1, darkena(t > 0 ? 0x4040FF : col, 0, 0xFF));
1065   if(hp >= 4) queuepoly(VBODY2 * VBS, cgi.shTerraArmor2, darkena(t > 1 ? 0xC00000 : col, 0, 0xFF));
1066   if(hp >= 2) queuepoly(VBODY3 * VBS, cgi.shTerraArmor3, darkena(t > 2 ? 0x612600 : col, 0, 0xFF));
1067   queuepoly(VHEAD, cgi.shTerraHead, darkena(t > 4 ? 0x202020 : t > 3 ? 0x504040 : col, 0, 0xFF));
1068   queuepoly(VHEAD1, cgi.shPFace, bcol);
1069   humanoid_eyes(V, 0x400000FF, darkena(col, 0, 0xFF));
1070   }
1071 #endif
1072 
drawPlayer(eMonster m,cell * where,const shiftmatrix & V,color_t col,double footphase,bool stop IS (false))1073 EX void drawPlayer(eMonster m, cell *where, const shiftmatrix& V, color_t col, double footphase, bool stop IS(false)) {
1074   charstyle& cs = getcs();
1075   #if CAP_COMPLEX2
1076   auto& knighted = camelot::knighted;
1077   #else
1078   const bool knighted = false;
1079   #endif
1080 
1081   if(GDIM == 3) {
1082     addradar(V, '@', cs.uicolor >> 8, 0xFF00FF00);
1083     }
1084 
1085   if(mapeditor::drawplayer && !mapeditor::drawUserShape(V, mapeditor::sgPlayer, cs.charid, cs.skincolor, where)) {
1086 
1087     if(cs.charid >= 8) {
1088       /* famililar */
1089       if(!mmspatial && !footphase) {
1090         if(stop) return;
1091         queuepoly(VALEGS, cgi.shWolfLegs, fc(150, cs.dresscolor, 4));
1092         }
1093       else {
1094         ShadowV(V, cgi.shWolfBody);
1095         if(stop) return;
1096         animallegs(VALEGS, moWolf, fc(500, cs.dresscolor, 4), footphase);
1097         }
1098       queuepoly(VABODY, cgi.shWolfBody, fc(0, cs.skincolor, 0));
1099       queuepoly(VAHEAD, cgi.shFamiliarHead, fc(500, cs.haircolor, 2));
1100       if(!shmup::on || shmup::curtime >= shmup::getPlayer()->nextshot) {
1101         color_t col = items[itOrbDiscord] ? watercolor(0) : fc(314, cs.eyecolor, 3);
1102         queuepoly(VAHEAD, cgi.shFamiliarEye, col);
1103         queuepoly(VAHEAD * Mirror, cgi.shFamiliarEye, col);
1104         }
1105 
1106       if(knighted)
1107         queuepoly(VABODY, cgi.shKnightCloak, darkena(cloakcolor(knighted), 1, 0xFF));
1108 
1109       if(tortoise::seek())
1110         tortoise::draw(VABODY, tortoise::seekbits, 4, 0);
1111       }
1112     else if(cs.charid >= 6) {
1113       /* dog */
1114       if(!mmspatial && !footphase) {
1115         if(stop) return;
1116         queuepoly(VABODY, cgi.shDogBody, fc(0, cs.skincolor, 0));
1117         }
1118       else {
1119         ShadowV(V, cgi.shDogTorso);
1120         if(stop) return;
1121         animallegs(VALEGS, moRunDog, fc(500, cs.dresscolor, 4), footphase);
1122         queuepoly(VABODY, cgi.shDogTorso, fc(0, cs.skincolor, 0));
1123         }
1124       queuepoly(VAHEAD, cgi.shDogHead, fc(150, cs.haircolor, 2));
1125 
1126       if(!shmup::on || shmup::curtime >= shmup::getPlayer()->nextshot) {
1127         color_t col = items[itOrbDiscord] ? watercolor(0) : fc(314, cs.eyecolor, 3);
1128         queuepoly(VAHEAD, cgi.shWolf1, col);
1129         queuepoly(VAHEAD, cgi.shWolf2, col);
1130         }
1131 
1132       color_t colnose = items[itOrbDiscord] ? watercolor(0) : fc(314, 0xFF, 3);
1133       queuepoly(VAHEAD, cgi.shWolf3, colnose);
1134 
1135       if(knighted)
1136         queuepoly(VABODY, cgi.shKnightCloak, darkena(cloakcolor(knighted), 1, 0xFF));
1137 
1138       if(tortoise::seek())
1139         tortoise::draw(VABODY, tortoise::seekbits, 4, 0);
1140       }
1141     else if(cs.charid >= 4) {
1142       /* cat */
1143       if(!mmspatial && !footphase) {
1144         if(stop) return;
1145         queuepoly(VALEGS, cgi.shCatLegs, fc(500, cs.dresscolor, 4));
1146         }
1147       else {
1148         ShadowV(V, cgi.shCatBody);
1149         if(stop) return;
1150         animallegs(VALEGS, moRunDog, fc(500, cs.dresscolor, 4), footphase);
1151         }
1152       queuepoly(VABODY, cgi.shCatBody, fc(0, cs.skincolor, 0));
1153       queuepoly(VAHEAD, cgi.shCatHead, fc(150, cs.haircolor, 2));
1154       if(!shmup::on || shmup::curtime >= shmup::getPlayer()->nextshot) {
1155         color_t col = items[itOrbDiscord] ? watercolor(0) : fc(314, cs.eyecolor, 3);
1156         queuepoly(VAHEAD * xpush(.04), cgi.shWolf1, col);
1157         queuepoly(VAHEAD * xpush(.04), cgi.shWolf2, col);
1158         }
1159 
1160       if(knighted)
1161         queuepoly(VABODY, cgi.shKnightCloak, darkena(cloakcolor(knighted), 1, 0xFF));
1162 
1163       if(tortoise::seek())
1164         tortoise::draw(VABODY, tortoise::seekbits, 4, 0);
1165       }
1166     else {
1167        /* human */
1168       ShadowV(V, (cs.charid&1) ? cgi.shFemaleBody : cgi.shPBody);
1169       if(stop) return;
1170 
1171       const transmatrix VBS = otherbodyparts(V, fc(0, cs.skincolor, 0), items[itOrbFish] ? moWaterElemental : moPlayer, footphase);
1172 
1173 
1174       queuepoly(VBODY * VBS, (cs.charid&1) ? cgi.shFemaleBody : cgi.shPBody, fc(0, cs.skincolor, 0));
1175 
1176       if(cs.charid&1)
1177         queuepoly(VBODY1 * VBS, cgi.shFemaleDress, fc(500, cs.dresscolor, 4));
1178 
1179       if(cs.charid == 2)
1180         queuepoly(VBODY2 * VBS, cgi.shPrinceDress,  fc(400, cs.dresscolor, 5));
1181       if(cs.charid == 3)
1182         queuepoly(VBODY2 * VBS, cgi.shPrincessDress,  fc(400, cs.dresscolor2, 5));
1183 
1184       if(items[itOrbSide3])
1185         queuepoly(VBODY * VBS, (cs.charid&1) ? cgi.shFerocityF : cgi.shFerocityM, fc(0, cs.skincolor, 0));
1186 
1187       if(items[itOrbHorns]) {
1188         queuepoly(VBODY * VBS, cgi.shBullHead, items[itOrbDiscord] ? watercolor(0) : 0xFF000030);
1189         queuepoly(VBODY * VBS, cgi.shBullHorn, items[itOrbDiscord] ? watercolor(0) : 0xFF000040);
1190         queuepoly(VBODY * VBS * Mirror, cgi.shBullHorn, items[itOrbDiscord] ? watercolor(0) : 0xFF000040);
1191         }
1192 
1193       if(items[itOrbSide1] && !shmup::on)
1194         queuepoly(VBODY * VBS * spin(-M_PI/24), cs.charid >= 2 ? cgi.shSabre : cgi.shPSword, fc(314, cs.swordcolor, 3)); // 3 not colored
1195 
1196       shiftmatrix VWPN = cs.lefthanded ? VBODY * VBS * Mirror : VBODY * VBS;
1197 
1198       if(peace::on) ;
1199       else if(racing::on) {
1200   #if CAP_RACING
1201         if(racing::trophy[multi::cpid])
1202           queuepoly(VWPN, cgi.shTrophy, racing::trophy[multi::cpid]);
1203   #endif
1204         }
1205       else if(items[itOrbThorns])
1206         queuepoly(VWPN, cgi.shHedgehogBladePlayer, items[itOrbDiscord] ? watercolor(0) : 0x00FF00FF);
1207       else if(!shmup::on && items[itOrbDiscord])
1208         queuepoly(VWPN, cs.charid >= 2 ? cgi.shSabre : cgi.shPSword, watercolor(0));
1209       else if(items[itRevolver])
1210         queuepoly(VWPN, cgi.shGunInHand, fc(314, cs.swordcolor, 3)); // 3 not colored
1211       else if(items[itOrbSlaying]) {
1212         queuepoly(VWPN, cgi.shFlailTrunk, fc(314, cs.swordcolor, 3));
1213         queuepoly(VWPN, cgi.shHammerHead, fc(314, cs.swordcolor, 3));
1214         }
1215       else if(items[itCurseWeakness]) {
1216         /* no weapon shown */
1217         }
1218       else if(!shmup::on)
1219         queuepoly(VWPN, cs.charid >= 2 ? cgi.shSabre : cgi.shPSword, fc(314, cs.swordcolor, 3)); // 3 not colored
1220       else if(shmup::curtime >= shmup::getPlayer()->nextshot)
1221         queuepoly(VWPN, cgi.shPKnife, fc(314, cs.swordcolor, 3)); // 3 not colored
1222 
1223       if(items[itOrbBeauty]) {
1224         if(cs.charid&1)
1225           queuepoly(VHEAD1, cgi.shFlowerHair, darkena(iinf[itOrbBeauty].color, 0, 0xFF));
1226         else
1227           queuepoly(VWPN, cgi.shFlowerHand, darkena(iinf[itOrbBeauty].color, 0, 0xFF));
1228         }
1229 
1230       if(where && where->land == laWildWest) {
1231         queuepoly(VHEAD1, cgi.shWestHat1, darkena(cs.swordcolor, 1, 0XFF));
1232         queuepoly(VHEAD2, cgi.shWestHat2, darkena(cs.swordcolor, 0, 0XFF));
1233         }
1234 
1235       if(cheater && !autocheat) {
1236         queuepoly(VHEAD1, (cs.charid&1) ? cgi.shGoatHead : cgi.shDemon, darkena(0xFFFF00, 0, 0xFF));
1237         // queuepoly(V, shHood, darkena(0xFF00, 1, 0xFF));
1238         }
1239       else {
1240         queuepoly(VHEAD, cgi.shPFace, fc(500, cs.skincolor, 1));
1241         queuepoly(VHEAD1, (cs.charid&1) ? cgi.shFemaleHair : cgi.shPHead, fc(150, cs.haircolor, 2));
1242         }
1243 
1244       humanoid_eyes(V, cs.eyecolor, cs.skincolor);
1245 
1246       if(knighted)
1247         queuepoly(VBODY * VBS, cgi.shKnightCloak, darkena(cloakcolor(knighted), 1, 0xFF));
1248 
1249       if(tortoise::seek())
1250         tortoise::draw(VBODY * VBS * ypush(-cgi.crossf*.25), tortoise::seekbits, 4, 0);
1251       }
1252     }
1253   }
1254 
1255 EX int wingphase(int period, int phase IS(0)) {
1256   ld t = fractick(period, phase);
1257   const int WINGS2 = WINGS * 2;
1258   int ti = int(t * WINGS2) % WINGS2;
1259   if(ti > WINGS) ti = WINGS2 - ti;
1260   return ti;
1261   }
1262 
wingmatrix(int period,int phase=0)1263 transmatrix wingmatrix(int period, int phase = 0) {
1264   ld t = fractick(period, phase) * 2 * M_PI;
1265   transmatrix Vwing = Id;
1266   Vwing[1][1] = .85 + .15 * sin(t);
1267   return Vwing;
1268   }
1269 
drawMimic(eMonster m,cell * where,const shiftmatrix & V,color_t col,double footphase)1270 void drawMimic(eMonster m, cell *where, const shiftmatrix& V, color_t col, double footphase) {
1271   charstyle& cs = getcs();
1272 
1273   if(mapeditor::drawUserShape(V, mapeditor::sgPlayer, cs.charid, darkena(col, 0, 0x80), where)) return;
1274 
1275   if(cs.charid >= 8) {
1276     queuepoly(VABODY, cgi.shWolfBody, darkena(col, 0, 0xC0));
1277     ShadowV(V, cgi.shWolfBody);
1278 
1279     if(mmspatial || footphase)
1280       animallegs(VALEGS, moWolf, darkena(col, 0, 0xC0), footphase);
1281     else
1282       queuepoly(VABODY, cgi.shWolfLegs, darkena(col, 0, 0xC0));
1283 
1284     queuepoly(VABODY, cgi.shFamiliarHead, darkena(col, 0, 0xC0));
1285     queuepoly(VAHEAD, cgi.shFamiliarEye, darkena(col, 0, 0xC0));
1286     queuepoly(VAHEAD * Mirror, cgi.shFamiliarEye, darkena(col, 0, 0xC0));
1287     }
1288   else if(cs.charid >= 6) {
1289     ShadowV(V, cgi.shDogBody);
1290     queuepoly(VAHEAD, cgi.shDogHead, darkena(col, 0, 0xC0));
1291     if(mmspatial || footphase) {
1292       animallegs(VALEGS, moRunDog, darkena(col, 0, 0xC0), footphase);
1293       queuepoly(VABODY, cgi.shDogTorso, darkena(col, 0, 0xC0));
1294       }
1295     else
1296       queuepoly(VABODY, cgi.shDogBody, darkena(col, 0, 0xC0));
1297     queuepoly(VABODY, cgi.shWolf1, darkena(col, 1, 0xC0));
1298     queuepoly(VABODY, cgi.shWolf2, darkena(col, 1, 0xC0));
1299     queuepoly(VABODY, cgi.shWolf3, darkena(col, 1, 0xC0));
1300     }
1301   else if(cs.charid >= 4) {
1302     ShadowV(V, cgi.shCatBody);
1303     queuepoly(VABODY, cgi.shCatBody, darkena(col, 0, 0xC0));
1304     queuepoly(VAHEAD, cgi.shCatHead, darkena(col, 0, 0xC0));
1305     if(mmspatial || footphase)
1306       animallegs(VALEGS, moRunDog, darkena(col, 0, 0xC0), footphase);
1307     else
1308       queuepoly(VALEGS, cgi.shCatLegs, darkena(col, 0, 0xC0));
1309     queuepoly(VAHEAD * xpush(.04), cgi.shWolf1, darkena(col, 1, 0xC0));
1310     queuepoly(VAHEAD * xpush(.04), cgi.shWolf2, darkena(col, 1, 0xC0));
1311     }
1312   else {
1313     const transmatrix VBS = otherbodyparts(V, darkena(col, 0, 0x40), m, footphase);
1314     queuepoly(VBODY * VBS, (cs.charid&1) ? cgi.shFemaleBody : cgi.shPBody,  darkena(col, 0, 0X80));
1315 
1316     if(!shmup::on) {
1317       bool emp = items[itOrbEmpathy] && m != moShadow;
1318       if(items[itOrbThorns] && emp)
1319         queuepoly(VBODY * VBS, cgi.shHedgehogBladePlayer, darkena(col, 0, 0x40));
1320       if(items[itOrbSide1] && !shmup::on)
1321         queuepoly(VBODY * VBS * spin(-M_PI/24), cs.charid >= 2 ? cgi.shSabre : cgi.shPSword, darkena(col, 0, 0x40));
1322       if(items[itOrbSide3] && emp)
1323         queuepoly(VBODY * VBS, (cs.charid&1) ? cgi.shFerocityF : cgi.shFerocityM, darkena(col, 0, 0x40));
1324 
1325       queuepoly(VBODY * VBS, (cs.charid >= 2 ? cgi.shSabre : cgi.shPSword), darkena(col, 0, 0XC0));
1326       }
1327     else if(!where || shmup::curtime >= shmup::getPlayer()->nextshot)
1328       queuepoly(VBODY * VBS, cgi.shPKnife, darkena(col, 0, 0XC0));
1329 
1330     #if CAP_COMPLEX2
1331     if(camelot::knighted)
1332       queuepoly(VBODY3 * VBS, cgi.shKnightCloak, darkena(col, 1, 0xC0));
1333     #endif
1334 
1335     queuepoly(VHEAD1, (cs.charid&1) ? cgi.shFemaleHair : cgi.shPHead,  darkena(col, 1, 0XC0));
1336     queuepoly(VHEAD, cgi.shPFace,  darkena(col, 0, 0XC0));
1337     if(cs.charid&1)
1338       queuepoly(VBODY1 * VBS, cgi.shFemaleDress,  darkena(col, 1, 0XC0));
1339     if(cs.charid == 2)
1340       queuepoly(VBODY2 * VBS, cgi.shPrinceDress,  darkena(col, 1, 0XC0));
1341     if(cs.charid == 3)
1342       queuepoly(VBODY2 * VBS, cgi.shPrincessDress,  darkena(col, 1, 0XC0));
1343 
1344     humanoid_eyes(V,  0xFF, darkena(col, 0, 0x40));
1345     }
1346   }
1347 
drawMonsterType(eMonster m,cell * where,const shiftmatrix & V1,color_t col,double footphase,color_t asciicol)1348 EX bool drawMonsterType(eMonster m, cell *where, const shiftmatrix& V1, color_t col, double footphase, color_t asciicol) {
1349 
1350 #if MAXMDIM >= 4
1351   if(GDIM == 3 && asciicol != NOCOLOR) {
1352     addradar(V1, minf[m].glyph, asciicol, isFriendly(m) ? 0x00FF00FF : 0xFF0000FF);
1353     }
1354 #endif
1355 
1356   char xch = minf[m].glyph;
1357 
1358   shiftmatrix V = V1;
1359   if(WDIM == 3 && (classflag(m) & CF_FACE_UP) && where && !hybri) V = V1 * cspin(0, 2, M_PI/2);
1360 
1361   #if CAP_SHAPES
1362   if(among(m, moTortoise, moWorldTurtle) && where && where->stuntime >= 3)
1363     drawStunStars(V, where->stuntime-2);
1364   else if (among(m, moTortoise, moWorldTurtle, moMutant) || m == moPlayer || (where && !where->stuntime)) ;
1365   else if(where && !(isMetalBeast(m) && where->stuntime == 1))
1366     drawStunStars(V, where->stuntime);
1367 
1368   if(mapeditor::drawUserShape(V, mapeditor::sgMonster, m, darkena(col, 0, 0xFF), where))
1369     return true;
1370   #endif
1371 
1372   if(!mmmon || !CAP_SHAPES) {
1373     draw_ascii(V1, xch, asciicol, 1.5);
1374     return true;
1375     }
1376 
1377 #if CAP_SHAPES
1378   switch(m) {
1379     case moTortoise: {
1380       int bits = where ? tortoise::getb(where) : tortoise::last;
1381       tortoise::draw(V, bits, 0, where ? where->stuntime : 0);
1382       if(tortoise::seek() && !tortoise::diff(bits) && where)
1383         queue_ring(V, cgi.shRing, darkena(0xFFFFFF, 0, 0x80 + 0x70 * sintick(200)), PPR::ITEM);
1384       return true;
1385       }
1386 
1387     case moWorldTurtle: {
1388       tortoise::draw(V, -1, 0, where ? where->stuntime : 0);
1389       return true;
1390       }
1391 
1392     case moPlayer:
1393       drawPlayer(m, where, V, col, footphase);
1394       return true;
1395 
1396     case moMimic: case moShadow: case moIllusion:
1397       drawMimic(m, where, V, col, footphase);
1398       return true;
1399 
1400     case moBullet:
1401       ShadowV(V, cgi.shKnife);
1402       queuepoly(VBODY * spin(-M_PI/4), cgi.shKnife, getcs().swordcolor);
1403       return true;
1404 
1405     case moKnight: case moKnightMoved: {
1406       ShadowV(V, cgi.shPBody);
1407       const transmatrix VBS = otherbodyparts(V, darkena(0xC0C0A0, 0, 0xC0), m, footphase);
1408       queuepoly(VBODY * VBS, cgi.shPBody, darkena(0xC0C0A0, 0, 0xC0));
1409       if(!racing::on)
1410         queuepoly(VBODY * VBS, cgi.shPSword, darkena(0xFFFF00, 0, 0xFF));
1411       queuepoly(VBODY1 * VBS, cgi.shKnightArmor, darkena(0xD0D0D0, 1, 0xFF));
1412       color_t col;
1413       if(!eubinary && where && where->master->alt)
1414         col = cloakcolor(roundTableRadius(where));
1415       else
1416         col = cloakcolor(newRoundTableRadius());
1417       queuepoly(VBODY2 * VBS, cgi.shKnightCloak, darkena(col, 1, 0xFF));
1418       queuepoly(VHEAD1, cgi.shPHead, darkena(0x703800, 1, 0XFF));
1419       queuepoly(VHEAD, cgi.shPFace, darkena(0xC0C0A0, 0, 0XFF));
1420       humanoid_eyes(V, 0x000000FF);
1421       return true;
1422       }
1423 
1424     case moGolem: case moGolemMoved: {
1425       ShadowV(V, cgi.shPBody);
1426       const transmatrix VBS = otherbodyparts(V, darkena(col, 1, 0XC0), m, footphase);
1427       queuepoly(VBODY * VBS, cgi.shPBody, darkena(col, 0, 0XC0));
1428       queuepoly(VHEAD, cgi.shGolemhead, darkena(col, 1, 0XFF));
1429       humanoid_eyes(V, 0xC0C000FF, darkena(col, 0, 0xFF));
1430       return true;
1431       }
1432 
1433     case moEvilGolem: case moIceGolem: {
1434       const shiftmatrix VBS = VBODY * otherbodyparts(V, darkena(col, 2, 0xC0), m, footphase);
1435       ShadowV(V, cgi.shPBody);
1436       queuepoly(VBS, cgi.shPBody, darkena(col, 0, 0XC0));
1437       queuepoly(VHEAD, cgi.shGolemhead, darkena(col, 1, 0XFF));
1438       humanoid_eyes(V, 0xFF0000FF, darkena(col, 0, 0xFF));
1439       return true;
1440       }
1441 
1442     case moFalsePrincess: case moRoseLady: case moRoseBeauty: {
1443       princess:
1444       bool girl = princessgender() == GEN_F;
1445       bool evil = !isPrincess(m);
1446 
1447       int facecolor = evil ? 0xC0B090FF : 0xD0C080FF;
1448 
1449       ShadowV(V, girl ? cgi.shFemaleBody : cgi.shPBody);
1450       const transmatrix VBS = otherbodyparts(V, facecolor, m, footphase);
1451       queuepoly(VBODY * VBS, girl ? cgi.shFemaleBody : cgi.shPBody, facecolor);
1452 
1453       if(m == moPrincessArmed)
1454         queuepoly(VBODY * VBS * Mirror, vid.cs.charid < 2 ? cgi.shSabre : cgi.shPSword, 0xFFFFFFFF);
1455 
1456       if((m == moFalsePrincess || m == moRoseBeauty) && where && where->cpdist == 1)
1457         queuepoly(VBODY * VBS, cgi.shPKnife, 0xFFFFFFFF);
1458 
1459       if(m == moRoseLady) {
1460         queuepoly(VBODY * VBS, cgi.shPKnife, 0xFFFFFFFF);
1461         queuepoly(VBODY * VBS * Mirror, cgi.shPKnife, 0xFFFFFFFF);
1462         }
1463 
1464       if(girl) {
1465         queuepoly(VBODY1 * VBS, cgi.shFemaleDress,  evil ? 0xC000C0FF : 0x00C000FF);
1466         if(vid.cs.charid < 2)
1467           queuepoly(VBODY2 * VBS, cgi.shPrincessDress, (evil ? 0xC040C0C0 : 0x8080FFC0) | UNTRANS);
1468         }
1469       else {
1470         if(vid.cs.charid < 2)
1471           queuepoly(VBODY1 * VBS, cgi.shPrinceDress,  evil ? 0x802080FF : 0x404040FF);
1472         }
1473 
1474       if(m == moRoseLady) {
1475   //    queuepoly(V, girl ? cgi.shGoatHead : cgi.shDemon,  0x800000FF);
1476         // make her hair a bit darker to stand out in 3D
1477         queuepoly(VHEAD1, girl ? cgi.shFemaleHair : cgi.shPHead,  evil ? 0x500050FF : GDIM == 3 ? 0x666A64FF : 0x332A22FF);
1478         }
1479       else if(m == moRoseBeauty) {
1480         if(girl) {
1481           queuepoly(VHEAD1, cgi.shBeautyHair,  0xF0A0D0FF);
1482           queuepoly(VHEAD2, cgi.shFlowerHair,  0xC00000FF);
1483           }
1484         else {
1485           queuepoly(VHEAD1, cgi.shPHead,  0xF0A0D0FF);
1486           queuepoly(VBODY * VBS, cgi.shFlowerHand,  0xC00000FF);
1487           queuepoly(VBODY2 * VBS, cgi.shSuspenders,  0xC00000FF);
1488           }
1489         }
1490       else {
1491         queuepoly(VHEAD1, girl ? cgi.shFemaleHair : cgi.shPHead,
1492           evil ? 0xC00000FF : 0x332A22FF);
1493         }
1494       queuepoly(VHEAD, cgi.shPFace,  facecolor);
1495       humanoid_eyes(V, evil ? 0x0000C0FF : 0x00C000FF, facecolor);
1496       return true;
1497       }
1498 
1499     case moWolf: case moRedFox: case moWolfMoved: case moLavaWolf: {
1500       ShadowV(V, cgi.shWolfBody);
1501       if(mmspatial || footphase)
1502         animallegs(VALEGS, moWolf, darkena(col, 0, 0xFF), footphase);
1503       else
1504         queuepoly(VALEGS, cgi.shWolfLegs, darkena(col, 0, 0xFF));
1505       queuepoly(VABODY, cgi.shWolfBody, darkena(col, 0, 0xFF));
1506       if(m == moRedFox) {
1507         queuepoly(VABODY, cgi.shFoxTail1, darkena(col, 0, 0xFF));
1508         queuepoly(VABODY, cgi.shFoxTail2, darkena(0xFFFFFF, 0, 0xFF));
1509         }
1510       queuepoly(VAHEAD, cgi.shWolfHead, darkena(col, 0, 0xFF));
1511       queuepoly(VAHEAD, cgi.shWolfEyes, darkena(col, 3, 0xFF));
1512       if(GDIM == 3) {
1513         queuepoly(VAHEAD, cgi.shFamiliarEye, 0xFF);
1514         queuepoly(VAHEAD * Mirror, cgi.shFamiliarEye, 0xFF);
1515         }
1516       return true;
1517       }
1518 
1519     case moReptile: {
1520       ShadowV(V, cgi.shReptileBody);
1521       animallegs(VALEGS, moReptile, darkena(col, 0, 0xFF), footphase);
1522       queuepoly(VABODY, cgi.shReptileBody, darkena(col, 0, 0xFF));
1523       queuepoly(VAHEAD, cgi.shReptileHead, darkena(col, 0, 0xFF));
1524       queuepoly(VAHEAD, cgi.shReptileEye, darkena(col, 3, 0xFF));
1525       queuepoly(VAHEAD * Mirror, cgi.shReptileEye, darkena(col, 3, 0xFF));
1526       if(GDIM == 2) queuepoly(VABODY, cgi.shReptileTail, darkena(col, 2, 0xFF));
1527       return true;
1528       }
1529 
1530     case moSalamander: {
1531       ShadowV(V, cgi.shReptileBody);
1532       animallegs(VALEGS, moReptile, darkena(0xD00000, 1, 0xFF), footphase);
1533       queuepoly(VABODY, cgi.shReptileBody, darkena(0xD00000, 0, 0xFF));
1534       queuepoly(VAHEAD, cgi.shReptileHead, darkena(0xD00000, 1, 0xFF));
1535       queuepoly(VAHEAD, cgi.shReptileEye, darkena(0xD00000, 0, 0xFF));
1536       queuepoly(VAHEAD * Mirror, cgi.shReptileEye, darkena(0xD00000, 0, 0xFF));
1537       queuepoly(VABODY, cgi.shReptileTail, darkena(0xD08000, 0, 0xFF));
1538       return true;
1539       }
1540 
1541     case moFrog: case moPhaser: case moVaulter: {
1542       ShadowV(V, cgi.shFrogBody);
1543       const shiftmatrix VL = GDIM == 3 ? V : mmscale(V, cgi.ALEG0);
1544       color_t xcolor = darkena(0xFF0000, 1, 0xFF);
1545       int alpha = (m == moPhaser ? 0xC0 : 0xFF);
1546       if(footphase) {
1547         queuepoly(VL, cgi.shFrogJumpFoot, darkena(col, 0, alpha));
1548         queuepoly(VL * Mirror, cgi.shFrogJumpFoot, darkena(col, 0, alpha));
1549         queuepoly(VALEGS, cgi.shFrogJumpLeg, xcolor);
1550         queuepoly(VALEGS * Mirror, cgi.shFrogJumpLeg, xcolor);
1551         }
1552       else {
1553         queuepoly(VL, cgi.shFrogRearFoot, darkena(col, 0, alpha));
1554         queuepoly(VL * Mirror, cgi.shFrogRearFoot, darkena(col, 0, alpha));
1555         queuepoly(VALEGS, cgi.shFrogRearLeg, xcolor);
1556         queuepoly(VALEGS * Mirror, cgi.shFrogRearLeg, xcolor);
1557         queuepoly(VALEGS, cgi.shFrogRearLeg2, xcolor);
1558         queuepoly(VALEGS * Mirror, cgi.shFrogRearLeg2, xcolor);
1559         }
1560       queuepoly(VL, cgi.shFrogFrontFoot, darkena(col, 0, alpha));
1561       queuepoly(VL * Mirror, cgi.shFrogFrontFoot, darkena(col, 0, alpha));
1562       queuepoly(VALEGS, cgi.shFrogFrontLeg, xcolor);
1563       queuepoly(VALEGS * Mirror, cgi.shFrogFrontLeg, xcolor);
1564       queuepoly(VABODY, cgi.shFrogBody, darkena(col, 0, alpha));
1565       queuepoly(VABODY, cgi.shFrogEye, darkena(col, 3, alpha));
1566       queuepoly(VABODY * Mirror, cgi.shFrogEye, darkena(col, 3, alpha));
1567       queuepoly(VABODY, cgi.shFrogStripe, xcolor);
1568       return true;
1569       }
1570 
1571     case moVineBeast: {
1572       ShadowV(V, cgi.shWolfBody);
1573       if(mmspatial || footphase)
1574         animallegs(VALEGS, moWolf, 0x00FF00FF, footphase);
1575       else
1576         queuepoly(VALEGS, cgi.shWolfLegs, 0x00FF00FF);
1577       queuepoly(VABODY, cgi.shWolfBody, darkena(col, 1, 0xFF));
1578       queuepoly(VAHEAD, cgi.shWolfHead, darkena(col, 0, 0xFF));
1579       queuepoly(VAHEAD, cgi.shWolfEyes, 0xFF0000FF);
1580       return true;
1581       }
1582 
1583     case moMouse: case moMouseMoved: {
1584       queuepoly(VALEGS, cgi.shMouse, darkena(col, 0, 0xFF));
1585       queuepoly(VALEGS, cgi.shMouseLegs, darkena(col, 1, 0xFF));
1586       queuepoly(VALEGS, cgi.shMouseEyes, 0xFF);
1587       return true;
1588       }
1589 
1590     case moRunDog: case moHunterDog: case moHunterGuard: case moHunterChanging: case moFallingDog: {
1591       if(!mmspatial && !footphase)
1592         queuepoly(VABODY, cgi.shDogBody, darkena(col, 0, 0xFF));
1593       else {
1594         ShadowV(V, cgi.shDogTorso);
1595         queuepoly(VABODY, cgi.shDogTorso, darkena(col, 0, 0xFF));
1596         animallegs(VALEGS, moRunDog, m == moFallingDog ? 0xFFFFFFFF : darkena(col, 0, 0xFF), footphase);
1597         }
1598       queuepoly(VAHEAD, cgi.shDogHead, darkena(col, 0, 0xFF));
1599 
1600       {
1601       dynamicval<color_t> dp(poly_outline);
1602       int eyecolor = 0x202020;
1603       bool redeyes = false;
1604       if(m == moHunterDog) eyecolor = 0xFF0000, redeyes = true;
1605       if(m == moHunterGuard) eyecolor = 0xFF6000, redeyes = true;
1606       if(m == moHunterChanging) eyecolor = 0xFFFF00, redeyes = true;
1607       int eyes = darkena(eyecolor, 0, 0xFF);
1608 
1609       if(redeyes) poly_outline = eyes;
1610       queuepoly(VAHEAD, cgi.shWolf1, eyes).flags |= POLY_FORCEWIDE;
1611       queuepoly(VAHEAD, cgi.shWolf2, eyes).flags |= POLY_FORCEWIDE;
1612       }
1613       queuepoly(VAHEAD, cgi.shWolf3, darkena(m == moRunDog ? 0x202020 : 0x000000, 0, 0xFF));
1614       return true;
1615       }
1616 
1617     case moOrangeDog: {
1618       if(!mmspatial && !footphase)
1619         queuepoly(VABODY, cgi.shDogBody, darkena(0xFFFFFF, 0, 0xFF));
1620       else {
1621         ShadowV(V, cgi.shDogTorso);
1622         if(GDIM == 2) queuepoly(VABODY, cgi.shDogTorso, darkena(0xFFFFFF, 0, 0xFF));
1623         animallegs(VALEGS, moRunDog, darkena(0xFFFFFF, 0, 0xFF), footphase);
1624         }
1625       queuepoly(VAHEAD, cgi.shDogHead, darkena(0xFFFFFF, 0, 0xFF));
1626       queuepoly(VABODY, cgi.shDogStripes, GDIM == 2 ? darkena(0x303030, 0, 0xFF) : 0xFFFFFFFF);
1627       queuepoly(VAHEAD, cgi.shWolf1, darkena(0x202020, 0, 0xFF));
1628       queuepoly(VAHEAD, cgi.shWolf2, darkena(0x202020, 0, 0xFF));
1629       queuepoly(VAHEAD, cgi.shWolf3, darkena(0x202020, 0, 0xFF));
1630       return true;
1631       }
1632 
1633     case moShark: case moGreaterShark: case moCShark:
1634       queuepoly(VFISH, cgi.shShark, darkena(col, 0, 0xFF));
1635       return true;
1636 
1637     case moPike:
1638       queuepoly(VFISH, cgi.shPikeBody, darkena(col, 0, 0xFF));
1639       queuepoly(VFISH, cgi.shPikeEye, darkena(col, 2, 0xFF));
1640       queuepoly(VFISH * Mirror, cgi.shPikeEye, darkena(col, 2, 0xFF));
1641       return true;
1642 
1643     case moEagle: case moParrot: case moBomberbird: case moAlbatross:
1644     case moTameBomberbird: case moWindCrow: case moTameBomberbirdMoved:
1645     case moSandBird: case moAcidBird: {
1646       ShadowV(V, cgi.shEagle);
1647       auto& sh = GDIM == 3 ? cgi.shAnimatedEagle[wingphase(200)] : cgi.shEagle;
1648       if(m == moParrot && GDIM == 3)
1649         queuepolyat(VBIRD, sh, darkena(col, 0, 0xFF), PPR::SUPERLINE);
1650       else
1651         queuepoly(VBIRD, sh, darkena(col, 0, 0xFF));
1652       return true;
1653       }
1654 
1655     case moSparrowhawk: case moWestHawk: {
1656       ShadowV(V, cgi.shHawk);
1657       auto& sh = GDIM == 3 ? cgi.shAnimatedHawk[wingphase(200)] : cgi.shHawk;
1658       queuepoly(VBIRD, sh, darkena(col, 0, 0xFF));
1659       return true;
1660       }
1661 
1662     case moButterfly: {
1663       transmatrix Vwing = wingmatrix(100);
1664       ShadowV(V * Vwing, cgi.shButterflyWing);
1665       if(GDIM == 2)
1666         queuepoly(VBIRD * Vwing, cgi.shButterflyWing, darkena(col, 0, 0xFF));
1667       else
1668         queuepoly(VBIRD, cgi.shAnimatedButterfly[wingphase(100)], darkena(col, 0, 0xFF));
1669       queuepoly(VBIRD, cgi.shButterflyBody, darkena(col, 2, 0xFF));
1670       return true;
1671       }
1672 
1673     case moGadfly: {
1674       transmatrix Vwing = wingmatrix(100);
1675       ShadowV(V * Vwing, cgi.shGadflyWing);
1676       queuepoly(VBIRD * Vwing, GDIM == 2 ? cgi.shGadflyWing : cgi.shAnimatedGadfly[wingphase(100)], darkena(col, 0, 0xFF));
1677       queuepoly(VBIRD, cgi.shGadflyBody, darkena(col, 1, 0xFF));
1678       queuepoly(VBIRD, cgi.shGadflyEye, darkena(col, 2, 0xFF));
1679       queuepoly(VBIRD * Mirror, cgi.shGadflyEye, darkena(col, 2, 0xFF));
1680       return true;
1681       }
1682 
1683     case moVampire: case moBat: {
1684       // vampires have no shadow and no mirror images
1685       if(m == moBat) ShadowV(V, cgi.shBatWings);
1686       if(m == moBat || !inmirrorcount) {
1687         queuepoly(VBIRD, GDIM == 2 ? cgi.shBatWings : cgi.shAnimatedBat[wingphase(100)], darkena(0x303030, 0, 0xFF));
1688         queuepoly(VBIRD, GDIM == 2 ? cgi.shBatBody : cgi.shAnimatedBat2[wingphase(100)], darkena(0x606060, 0, 0xFF));
1689         }
1690       /* queuepoly(V, cgi.shBatMouth, darkena(0xC00000, 0, 0xFF));
1691       queuepoly(V, cgi.shBatFang, darkena(0xFFC0C0, 0, 0xFF));
1692       queuepoly(V*Mirror, cgi.shBatFang, darkena(0xFFC0C0, 0, 0xFF));
1693       queuepoly(V, cgi.shBatEye, darkena(00000000, 0, 0xFF));
1694       queuepoly(V*Mirror, cgi.shBatEye, darkena(00000000, 0, 0xFF)); */
1695       return true;
1696       }
1697 
1698     case moGargoyle: {
1699       ShadowV(V, cgi.shGargoyleWings);
1700       queuepoly(VBIRD, GDIM == 2 ? cgi.shGargoyleWings : cgi.shAnimatedGargoyle[wingphase(300)], darkena(col, 0, 0xD0));
1701       queuepoly(VBIRD, GDIM == 2 ? cgi.shGargoyleBody : cgi.shAnimatedGargoyle2[wingphase(300)], darkena(col, 0, 0xFF));
1702       return true;
1703       }
1704 
1705     case moZombie: {
1706       int c = darkena(col, where && where->land == laHalloween ? 1 : 0, 0xFF);
1707       const transmatrix VBS = otherbodyparts(V, c, m, footphase);
1708       ShadowV(V, cgi.shPBody);
1709       queuepoly(VBODY * VBS, cgi.shPBody, c);
1710       return true;
1711       }
1712 
1713     case moTerraWarrior: {
1714       drawTerraWarrior(V, 7, (where ? where->hitpoints : 7), footphase);
1715       return true;
1716       }
1717 
1718     case moVariantWarrior: {
1719       const shiftmatrix VBS = VBODY * otherbodyparts(V, darkena(col, 0, 0xC0), m, footphase);
1720       ShadowV(V, cgi.shPBody);
1721       queuepoly(VBS, cgi.shPBody, darkena(0xFFD500, 0, 0xF0));
1722       if(!peace::on) queuepoly(VBS, cgi.shPSword, 0xFFFF00FF);
1723       queuepoly(VHEAD, cgi.shHood, 0x008000FF);
1724       humanoid_eyes(V, 0xFFFF00FF);
1725       return true;
1726       }
1727 
1728     case moDesertman: {
1729       const shiftmatrix VBS = VBODY * otherbodyparts(V, darkena(col, 0, 0xC0), m, footphase);
1730       ShadowV(V, cgi.shPBody);
1731       queuepoly(VBS, cgi.shPBody, darkena(col, 0, 0xC0));
1732       if(!peace::on) queuepoly(VBS, cgi.shPSword, 0xFFFF00FF);
1733       queuepoly(VHEAD, cgi.shHood, 0xD0D000C0 | UNTRANS);
1734       humanoid_eyes(V, 0x301800FF);
1735       return true;
1736       }
1737 
1738     case moMonk: {
1739       const transmatrix VBS = otherbodyparts(V, darkena(col, 0, 0xC0), m, footphase);
1740       ShadowV(V, cgi.shRaiderBody);
1741       queuepoly(VBODY * VBS, cgi.shRaiderBody, darkena(col, 0, 0xFF));
1742       queuepoly(VBODY1 * VBS, cgi.shRaiderShirt, darkena(col, 2, 0xFF));
1743       if(!peace::on) queuepoly(VBODY * VBS, cgi.shPKnife, 0xFFC0C0C0);
1744       queuepoly(VBODY2 * VBS, cgi.shRaiderArmor, darkena(col, 1, 0xFF));
1745       queuepolyat(VBODY3 * VBS, cgi.shRatCape2, darkena(col, 2, 0xFF), PPR::MONSTER_ARMOR0);
1746       queuepoly(VHEAD1, cgi.shRaiderHelmet, darkena(col, 0, 0XFF));
1747       queuepoly(VHEAD, cgi.shPFace, darkena(0xC0C0A0, 0, 0XFF));
1748       humanoid_eyes(V, 0x000000FF);
1749       return true;
1750       }
1751 
1752     case moCrusher: {
1753       const transmatrix VBS = otherbodyparts(V, darkena(col, 1, 0xFF), m, footphase);
1754       ShadowV(V, cgi.shRaiderBody);
1755       queuepoly(VBODY * VBS, cgi.shRaiderBody, darkena(col, 0, 0xFF));
1756       queuepoly(VBODY1 * VBS, cgi.shRaiderShirt, darkena(col, 2, 0xFF));
1757       queuepoly(VBODY2 * VBS, cgi.shRaiderArmor, darkena(col, 1, 0xFF));
1758       queuepoly(VBODY * VBS, cgi.shFlailTrunk, darkena(col, 1, 0XFF));
1759       queuepoly(VBODY1 * VBS, cgi.shHammerHead, darkena(col, 0, 0XFF));
1760       queuepoly(VHEAD1, cgi.shRaiderHelmet, darkena(col, 0, 0XFF));
1761       queuepoly(VHEAD, cgi.shPFace, darkena(0xC0C0A0, 0, 0XFF));
1762       humanoid_eyes(V, 0x000000FF);
1763       return true;
1764       }
1765 
1766     case moPair: {
1767       const transmatrix VBS = otherbodyparts(V, darkena(col, 1, 0xFF), m, footphase);
1768       ShadowV(V, cgi.shRaiderBody);
1769       queuepoly(VBODY * VBS, cgi.shRaiderBody, darkena(col, 0, 0xFF));
1770       queuepoly(VBODY1 * VBS, cgi.shRaiderShirt, darkena(col, 2, 0xFF));
1771       queuepoly(VBODY2 * VBS, cgi.shRaiderArmor, darkena(col, 1, 0xFF));
1772       queuepoly(VBODY * VBS, cgi.shPickAxe, darkena(0xA0A0A0, 0, 0XFF));
1773       queuepoly(VHEAD1, cgi.shRaiderHelmet, darkena(col, 0, 0XFF));
1774       queuepoly(VHEAD, cgi.shPFace, darkena(0xC0C0A0, 0, 0XFF));
1775       humanoid_eyes(V, 0x000000FF);
1776       return true;
1777       }
1778 
1779     case moAltDemon: case moHexDemon: {
1780       const transmatrix VBS = otherbodyparts(V, darkena(col, 0, 0xC0), m, footphase);
1781       ShadowV(V, cgi.shRaiderBody);
1782       queuepoly(VBODY * VBS, cgi.shRaiderBody, darkena(col, 0, 0xFF));
1783       queuepoly(VBODY1 * VBS, cgi.shRaiderShirt, darkena(col, 2, 0xFF));
1784       queuepoly(VBODY2 * VBS, cgi.shRaiderArmor, darkena(col, 1, 0xFF));
1785       if(!peace::on) queuepoly(VBODY * VBS, cgi.shPSword, 0xFFD0D0D0);
1786       queuepoly(VHEAD1, cgi.shRaiderHelmet, darkena(col, 0, 0XFF));
1787       queuepoly(VHEAD, cgi.shPFace, darkena(0xC0C0A0, 0, 0XFF));
1788       humanoid_eyes(V, 0x000000FF);
1789       return true;
1790       }
1791 
1792     case moSkeleton: {
1793       const shiftmatrix VBS = VBODY * otherbodyparts(V, darkena(0xFFFFFF, 0, 0xFF), moSkeleton, footphase);
1794       queuepoly(VBS, cgi.shSkeletonBody, darkena(0xFFFFFF, 0, 0xFF));
1795       if(GDIM == 2) queuepoly(VHEAD, cgi.shSkull, darkena(0xFFFFFF, 0, 0xFF));
1796       if(GDIM == 2) queuepoly(VHEAD1, cgi.shSkullEyes, 0x000000FF);
1797       humanoid_eyes(V, 0x000000FF, 0xFFFFFFFF);
1798       ShadowV(V, cgi.shSkeletonBody);
1799       queuepoly(VBS, cgi.shSabre, 0xFFFFFFFF);
1800       return true;
1801       }
1802 
1803     case moPalace: case moFatGuard: case moVizier: {
1804       ShadowV(V, cgi.shPBody);
1805       const transmatrix VBS = otherbodyparts(V, darkena(0xFFD500, 0, 0xFF), m, footphase);
1806       if(m == moFatGuard) {
1807         queuepoly(VBODY * VBS, cgi.shFatBody, darkena(0xC06000, 0, 0xFF));
1808         col = 0xFFFFFF;
1809         if(!where || where->hitpoints >= 3)
1810           queuepoly(VBODY1 * VBS, cgi.shKnightCloak, darkena(0xFFC0C0, 1, 0xFF));
1811         }
1812       else {
1813         queuepoly(VBODY * VBS, cgi.shPBody, darkena(0xFFD500, 0, 0xFF));
1814         queuepoly(VBODY1 * VBS, cgi.shKnightArmor, m == moVizier ? 0xC000C0FF :
1815           darkena(0x00C000, 1, 0xFF));
1816         if(where && where->hitpoints >= 3)
1817           queuepoly(VBODY2 * VBS, cgi.shKnightCloak, m == moVizier ? 0x800080Ff :
1818             darkena(0x00FF00, 1, 0xFF));
1819         }
1820       queuepoly(VHEAD1, cgi.shTurban1, darkena(col, 1, 0xFF));
1821       if(!where || where->hitpoints >= 2)
1822         queuepoly(VHEAD2, cgi.shTurban2, darkena(col, 0, 0xFF));
1823       queuepoly(VBODY * VBS, cgi.shSabre, 0xFFFFFFFF);
1824       humanoid_eyes(V, 0x301800FF);
1825       return true;
1826       }
1827 
1828     case moCrystalSage: {
1829       const shiftmatrix VBS = VBODY * otherbodyparts(V, 0xFFFFFFFF, m, footphase);
1830       ShadowV(V, cgi.shPBody);
1831       queuepoly(VBS, cgi.shPBody, 0xFFFFFFFF);
1832       queuepoly(VHEAD1, cgi.shPHead, 0xFFFFFFFF);
1833       queuepoly(VHEAD, cgi.shPFace, 0xFFFFFFFF);
1834       humanoid_eyes(V, 0xFFFFFFFF, 0xC0C0C0FF);
1835       return true;
1836       }
1837 
1838     case moHedge: {
1839       ShadowV(V, cgi.shPBody);
1840       const shiftmatrix VBS = VBODY * otherbodyparts(V, darkena(col, 1, 0xFF), m, footphase);
1841       queuepoly(VBS, cgi.shPBody, darkena(col, 0, 0xFF));
1842       queuepoly(VBS, cgi.shHedgehogBlade, 0xC0C0C0FF);
1843       queuepoly(VHEAD1, cgi.shPHead, 0x804000FF);
1844       queuepoly(VHEAD, cgi.shPFace, 0xF09000FF);
1845       humanoid_eyes(V, 0x00D000FF);
1846       return true;
1847       }
1848 
1849     case moYeti: case moMonkey: {
1850       const shiftmatrix VBS = VBODY * otherbodyparts(V, darkena(col, 0, 0xC0), m, footphase);
1851       ShadowV(V, cgi.shPBody);
1852       queuepoly(VBS, cgi.shYeti, darkena(col, 0, 0xC0));
1853       queuepoly(VHEAD1, cgi.shPHead, darkena(col, 0, 0xFF));
1854       humanoid_eyes(V, 0x000000FF, darkena(col, 0, 0xFF));
1855       return true;
1856       }
1857 
1858     case moResearcher: {
1859       const shiftmatrix VBS = VBODY * otherbodyparts(V, darkena(col, 0, 0xFF), m, footphase);
1860       ShadowV(V, cgi.shPBody);
1861       queuepoly(VBS, cgi.shPBody, darkena(0xFFFF00, 0, 0xC0));
1862       queuepoly(VHEAD, cgi.shAztecHead, darkena(col, 0, 0xFF));
1863       queuepoly(VHEAD1, cgi.shAztecCap, darkena(0xC000C0, 0, 0xFF));
1864       humanoid_eyes(V, 0x000000FF);
1865       return true;
1866       }
1867 
1868     case moFamiliar: {
1869       ShadowV(V, cgi.shWolfBody);
1870       queuepoly(VABODY, cgi.shWolfBody, darkena(0xA03000, 0, 0xFF));
1871        if(mmspatial || footphase)
1872          animallegs(VALEGS, moWolf, darkena(0xC04000, 0, 0xFF), footphase);
1873        else
1874         queuepoly(VALEGS, cgi.shWolfLegs, darkena(0xC04000, 0, 0xFF));
1875 
1876       queuepoly(VAHEAD, cgi.shFamiliarHead, darkena(0xC04000, 0, 0xFF));
1877       // queuepoly(V, cgi.shCatLegs, darkena(0x902000, 0, 0xFF));
1878       if(true) {
1879         queuepoly(VAHEAD, cgi.shFamiliarEye, darkena(0xFFFF00, 0, 0xFF));
1880         queuepoly(VAHEAD * Mirror, cgi.shFamiliarEye, darkena(0xFFFF00, 0, 0xFF));
1881         }
1882       return true;
1883       }
1884 
1885     case moRanger: {
1886       ShadowV(V, cgi.shPBody);
1887       const shiftmatrix VBS = VBODY * otherbodyparts(V, darkena(col, 0, 0xFF), m, footphase);
1888       queuepoly(VBS, cgi.shPBody, darkena(col, 0, 0xC0));
1889       if(!peace::on) queuepoly(VBS, cgi.shPSword, darkena(col, 0, 0xFF));
1890       queuepoly(VHEAD, cgi.shArmor, darkena(col, 1, 0xFF));
1891       humanoid_eyes(V, 0x000000FF);
1892       return true;
1893       }
1894 
1895     case moNarciss: {
1896       ShadowV(V, cgi.shPBody);
1897       const shiftmatrix VBS = VBODY * otherbodyparts(V, darkena(col, 0, 0xFF), m, footphase);
1898       queuepoly(VBS, cgi.shFlowerHand, darkena(col, 0, 0xFF));
1899       queuepoly(VBS, cgi.shPBody, 0xFFE080FF);
1900       if(!peace::on) queuepoly(VBS, cgi.shPKnife, 0xC0C0C0FF);
1901       queuepoly(VHEAD, cgi.shPFace, 0xFFE080FF);
1902       queuepoly(VHEAD1, cgi.shPHead, 0x806A00FF);
1903       humanoid_eyes(V, 0x000000FF);
1904       return true;
1905       }
1906 
1907     case moMirrorSpirit: {
1908       ShadowV(V, cgi.shPBody);
1909       const shiftmatrix VBS = VBODY * otherbodyparts(V, darkena(col, 0, 0x90), m, footphase);
1910       queuepoly(VBS, cgi.shPBody, darkena(col, 0, 0x90));
1911       if(!peace::on) queuepoly(VBS * Mirror, cgi.shPSword, darkena(col, 0, 0xD0));
1912       queuepoly(VHEAD1, cgi.shPHead, darkena(col, 1, 0x90));
1913       queuepoly(VHEAD2, cgi.shPFace, darkena(col, 1, 0x90));
1914       queuepoly(VHEAD, cgi.shArmor, darkena(col, 0, 0xC0));
1915       humanoid_eyes(V, 0xFFFFFFFF, darkena(col, 0, 0xFF));
1916       return true;
1917       }
1918 
1919     case moJiangshi: {
1920       ShadowV(V, cgi.shJiangShi);
1921       auto z2 = WDIM == 3 ? 0 : GDIM == 3 ? -abs(sin(footphase * M_PI * 2)) * cgi.human_height/3 : geom3::lev_to_factor(abs(sin(footphase * M_PI * 2)) * cgi.human_height);
1922       auto V0 = V;
1923       auto V = mmscale(V0, z2);
1924       otherbodyparts(V, darkena(col, 0, 0xFF), m, m == moJiangshi ? 0 : footphase);
1925       queuepoly(VBODY, cgi.shJiangShi, darkena(col, 0, 0xFF));
1926       queuepoly(VBODY1, cgi.shJiangShiDress, darkena(0x202020, 0, 0xFF));
1927       queuepoly(VHEAD, cgi.shTerraHead, darkena(0x101010, 0, 0xFF));
1928       queuepoly(VHEAD1, cgi.shPFace, darkena(col, 0, 0xFF));
1929       queuepoly(VHEAD2, cgi.shJiangShiCap1, darkena(0x800000, 0, 0xFF));
1930       queuepoly(VHEAD3, cgi.shJiangShiCap2, darkena(0x400000, 0, 0xFF));
1931       humanoid_eyes(V, 0x000000FF, darkena(col, 0, 0xFF));
1932       return true;
1933       }
1934 
1935     case moGhost: case moSeep: case moFriendlyGhost: {
1936       if(m == moFriendlyGhost) col = fghostcolor(where);
1937       queuepolyat(VGHOST, cgi.shGhost, darkena(col, 0, m == moFriendlyGhost ? 0xC0 : 0x80), GDIM == 3 ? PPR::SUPERLINE : cgi.shGhost.prio);
1938       queuepolyat(VGHOST, cgi.shGhostEyes, 0xFF, GDIM == 3 ? PPR::SUPERLINE : cgi.shEyes.prio);
1939       return true;
1940       }
1941 
1942     case moVineSpirit: {
1943       queuepoly(VGHOST, cgi.shGhost, 0xD0D0D0C0 | UNTRANS);
1944       queuepolyat(VGHOST, cgi.shGhostEyes, 0xFF0000FF, GDIM == 3 ? PPR::SUPERLINE : cgi.shGhostEyes.prio);
1945       return true;
1946       }
1947 
1948     case moFireFairy: {
1949       col = firecolor(0);
1950       const shiftmatrix VBS = VBODY * otherbodyparts(V, darkena(col, 0, 0xFF), m, footphase);
1951       ShadowV(V, cgi.shFemaleBody);
1952       queuepoly(VBS, cgi.shFemaleBody, darkena(col, 0, 0XC0));
1953       queuepoly(VHEAD, cgi.shWitchHair, darkena(col, 1, 0xFF));
1954       queuepoly(VHEAD1, cgi.shPFace, darkena(col, 0, 0XFF));
1955       humanoid_eyes(V, darkena(col, 1, 0xFF));
1956       return true;
1957       }
1958 
1959     case moRusalka: {
1960       col = watercolor(0);
1961       bool girl = princessgender() == GEN_F;
1962       if(girl) {
1963         const shiftmatrix VBS = VBODY * otherbodyparts(V, col, m, footphase);
1964         ShadowV(V, cgi.shFemaleBody);
1965         queuepoly(VBS, cgi.shFemaleBody, watercolor(100));
1966         queuepoly(VHEAD1, cgi.shFemaleHair,  watercolor(150));
1967         // queuepoly(VHEAD2, cgi.shFlowerHair,  watercolor(50));
1968         // queuepoly(VHEAD, cgi.shWitchHair, watercolor(150));
1969         queuepoly(VHEAD1, cgi.shPFace, watercolor(200));
1970         queuepoly(VHEAD1, cgi.shWightCloak, watercolor(50) & 0xFFFFFF80);
1971         humanoid_eyes(V, col | 0xFF);
1972         }
1973       else {
1974         const shiftmatrix VBS = VBODY * otherbodyparts(V, col, m, footphase);
1975         ShadowV(V, cgi.shPBody);
1976         queuepoly(VBS, cgi.shPBody, watercolor(100));
1977 
1978         queuepoly(VBS, cgi.shSuspenders,  watercolor(150));
1979 
1980         queuepoly(VHEAD1, cgi.shPHead,  watercolor(50));
1981         queuepoly(VHEAD1, cgi.shPFace, watercolor(200));
1982         queuepoly(VHEAD1, cgi.shWightCloak, watercolor(50) & 0xFFFFFF80);
1983         humanoid_eyes(V, col | 0xFF);
1984         }
1985       return true;
1986       }
1987 
1988     case moSlime: {
1989       queuepoly(VFISH, cgi.shSlime, darkena(col, 0, 0x80));
1990       queuepoly(VSLIMEEYE, cgi.shSlimeEyes, 0xFF);
1991       return true;
1992       }
1993 
1994     case moKrakenH: {
1995       queuepoly(VFISH, cgi.shKrakenHead, darkena(col, 0, 0xD0));
1996       queuepoly(VFISH, cgi.shKrakenEye, 0xFFFFFFC0 | UNTRANS);
1997       queuepoly(VFISH, cgi.shKrakenEye2, 0xC0);
1998       queuepoly(VFISH * Mirror, cgi.shKrakenEye, 0xFFFFFFC0 | UNTRANS);
1999       queuepoly(VFISH * Mirror, cgi.shKrakenEye2, 0xC0);
2000       return true;
2001       }
2002 
2003     case moKrakenT: {
2004       queuepoly(VFISH, cgi.shSeaTentacle, darkena(col, 0, 0xD0));
2005       return true;
2006       }
2007 
2008     case moCultist: case moPyroCultist: case moCultistLeader: {
2009       const shiftmatrix VBS = VBODY * otherbodyparts(V, darkena(col, 1, 0xFF), m, footphase);
2010       ShadowV(V, cgi.shPBody);
2011       queuepoly(VBS, cgi.shPBody, darkena(col, 0, 0xC0));
2012       if(!peace::on) queuepoly(VBS, cgi.shPSword, darkena(col, 2, 0xFF));
2013       queuepoly(VHEAD, cgi.shHood, darkena(col, 1, 0xFF));
2014       humanoid_eyes(V, 0x00FF00FF);
2015       return true;
2016       }
2017 
2018     case moPirate: {
2019       const shiftmatrix VBS = VBODY * otherbodyparts(V, darkena(col, 0, 0xFF), m, footphase);
2020       ShadowV(V, cgi.shPBody);
2021       queuepoly(VBS, cgi.shPBody, darkena(0x404040, 0, 0xFF));
2022       queuepoly(VBS, cgi.shPirateHook, darkena(0xD0D0D0, 0, 0xFF));
2023       queuepoly(VHEAD, cgi.shPFace, darkena(0xFFFF80, 0, 0xFF));
2024       queuepoly(VHEAD1, cgi.shEyepatch, darkena(0, 0, 0xC0));
2025       queuepoly(VHEAD2, cgi.shPirateHood, darkena(col, 0, 0xFF));
2026       humanoid_eyes(V, 0x000000FF);
2027       return true;
2028       }
2029 
2030     case moRatling: case moRatlingAvenger: {
2031       const transmatrix VBS = otherbodyparts(V, darkena(col, 0, 0xFF), m, footphase);
2032       ShadowV(V, cgi.shYeti);
2033       queuepoly(VLEG, cgi.shRatTail, darkena(col, 0, 0xFF));
2034       queuepoly(VBODY * VBS, cgi.shYeti, darkena(col, 1, 0xFF));
2035 
2036       float t = sintick(1000, where ? where->cpdist*M_PI : 0);
2037       int eyecol = t > 0.92 ? 0xFF0000 : 0;
2038 
2039       if(GDIM == 2) {
2040         queuepoly(VHEAD, cgi.shRatHead, darkena(col, 0, 0xFF));
2041         queuepoly(VHEAD, cgi.shWolf1, darkena(eyecol, 0, 0xFF));
2042         queuepoly(VHEAD, cgi.shWolf2, darkena(eyecol, 0, 0xFF));
2043         queuepoly(VHEAD, cgi.shWolf3, darkena(0x202020, 0, 0xFF));
2044         if(m == moRatlingAvenger) queuepoly(VHEAD1, cgi.shRatCape1, 0x303030FF);
2045         }
2046 #if MAXMDIM >= 4
2047       else {
2048         shiftmatrix V1 = V * zpush(cgi.AHEAD - zc(0.4) - zc(0.98) + cgi.HEAD); // * cpush(0, cgi.scalefactor * (-0.1));
2049         queuepoly(V1, cgi.shRatHead, darkena(col, 0, 0xFF));
2050 
2051         /*
2052         queuepoly(V1, cgi.shFamiliarEye, darkena(eyecol, 0, 0xFF));
2053         queuepoly(V1 * Mirror, cgi.shFamiliarEye, darkena(eyecol, 0, 0xFF));
2054         queuepoly(V1, cgi.shWolfEyes, darkena(col, 3, 0xFF));
2055         */
2056         queuepoly(V1, cgi.shRatEye1, darkena(eyecol, 0, 0xFF));
2057         queuepoly(V1, cgi.shRatEye2, darkena(eyecol, 0, 0xFF));
2058         queuepoly(V1, cgi.shRatEye3, darkena(0x202020, 0, 0xFF));
2059         if(m == moRatlingAvenger) queuepoly(V1, cgi.shRatCape1, 0x303030FF);
2060         }
2061 #endif
2062       if(m == moRatlingAvenger) {
2063         queuepoly(VBODY1 * VBS, cgi.shRatCape2, 0x484848FF);
2064         }
2065       return true;
2066       }
2067 
2068     case moViking: {
2069       const transmatrix VBS = otherbodyparts(V, darkena(col, 1, 0xFF), m, footphase);
2070       ShadowV(V, cgi.shPBody);
2071       queuepoly(VBODY * VBS, cgi.shPBody, darkena(0xE00000, 0, 0xFF));
2072       if(!peace::on) queuepoly(VBODY * VBS, cgi.shPSword, darkena(0xD0D0D0, 0, 0xFF));
2073       queuepoly(VBODY1 * VBS, cgi.shKnightCloak, darkena(0x404040, 0, 0xFF));
2074       queuepoly(VHEAD, cgi.shVikingHelmet, darkena(0xC0C0C0, 0, 0XFF));
2075       queuepoly(VHEAD, cgi.shPFace, darkena(0xFFFF80, 0, 0xFF));
2076       humanoid_eyes(V, 0x000000FF);
2077       return true;
2078       }
2079 
2080     case moOutlaw: {
2081       const transmatrix VBS = otherbodyparts(V, darkena(col, 1, 0xFF), m, footphase);
2082       ShadowV(V, cgi.shPBody);
2083       queuepoly(VBODY * VBS, cgi.shPBody, darkena(col, 0, 0xFF));
2084       queuepoly(VBODY1 * VBS, cgi.shKnightCloak, darkena(col, 1, 0xFF));
2085       queuepoly(VHEAD1, cgi.shWestHat1, darkena(col, 2, 0XFF));
2086       queuepoly(VHEAD2, cgi.shWestHat2, darkena(col, 1, 0XFF));
2087       queuepoly(VHEAD, cgi.shPFace, darkena(0xFFFF80, 0, 0xFF));
2088       queuepoly(VBODY * VBS, cgi.shGunInHand, darkena(col, 1, 0XFF));
2089       humanoid_eyes(V, 0x000000FF);
2090       return true;
2091       }
2092 
2093     case moNecromancer: {
2094       const shiftmatrix VBS = VBODY * otherbodyparts(V, darkena(col, 0, 0xFF), m, footphase);
2095       ShadowV(V, cgi.shPBody);
2096       queuepoly(VBS, cgi.shPBody, 0xC00000C0 | UNTRANS);
2097       queuepoly(VHEAD, cgi.shHood, darkena(col, 1, 0xFF));
2098       humanoid_eyes(V, 0xFF0000FF);
2099       return true;
2100       }
2101 
2102     case moDraugr: {
2103       const shiftmatrix VBS = VBODY * otherbodyparts(V, 0x483828D0 | UNTRANS, m, footphase);
2104       queuepoly(VBS, cgi.shPBody, 0x483828D0 | UNTRANS);
2105       queuepoly(VBS, cgi.shPSword, 0xFFFFD0A0 | UNTRANS);
2106       queuepoly(VHEAD, cgi.shPHead, 0x483828D0 | UNTRANS);
2107       humanoid_eyes(V, 0xFF0000FF, 0x483828FF);
2108       // queuepoly(V, cgi.shSkull, 0xC06020D0);
2109       //queuepoly(V, cgi.shSkullEyes, 0x000000D0);
2110   //  queuepoly(V, cgi.shWightCloak, 0xC0A080A0);
2111       int b = where ? where->cpdist : 0;
2112       b--;
2113       if(b < 0) b = 0;
2114       if(b > 6) b = 6;
2115       queuepoly(VHEAD1, cgi.shWightCloak, color_t(0x605040A0 | UNTRANS) + color_t(0x10101000 * b));
2116       return true;
2117       }
2118 
2119     case moVoidBeast: {
2120       const shiftmatrix VBS = VBODY * otherbodyparts(V, 0x080808D0 | UNTRANS, m, footphase);
2121       queuepoly(VBS, cgi.shPBody, 0x080808D0 | UNTRANS);
2122       queuepoly(VHEAD, cgi.shPHead, 0x080808D0 | UNTRANS);
2123       queuepoly(VHEAD, cgi.shWightCloak, 0xFF0000A0 | UNTRANS);
2124       humanoid_eyes(V, 0xFF0000FF, 0x080808FF);
2125       return true;
2126       }
2127 
2128     case moGoblin: {
2129       const shiftmatrix VBS = VBODY * otherbodyparts(V, darkena(col, 0, 0xFF), m, footphase);
2130       ShadowV(V, cgi.shYeti);
2131       queuepoly(VBS, cgi.shYeti, darkena(col, 0, 0xC0));
2132       queuepoly(VHEAD, cgi.shArmor, darkena(col, 1, 0XFF));
2133       humanoid_eyes(V, 0x800000FF, darkena(col, 0, 0xFF));
2134       return true;
2135       }
2136 
2137     case moLancer: case moFlailer: case moMiner: {
2138       shiftmatrix V2 = V;
2139       if(m == moLancer)
2140         V2 = V * spin((where && where->type == 6) ? -M_PI/3 : -M_PI/2 );
2141       shiftmatrix Vh = mmscale(V2, cgi.HEAD);
2142       shiftmatrix Vb = mmscale(V2, cgi.BODY);
2143       Vb = Vb * otherbodyparts(V2, darkena(col, 1, 0xFF), m, footphase);
2144       ShadowV(V2, cgi.shPBody);
2145       queuepoly(Vb, cgi.shPBody, darkena(col, 0, 0xC0));
2146       queuepoly(Vh, m == moFlailer ? cgi.shArmor : cgi.shHood, darkena(col, 1, 0XFF));
2147       if(m == moMiner)
2148         queuepoly(Vb, cgi.shPickAxe, darkena(0xC0C0C0, 0, 0XFF));
2149       if(m == moLancer)
2150         queuepoly(Vb, cgi.shPike, darkena(col, 0, 0XFF));
2151       if(m == moFlailer) {
2152         queuepoly(Vb, cgi.shFlailBall, darkena(col, 0, 0XFF));
2153         queuepoly(Vb, cgi.shFlailChain, darkena(col, 1, 0XFF));
2154         queuepoly(Vb, cgi.shFlailTrunk, darkena(col, 0, 0XFF));
2155         }
2156       humanoid_eyes(V, 0x000000FF);
2157       return true;
2158       }
2159 
2160     case moTroll: {
2161       const shiftmatrix VBS = VBODY * otherbodyparts(V, darkena(col, 0, 0xFF), m, footphase);
2162       ShadowV(V, cgi.shYeti);
2163       queuepoly(VBS, cgi.shYeti, darkena(col, 0, 0xC0));
2164       queuepoly(VHEAD1, cgi.shPHead, darkena(col, 1, 0XFF));
2165       queuepoly(VHEAD, cgi.shPFace, darkena(col, 2, 0XFF));
2166       humanoid_eyes(V, 0x004000FF, darkena(col, 0, 0xFF));
2167       return true;
2168       }
2169 
2170     case moFjordTroll: case moForestTroll: case moStormTroll: {
2171       const shiftmatrix VBS = VBODY * otherbodyparts(V, darkena(col, 0, 0xFF), m, footphase);
2172       ShadowV(V, cgi.shYeti);
2173       queuepoly(VBS, cgi.shYeti, darkena(col, 0, 0xC0));
2174       queuepoly(VHEAD1, cgi.shPHead, darkena(col, 1, 0XFF));
2175       queuepoly(VHEAD, cgi.shPFace, darkena(col, 2, 0XFF));
2176       humanoid_eyes(V, 0x004000FF, darkena(col, 0, 0xFF));
2177       return true;
2178       }
2179 
2180     case moDarkTroll: {
2181       const shiftmatrix VBS = VBODY * otherbodyparts(V, darkena(col, 0, 0xFF), m, footphase);
2182       ShadowV(V, cgi.shYeti);
2183       queuepoly(VBS, cgi.shYeti, darkena(col, 0, 0xC0));
2184       queuepoly(VHEAD1, cgi.shPHead, darkena(col, 1, 0XFF));
2185       queuepoly(VHEAD, cgi.shPFace, 0xFFFFFF80 | UNTRANS);
2186       humanoid_eyes(V, 0x000000FF, darkena(col, 0, 0xFF));
2187       return true;
2188       }
2189 
2190     case moRedTroll: {
2191       const shiftmatrix VBS = VBODY * otherbodyparts(V, darkena(col, 0, 0xFF), m, footphase);
2192       ShadowV(V, cgi.shYeti);
2193       queuepoly(VBS, cgi.shYeti, darkena(col, 0, 0xC0));
2194       queuepoly(VHEAD1, cgi.shPHead, darkena(0xFF8000, 0, 0XFF));
2195       queuepoly(VHEAD, cgi.shPFace, 0xFFFFFF80 | UNTRANS);
2196       humanoid_eyes(V, 0x000000FF, darkena(col, 0, 0xFF));
2197       return true;
2198       }
2199 
2200     case moEarthElemental: {
2201       const shiftmatrix VBS = VBODY * otherbodyparts(V, darkena(col, 1, 0xFF), m, footphase);
2202       ShadowV(V, cgi.shWaterElemental);
2203       queuepoly(VBS, cgi.shWaterElemental, darkena(col, 0, 0xC0));
2204       queuepoly(VHEAD1, cgi.shFemaleHair, darkena(col, 0, 0XFF));
2205       queuepoly(VHEAD, cgi.shPFace, 0xF0000080 | UNTRANS);
2206       humanoid_eyes(V, 0xD0D000FF, darkena(col, 1, 0xFF));
2207       return true;
2208       }
2209 
2210     case moWaterElemental: {
2211       const shiftmatrix VBS = VBODY * otherbodyparts(V, watercolor(50), m, footphase);
2212       ShadowV(V, cgi.shWaterElemental);
2213       queuepoly(VBS, cgi.shWaterElemental, watercolor(0));
2214       queuepoly(VHEAD1, cgi.shFemaleHair, watercolor(100));
2215       queuepoly(VHEAD, cgi.shPFace, watercolor(200));
2216       humanoid_eyes(V, 0x0000FFFF, watercolor(150));
2217       return true;
2218       }
2219 
2220     case moFireElemental: {
2221       const shiftmatrix VBS = VBODY * otherbodyparts(V, darkena(firecolor(50), 0, 0xFF), m, footphase);
2222       ShadowV(V, cgi.shWaterElemental);
2223       queuepoly(VBS, cgi.shWaterElemental, darkena(firecolor(0), 0, 0xFF));
2224       queuepoly(VHEAD1, cgi.shFemaleHair, darkena(firecolor(100), 0, 0xFF));
2225       queuepoly(VHEAD, cgi.shPFace, darkena(firecolor(200), 0, 0xFF));
2226       humanoid_eyes(V, darkena(firecolor(200), 0, 0xFF), darkena(firecolor(50), 0, 0xFF));
2227       return true;
2228       }
2229 
2230     case moAirElemental: {
2231       const shiftmatrix VBS = VBODY * otherbodyparts(V, darkena(col, 0, 0x40), m, footphase);
2232       ShadowV(V, cgi.shWaterElemental);
2233       queuepoly(VBS, cgi.shWaterElemental, darkena(col, 0, 0x80));
2234       queuepoly(VHEAD1, cgi.shFemaleHair, darkena(col, 0, 0x80));
2235       queuepoly(VHEAD, cgi.shPFace, darkena(col, 0, 0x80));
2236       humanoid_eyes(V, 0xFFFFFFFF, darkena(col, 1, 0xFF));
2237       return true;
2238       }
2239 
2240     case moWorm: case moWormwait: case moHexSnake: {
2241       queuepoly(V, cgi.shWormHead, darkena(col, 0, 0xFF));
2242       queuepolyat(V, cgi.shWormEyes, 0xFF, PPR::ONTENTACLE_EYES);
2243       return true;
2244       }
2245 
2246     case moDragonHead: {
2247       queuepoly(V, cgi.shDragonHead, darkena(col, 0, 0xFF));
2248       queuepolyat(V, cgi.shDragonEyes, 0xFF, PPR::ONTENTACLE_EYES);
2249       int noscolor = 0xFF0000FF;
2250       queuepoly(V, cgi.shDragonNostril, noscolor);
2251       queuepoly(V * Mirror, cgi.shDragonNostril, noscolor);
2252       return true;
2253       }
2254 
2255     case moDragonTail: {
2256       queuepoly(V, cgi.shDragonSegment, darkena(col, 0, 0xFF));
2257       return true;
2258       }
2259 
2260     case moTentacle: case moTentaclewait: case moTentacleEscaping: {
2261       queuepoly(V, cgi.shTentHead, darkena(col, 0, 0xFF));
2262       ShadowV(V, cgi.shTentHead, PPR::GIANTSHADOW);
2263       return true;
2264       }
2265 
2266     case moAsteroid: {
2267       queuepoly(V, cgi.shAsteroid[1], darkena(col, 0, 0xFF));
2268       return true;
2269       }
2270 
2271     #if CAP_COMPLEX2
2272     case moAnimatedDie: case moAngryDie: {
2273       if(where)
2274         dice::draw_die(where, V, 1, darkena(col, 0, 0xFF));
2275       else
2276         queuepoly(V, cgi.shDodeca, darkena(col, 0, 0xFF));
2277       return true;
2278       }
2279     #endif
2280 
2281     default: ;
2282     }
2283 
2284   if(isPrincess(m)) goto princess;
2285 
2286   else if(isBull(m)) {
2287     ShadowV(V, cgi.shBullBody);
2288     int hoofcol = darkena(gradient(0, col, 0, .65, 1), 0, 0xFF);
2289     if(mmspatial || footphase)
2290       animallegs(VALEGS, moRagingBull, hoofcol, footphase);
2291     queuepoly(VABODY, cgi.shBullBody, darkena(gradient(0, col, 0, .80, 1), 0, 0xFF));
2292     queuepoly(VAHEAD, cgi.shBullHead, darkena(col, 0, 0xFF));
2293     queuepoly(VAHEAD, cgi.shBullHorn, darkena(0xFFFFFF, 0, 0xFF));
2294     queuepoly(VAHEAD * Mirror, cgi.shBullHorn, darkena(0xFFFFFF, 0, 0xFF));
2295     }
2296 
2297   else if(isBug(m)) {
2298     ShadowV(V, cgi.shBugBody);
2299     if(!mmspatial && !footphase)
2300       queuepoly(VABODY, cgi.shBugBody, darkena(col, 0, 0xFF));
2301     else {
2302       animallegs(VALEGS, moBug0, darkena(col, 0, 0xFF), footphase);
2303       queuepoly(VABODY, cgi.shBugAntenna, darkena(col, 1, 0xFF));
2304       }
2305     queuepoly(VABODY, cgi.shBugArmor, darkena(col, 1, 0xFF));
2306     }
2307   else if(isSwitch(m)) {
2308     queuepoly(VFISH, cgi.shJelly, darkena(col, 0, 0xD0));
2309     queuepolyat(VBODY, cgi.shJelly, darkena(col, 0, 0xD0), PPR::MONSTER_BODY);
2310     queuepolyat(VHEAD, cgi.shJelly, darkena(col, 0, 0xD0), PPR::MONSTER_HEAD);
2311     queuepolyat(VHEAD, cgi.shSlimeEyes, 0xFF, PPR::MONSTER_HEAD);
2312     }
2313   else if(isDemon(m)) {
2314     const shiftmatrix VBS = VBODY * otherbodyparts(V, darkena(col, 0, 0xC0), m, footphase);
2315     queuepoly(VBS, cgi.shPBody, darkena(col, 1, 0xC0));
2316     ShadowV(V, cgi.shPBody);
2317     int acol = col;
2318     if(xch == 'D') acol = 0xD0D0D0;
2319     queuepoly(VHEAD, cgi.shDemon, darkena(acol, 0, 0xFF));
2320     humanoid_eyes(V, 0xFF0000FF, 0xC00000FF);
2321     }
2322   else if(isMagneticPole(m)) {
2323     if(m == moNorthPole)
2324       queuepolyat(VBODY * spin(M_PI), cgi.shTentacle, 0x000000C0, PPR::TENTACLE1);
2325     queuepolyat(VBODY, cgi.shDisk, darkena(col, 0, 0xFF), PPR::MONSTER_BODY);
2326     }
2327   else if(isMetalBeast(m) || m == moBrownBug) {
2328     ShadowV(V, cgi.shTrylobite);
2329     if(!mmspatial)
2330       queuepoly(VABODY, cgi.shTrylobite, darkena(col, 1, 0xC0));
2331     else {
2332       queuepoly(VABODY, cgi.shTrylobiteBody, darkena(col, 1, 0xFF));
2333       animallegs(VALEGS, moMetalBeast, darkena(col, 1, 0xFF), footphase);
2334       }
2335     int acol = col;
2336     queuepoly(VAHEAD, cgi.shTrylobiteHead, darkena(acol, 0, 0xFF));
2337     }
2338   else if(m == moHexer) {
2339     const shiftmatrix VBS = VBODY * otherbodyparts(V, darkena(col, 1, 0xFF), m, footphase);
2340     ShadowV(V, cgi.shFemaleBody);
2341     queuepoly(VBS, cgi.shFemaleBody, darkena(0x800080, 0, 0xFF));
2342     queuepoly(VHEAD1, cgi.shWitchHair, darkena(0xFF00FF, 1, 0xFF));
2343     queuepoly(VHEAD, cgi.shPFace, darkena(0xFFFFFF, 0, 0xFF));
2344     queuepoly(VBS, cgi.shWitchDress, darkena(col, 1, 0XC0));
2345     humanoid_eyes(V, 0xF000F0FF);
2346     }
2347   else if(isWitch(m)) {
2348     const shiftmatrix VBS = VBODY * otherbodyparts(V, darkena(col, 1, 0xFF), m, footphase);
2349     int cc = 0xFF;
2350     if(m == moWitchGhost) cc = 0x85 + 120 * sintick(160);
2351     if(m == moWitchWinter && where) drawWinter(V, 0, iinf[itOrbWinter].color);
2352     if(m == moWitchFlash && where) drawFlash(V);
2353     if(m == moWitchSpeed && where) drawSpeed(V);
2354     if(m == moWitchFire) col = firecolor(0);
2355     ShadowV(V, cgi.shFemaleBody);
2356     queuepoly(VBS, cgi.shFemaleBody, darkena(col, 0, cc));
2357 //    queuepoly(cV2, ct, cgi.shPSword, darkena(col, 0, 0XFF));
2358 //    queuepoly(V, cgi.shHood, darkena(col, 0, 0XC0));
2359     if(m == moWitchFire) col = firecolor(100);
2360     queuepoly(VHEAD1, cgi.shWitchHair, darkena(col, 1, cc));
2361     if(m == moWitchFire) col = firecolor(200);
2362     queuepoly(VHEAD, cgi.shPFace, darkena(col, 0, cc));
2363     if(m == moWitchFire) col = firecolor(300);
2364     queuepoly(VBS, cgi.shWitchDress, darkena(col, 1, 0XC0));
2365     humanoid_eyes(V, 0x000000FF);
2366     }
2367 
2368   // just for the HUD glyphs...
2369   else if(isAnyIvy(m)) {
2370     queuepoly(V, cgi.shILeaf[0], darkena(col, 0, 0xFF));
2371     }
2372 
2373   else
2374     draw_ascii(V1, minf[m].glyph, asciicol, 1.5);
2375 
2376   return true;
2377 #endif
2378   }
2379 
drawMonsterTypeDH(eMonster m,cell * where,const shiftmatrix & V,color_t col,bool dh,ld footphase,color_t asciicol)2380 bool drawMonsterTypeDH(eMonster m, cell *where, const shiftmatrix& V, color_t col, bool dh, ld footphase, color_t asciicol) {
2381   dynamicval<color_t> p(poly_outline, poly_outline);
2382   if(dh) {
2383     poly_outline = OUTLINE_DEAD;
2384     darken++;
2385     }
2386   bool b = drawMonsterType(m,where,V,col, footphase, asciicol);
2387   if(dh) {
2388     darken--;
2389     }
2390   return b;
2391   }
2392 
2393 EX shiftmatrix playerV;
2394 
applyAnimation(cell * c,shiftmatrix & V,double & footphase,int layer)2395 EX bool applyAnimation(cell *c, shiftmatrix& V, double& footphase, int layer) {
2396   if(!animations[layer].count(c)) return false;
2397   animation& a = animations[layer][c];
2398 
2399   int td = ticks - a.ltick;
2400   ld aspd = td / 1000.0 * exp(vid.mspeed);
2401   ld R;
2402   again:
2403 
2404   if(sl2) {
2405     a.wherenow = slr::translate(tC0(a.wherenow));
2406     hyperpoint h = tC0(iso_inverse(a.wherenow));
2407     hyperpoint ie = slr::get_inverse_exp(shiftless(h));
2408     auto R = hypot_d(3, ie);
2409     aspd *= (1+R+(shmup::on?1:0));
2410     if(R < aspd || std::isnan(R) || std::isnan(aspd) || R > 10) {
2411       animations[layer].erase(c);
2412       return false;
2413       }
2414     a.wherenow = nisot::parallel_transport(a.wherenow, tangent_length(h, aspd));
2415     a.footphase += a.attacking == 2 ? -aspd : aspd;
2416     // todo attack animation, rotate correctly
2417     footphase = a.footphase;
2418     V = V * a.wherenow;
2419     a.ltick = ticks;
2420     return true;
2421     }
2422 
2423   if(among(a.attacking, 1, 3))
2424     R = hdist(tC0(a.attackat), tC0(a.wherenow));
2425   else
2426     R = hdist0(tC0(a.wherenow));
2427   aspd *= (1+R+(shmup::on?1:0));
2428 
2429   if(a.attacking == 3 && aspd > R) aspd = R;
2430 
2431   if((R < aspd || std::isnan(R) || std::isnan(aspd) || R > 10) && a.attacking != 3) {
2432     if(a.attacking == 1) { a.attacking = 2; goto again; }
2433     animations[layer].erase(c);
2434     return false;
2435     }
2436   else {
2437     hyperpoint wnow;
2438     if(a.attacking == 1 || a.attacking == 3)
2439       wnow = tC0(z_inverse(a.wherenow) * a.attackat);
2440     else
2441       wnow = tC0(z_inverse(a.wherenow));
2442 
2443     if(prod) {
2444       auto d = product_decompose(wnow);
2445       ld dist = d.first / R * aspd;
2446       if(abs(dist) > abs(d.first)) dist = -d.first;
2447       a.wherenow = mscale(a.wherenow, dist);
2448       /* signed_sqrt to prevent precision errors */
2449       aspd *= signed_sqrt(R*R - d.first * d.first) / R;
2450       }
2451     a.wherenow = a.wherenow * rspintox(wnow);
2452     a.wherenow = a.wherenow * xpush(aspd);
2453     if(cgflags & qAFFINE) {
2454       transmatrix T = a.wherenow;
2455       fixmatrix_euclid(T);
2456       a.wherenow = inverse(T) * a.wherenow;
2457       for(int i=0; i<MDIM; i++)
2458         a.wherenow[i] = lerp(a.wherenow[i], Id[i], aspd / R);
2459       a.wherenow = T * a.wherenow;
2460       }
2461     fixmatrix(a.wherenow);
2462     a.footphase += a.attacking == 2 ? -aspd : aspd;
2463     if(a.attacking == 3 && aspd >= R) {
2464       a.footphase = 0;
2465       hyperpoint h1 = a.wherenow * C0;
2466       a.wherenow = rgpushxto0(h1) * rspintox(h1);
2467       }
2468     footphase = a.footphase;
2469     V = V * a.wherenow;
2470     if(a.mirrored) V = V * Mirror;
2471     if(a.attacking == 2) V = V * pispin;
2472     // if(GDIM == 3) V = V * cspin(0, 2, M_PI/2);
2473     a.ltick = ticks;
2474     return true;
2475     }
2476   }
2477 
chainAngle(cell * c,shiftmatrix & V,cell * c2,double dft,const shiftmatrix & Vwhere)2478 double chainAngle(cell *c, shiftmatrix& V, cell *c2, double dft, const shiftmatrix &Vwhere) {
2479   if(!gmatrix0.count(c2)) return dft;
2480   hyperpoint h = C0;
2481   if(animations[LAYER_BIG].count(c2)) h = animations[LAYER_BIG][c2].wherenow * h;
2482   h = inverse_shift(V, Vwhere) * calc_relative_matrix(c2, c, C0) * h;
2483   return atan2(h[1], h[0]);
2484   }
2485 
2486 // equivalent to V = V * spin(-chainAngle(c,V,c2,dft));
chainAnimation(cell * c,cell * c2,shiftmatrix & V,const shiftmatrix & Vwhere,ld & length)2487 bool chainAnimation(cell *c, cell *c2, shiftmatrix& V, const shiftmatrix &Vwhere, ld& length) {
2488   hyperpoint h = C0;
2489   if(animations[LAYER_BIG].count(c2)) h = animations[LAYER_BIG][c2].wherenow * h;
2490   h = inverse_shift(V, Vwhere) * h;
2491   length = hdist0(h);
2492   V = V * rspintox(h);
2493   return true;
2494   }
2495 
2496 // push down the queue after q-th element, `down` absolute units down,
2497 // based on cell c and transmatrix V
2498 // do change the zoom factor? do change the priorities?
2499 
cellcolor(cell * c)2500 EX int cellcolor(cell *c) {
2501   if(isPlayerOn(c) || isFriendly(c)) return OUTLINE_FRIEND;
2502   if(noHighlight(c->monst)) return OUTLINE_NONE;
2503   if(c->monst) return OUTLINE_ENEMY;
2504 
2505   if(c->wall == waMirror) return c->land == laMirror ? OUTLINE_TREASURE : OUTLINE_ORB;
2506 
2507   if(c->item && !itemHiddenFromSight(c)) {
2508     int k = itemclass(c->item);
2509     if(k == IC_TREASURE)
2510       return OUTLINE_TREASURE;
2511     else if(k == IC_ORB)
2512       return OUTLINE_ORB;
2513     else
2514       return OUTLINE_OTHER;
2515     }
2516 
2517   return OUTLINE_NONE;
2518   }
2519 
taildist(cell * c)2520 int taildist(cell *c) {
2521   int s = 0;
2522   while(s < 1000 && c->mondir != NODIR && isWorm(c->monst)) {
2523     s++; c = c->move(c->mondir);
2524     }
2525   return s;
2526   }
2527 
2528 int last_wormsegment = -1;
2529 vector<vector< function<void()> > > wormsegments;
2530 
add_segment(int d,const function<void ()> & s)2531 void add_segment(int d, const function<void()>& s) {
2532   if(isize(wormsegments) <= d) wormsegments.resize(d+1);
2533   last_wormsegment = max(last_wormsegment, d);
2534   wormsegments[d].push_back(s);
2535   }
2536 
drawWormSegments()2537 void drawWormSegments() {
2538   for(int i=0; i<=last_wormsegment; i++) {
2539     for(auto& f: wormsegments[i]) f();
2540     wormsegments[i].clear();
2541     }
2542   last_wormsegment = -1;
2543   }
2544 
2545 EX bool dont_face_pc = false;
2546 
2547 EX shiftmatrix die_target;
2548 
drawMonster(const shiftmatrix & Vparam,int ct,cell * c,color_t col,color_t asciicol)2549 EX bool drawMonster(const shiftmatrix& Vparam, int ct, cell *c, color_t col, color_t asciicol) {
2550   #if CAP_SHAPES
2551 
2552   bool darkhistory = history::includeHistory && history::inkillhistory.count(c);
2553   color_t outline = OUTLINE_NONE;
2554 
2555   if(doHighlight()) {
2556     outline =
2557       (isPlayerOn(c) || isFriendly(c)) ? OUTLINE_FRIEND :
2558       noHighlight(c->monst) ? OUTLINE_NONE :
2559       OUTLINE_ENEMY;
2560     poly_outline = outline;
2561     }
2562 
2563   // highlight faraway enemies if that's needed
2564   if (vid.faraway_highlight && c->cpdist >= 6 && vid.faraway_highlight <= get_threat_level(c)) {
2565 
2566     // basic red-green oscillation
2567     color_t r = ceil(255*abs(sintick(1000, 0.25)));
2568     color_t g = ceil(255*abs(sintick(1000, 0.00)));
2569     color_t hlc = (r<<16) + (g<<8);
2570 
2571     hlc = gradient(col, hlc, 0, vid.faraway_highlight_color, 100);
2572 
2573     if(c->cpdist == 6)
2574       hlc = gradient(0, hlc, 0, 1, 1.5);
2575 
2576     addauraspecial(tC0(Vparam), hlc, 0);
2577     }
2578 
2579   bool nospins = false, nospinb = false;
2580   double footphaseb = 0, footphase = 0;
2581 
2582   shiftmatrix Vs = Vparam; nospins = applyAnimation(c, Vs, footphase, LAYER_SMALL);
2583   shiftmatrix Vb = Vparam; nospinb = applyAnimation(c, Vb, footphaseb, LAYER_BIG);
2584 //  nospin = true;
2585 
2586   eMonster m = c->monst;
2587 
2588   bool half_elliptic = elliptic && GDIM == 3 && WDIM == 2;
2589   bool mirrored = det(Vparam.T) > 0;
2590 
2591   bool res = m;
2592 
2593   if(!m) ;
2594 
2595   else if(half_elliptic && mirrored != c->monmirror && !isMimic(m)) ;
2596 
2597   else if(isAnyIvy(c) || isWorm(c)) {
2598 
2599     if((m == moHexSnake || m == moHexSnakeTail) && c->hitpoints == 2) {
2600       int d = c->mondir;
2601       if(d == NODIR)
2602         forCellIdEx(c2, i, c)
2603           if(among(c2->monst, moHexSnakeTail, moHexSnake) && c2->mondir == c->c.spin(i))
2604             d = i;
2605       if(d == NODIR) { d = hrand(c->type); createMov(c, d); }
2606       int c1 = nestcolors[pattern_threecolor(c)];
2607       int c2 = nestcolors[pattern_threecolor(c->move(d))];
2608       col = (c1 + c2); // sum works because they are dark and should be brightened
2609       }
2610 
2611     if(isDragon(c->monst) && c->stuntime == 0) col = 0xFF6000;
2612 
2613     if(GDIM == 3)
2614       addradar(Vparam, minf[m].glyph, asciicol, isFriendly(m) ? 0x00FF00FF : 0xFF0000FF);
2615 
2616     shiftmatrix Vb0 = Vb;
2617     if(c->mondir != NODIR && GDIM == 3 && isAnyIvy(c)) {
2618       queueline(tC0(Vparam), Vparam  * tC0(currentmap->adj(c, c->mondir)), (col << 8) + 0xFF, 0);
2619       }
2620     else if(c->mondir != NODIR) {
2621 
2622       if(mmmon) {
2623         ld length;
2624         cell *c2 = c->move(c->mondir);
2625 
2626         if(nospinb) {
2627           chainAnimation(c, c2, Vb, Vparam * currentmap->adj(c, c->mondir), length);
2628           }
2629         else {
2630           Vb = Vb * ddspin(c, c->mondir);
2631           length = cellgfxdist(c, c->mondir);
2632           }
2633 
2634         if(c->monmirror) Vb = Vb * Mirror;
2635 
2636         if(mapeditor::drawUserShape(Vb, mapeditor::sgMonster, c->monst, (col << 8) + 0xFF, c))
2637           return false;
2638 
2639         if(isIvy(c) || isMutantIvy(c) || c->monst == moFriendlyIvy)
2640           queuepoly(Vb, cgi.shIBranch, (col << 8) + 0xFF);
2641 /*         else if(c->monst < moTentacle && wormstyle == 0) {
2642           ShadowV(Vb, cgi.shTentacleX, PPR::GIANTSHADOW);
2643           queuepoly(mmscale(Vb, cgi.ABODY), cgi.shTentacleX, 0xFF);
2644           queuepoly(mmscale(Vb, cgi.ABODY), cgi.shTentacle, (col << 8) + 0xFF);
2645           } */
2646 //        else if(c->monst < moTentacle) {
2647 //          }
2648 
2649         else if(c->monst == moDragonHead || c->monst == moDragonTail) {
2650           char part = dragon::bodypart(c, dragon::findhead(c));
2651           if(part != '2') {
2652             queuepoly(mmscale(Vb, cgi.ABODY), cgi.shDragonSegment, darkena(col, 0, 0xFF));
2653             ShadowV(Vb, cgi.shDragonSegment, PPR::GIANTSHADOW);
2654             }
2655           }
2656         else {
2657           if(c->monst == moTentacleGhost) {
2658             hyperpoint V0 = history::on ? unshift(tC0(Vs)) : inverse_shift(cwtV, tC0(Vs));
2659             hyperpoint V1 = spintox(V0) * V0;
2660             Vs = cwtV * rspintox(V0) * rpushxto0(V1) * pispin;
2661             drawMonsterType(moGhost, c, Vs, col, footphase, asciicol);
2662             col = minf[moTentacletail].color;
2663             }
2664           /*
2665           queuepoly(mmscale(Vb, cgi.ABODY), cgi.shTentacleX, 0xFFFFFFFF);
2666           queuepoly(mmscale(Vb, cgi.ABODY), cgi.shTentacle, (col << 8) + 0xFF);
2667           ShadowV(Vb, cgi.shTentacleX, PPR::GIANTSHADOW);
2668           */
2669           bool hexsnake = c->monst == moHexSnake || c->monst == moHexSnakeTail;
2670           bool thead = c->monst == moTentacle || c->monst == moTentaclewait || c->monst == moTentacleEscaping;
2671           hpcshape& sh = hexsnake ? cgi.shWormSegment : cgi.shSmallWormSegment;
2672           ld wav = hexsnake ? 0 :
2673             c->monst < moTentacle ? 1/1.5 :
2674             1;
2675           color_t col0 = col;
2676           if(c->monst == moWorm || c->monst == moWormwait)
2677             col0 = minf[moWormtail].color;
2678           else if(thead)
2679             col0 = minf[moTentacletail].color;
2680           add_segment(taildist(c), [=] () {
2681             for(int i=11; i>=0; i--) {
2682               if(i < 3 && (c->monst == moTentacle || c->monst == moTentaclewait)) continue;
2683               if(doHighlight())
2684                 poly_outline = outline;
2685               shiftmatrix Vbx = Vb;
2686               if(WDIM == 2) Vbx = Vbx * spin(sin(M_PI * i / 6.) * wav / (i+.1));
2687               Vbx = Vbx * xpush(length * (i) / 12.0);
2688               // shiftmatrix Vbx2 = Vnext * xpush(length2 * i / 6.0);
2689               // Vbx = Vbx * rspintox(inverse(Vbx) * Vbx2 * C0) * pispin;
2690               ShadowV(Vbx, sh, PPR::GIANTSHADOW);
2691               queuepoly(mmscale(Vbx, cgi.ABODY), sh, (col0 << 8) + 0xFF);
2692               }
2693             });
2694           }
2695         }
2696 
2697       else {
2698         shiftmatrix T = Vparam * ddspin(c, c->mondir);
2699         color_t col = darkena(0x606020, 0, 0xFF);
2700         for(int u=-1; u<=1; u++)
2701           queueline(T*xspinpush0(M_PI/2, u*cgi.crossf/5), T*xspinpush(0, cgi.crossf)*xspinpush0(M_PI/2, u*cgi.crossf/5), col, 2 + vid.linequality);
2702         }
2703       }
2704 
2705     if(mmmon) {
2706       if(isAnyIvy(c)) {
2707         if(hybri) {
2708           queuepoly(Vb, cgi.shILeaf[ctof(c)], darkena(col, 0, 0xFF));
2709           for(int a=0; a<c->type-2; a++)
2710             queuepoly(Vb * spin(a * 2 * M_PI / (c->type-2)), cgi.shILeaf[2], darkena(col, 0, 0xFF));
2711           }
2712         else if(GDIM == 3) {
2713           queuepoly(face_the_player(Vb), cgi.shILeaf[1], darkena(col, 0, 0xFF));
2714           }
2715         else {
2716           if(c->monmirror) Vb = Vb * Mirror;
2717           queuepoly(mmscale(Vb, cgi.ABODY), cgi.shILeaf[ctof(c)], darkena(col, 0, 0xFF));
2718           ShadowV(Vb, cgi.shILeaf[ctof(c)], PPR::GIANTSHADOW);
2719           }
2720         }
2721       else if(m == moWorm || m == moWormwait || m == moHexSnake) {
2722         Vb = Vb * pispin;
2723         if(c->monmirror) Vb = Vb * Mirror;
2724         shiftmatrix Vbh = mmscale(Vb, cgi.AHEAD);
2725         queuepoly(Vbh, cgi.shWormHead, darkena(col, 0, 0xFF));
2726         queuepolyat(Vbh, cgi.shWormEyes, 0xFF, PPR::ONTENTACLE_EYES);
2727         ShadowV(Vb, cgi.shWormHead, PPR::GIANTSHADOW);
2728         }
2729       else if(m == moDragonHead) {
2730         if(c->monmirror) Vb = Vb * Mirror;
2731         shiftmatrix Vbh = mmscale(Vb, cgi.AHEAD);
2732         ShadowV(Vb, cgi.shDragonHead, PPR::GIANTSHADOW);
2733         queuepoly(Vbh, cgi.shDragonHead, darkena(col, c->hitpoints?0:1, 0xFF));
2734         queuepolyat(Vbh/* * pispin */, cgi.shDragonEyes, 0xFF, PPR::ONTENTACLE_EYES);
2735 
2736         int noscolor = (c->hitpoints == 1 && c->stuntime ==1) ? 0xFF0000FF : 0xFF;
2737         queuepoly(Vbh, cgi.shDragonNostril, noscolor);
2738         queuepoly(Vbh * Mirror, cgi.shDragonNostril, noscolor);
2739         }
2740       else if(m == moTentacle || m == moTentaclewait || m == moTentacleEscaping) {
2741         Vb = Vb * pispin;
2742         if(c->monmirror) Vb = Vb * Mirror;
2743         shiftmatrix Vbh = mmscale(Vb, cgi.AHEAD);
2744         queuepoly(Vbh, cgi.shTentHead, darkena(col, 0, 0xFF));
2745         ShadowV(Vb, cgi.shTentHead, PPR::GIANTSHADOW);
2746         }
2747       else if(m == moDragonTail) {
2748         cell *c2 = NULL;
2749         for(int i=0; i<c->type; i++)
2750           if(c->move(i) && isDragon(c->move(i)->monst) && c->move(i)->mondir == c->c.spin(i))
2751             c2 = c->move(i);
2752         int nd = neighborId(c, c2);
2753         char part = dragon::bodypart(c, dragon::findhead(c));
2754         if(part == 't') {
2755           if(nospinb) {
2756             ld length;
2757             chainAnimation(c, c2, Vb, Vparam * currentmap->adj(c, nd), length);
2758             Vb = Vb * pispin;
2759             }
2760           else {
2761             Vb = Vb0 * ddspin(c, nd, M_PI);
2762             }
2763           if(c->monmirror) Vb = Vb * Mirror;
2764           shiftmatrix Vbb = mmscale(Vb, cgi.ABODY);
2765           queuepoly(Vbb, cgi.shDragonTail, darkena(col, c->hitpoints?0:1, 0xFF));
2766           ShadowV(Vb, cgi.shDragonTail, PPR::GIANTSHADOW);
2767           }
2768         else if(true) {
2769           if(nospinb) {
2770             ld length;
2771             chainAnimation(c, c2, Vb, Vparam * currentmap->adj(c, nd), length);
2772             Vb = Vb * pispin;
2773             double ang = chainAngle(c, Vb, c->move(c->mondir), currentmap->spin_angle(c, c->mondir) - currentmap->spin_angle(c, nd), Vparam);
2774             ang /= 2;
2775             Vb = Vb * spin(M_PI-ang);
2776             }
2777           else {
2778             /* todo what if no spin_angle */
2779             ld hdir0 = currentmap->spin_angle(c, nd) + M_PI;
2780             ld hdir1 = currentmap->spin_angle(c, c->mondir);
2781             while(hdir1 > hdir0 + M_PI) hdir1 -= 2*M_PI;
2782             while(hdir1 < hdir0 - M_PI) hdir1 += 2*M_PI;
2783             Vb = Vb0 * spin((hdir0 + hdir1)/2 + M_PI);
2784             }
2785           if(c->monmirror) Vb = Vb * Mirror;
2786           shiftmatrix Vbb = mmscale(Vb, cgi.ABODY);
2787           if(part == 'l' || part == '2') {
2788             queuepoly(Vbb, cgi.shDragonLegs, darkena(col, c->hitpoints?0:1, 0xFF));
2789             }
2790           queuepoly(Vbb, cgi.shDragonWings, darkena(col, c->hitpoints?0:1, 0xFF));
2791           }
2792         }
2793       else {
2794         if(c->monst == moTentacletail && c->mondir == NODIR) {
2795           if(c->monmirror) Vb = Vb * Mirror;
2796           queuepoly(GDIM == 3 ? mmscale(Vb, cgi.ABODY) : Vb, cgi.shWormSegment, darkena(col, 0, 0xFF));
2797           }
2798         else if(c->mondir == NODIR) {
2799           bool hexsnake = c->monst == moHexSnake || c->monst == moHexSnakeTail;
2800           cell *c2 = NULL;
2801           for(int i=0; i<c->type; i++)
2802             if(c->move(i) && isWorm(c->move(i)->monst) && c->move(i)->mondir == c->c.spin(i))
2803               c2 = c->move(i);
2804           int nd = neighborId(c, c2);
2805           if(nospinb) {
2806             ld length;
2807             chainAnimation(c, c2, Vb, Vparam * currentmap->adj(c, nd), length);
2808             Vb = Vb * pispin;
2809             }
2810           else {
2811             Vb = Vb0 * ddspin(c, nd, M_PI);
2812             }
2813           if(c->monmirror) Vb = Vb * Mirror;
2814           shiftmatrix Vbb = mmscale(Vb, cgi.ABODY) * pispin;
2815           hpcshape& sh = hexsnake ? cgi.shWormTail : cgi.shSmallWormTail;
2816           queuepoly(Vbb, sh, darkena(col, 0, 0xFF));
2817           ShadowV(Vb, sh, PPR::GIANTSHADOW);
2818           }
2819         }
2820       }
2821     else res = res && drawMonsterType(c->monst, c, Vb, col, footphase, asciicol);
2822     }
2823 
2824   else if(isMimic(c)) {
2825     int xdir = 0, copies = 1;
2826     if(c->wall == waMirrorWall) {
2827       xdir = mirror::mirrordir(c);
2828       copies = 2;
2829       if(xdir == -1) copies = 6, xdir = 0;
2830       }
2831     for(auto& m: mirror::mirrors) if(c == m.second.at)
2832     for(int d=0; d<copies; d++) {
2833       multi::cpid = m.first;
2834       auto cw = m.second;
2835       if(d&1) cw = cw.mirrorat(xdir);
2836       if(d>=2) cw += 2;
2837       if(d>=4) cw += 2;
2838       shiftmatrix Vs = Vparam;
2839       bool mirr = cw.mirrored;
2840       if(mirrored != mirr && half_elliptic) continue;
2841       shiftmatrix T = shiftless(Id);
2842       nospins = applyAnimation(cwt.at, T, footphase, LAYER_SMALL);
2843       if(nospins)
2844         Vs = Vs * ddspin(c, cw.spin, 0) * iddspin(cwt.at, cwt.spin, 0) * unshift(T);
2845       else
2846         Vs = Vs * ddspin(c, cw.spin, 0);
2847       if(mirr) Vs = Vs * Mirror;
2848       if(inmirrorcount&1) mirr = !mirr;
2849       col = mirrorcolor(geometry == gElliptic ? det(Vs.T) < 0 : mirr);
2850       if(!mouseout() && !nospins && GDIM == 2) {
2851         shiftpoint P2 = Vs * inverse_shift(inmirrorcount ? ocwtV : cwtV, mouseh);
2852         queuestr(P2, 10, "x", 0xFF00);
2853         }
2854       if(!nospins && flipplayer) Vs = Vs * pispin;
2855 
2856       res = res && drawMonsterType(moMimic, c, Vs, col, footphase, asciicol);
2857       drawPlayerEffects(Vs, c, false);
2858       }
2859     }
2860 
2861   // illusions face randomly
2862 
2863   else if(c->monst == moIllusion) {
2864     multi::cpid = 0;
2865     if(c->monmirror) Vs = Vs * Mirror;
2866     drawMonsterType(c->monst, c, Vs, col, footphase, asciicol);
2867     drawPlayerEffects(Vs, c, false);
2868     }
2869 
2870   // wolves face the heat
2871 
2872   else if(c->monst == moWolf && c->cpdist > 1) {
2873     if(!nospins) {
2874       int d = 0;
2875       double bheat = -999;
2876       for(int i=0; i<c->type; i++) if(c->move(i) && HEAT(c->move(i)) > bheat) {
2877         bheat = HEAT(c->move(i));
2878         d = i;
2879         }
2880       Vs = Vs * ddspin(c, d, 0);
2881       }
2882     if(c->monmirror) Vs = Vs * Mirror;
2883     return drawMonsterTypeDH(m, c, Vs, col, darkhistory, footphase, asciicol);
2884     }
2885 
2886   else if(c->monst == moKrakenT) {
2887     if(c->hitpoints == 0) col = 0x404040;
2888     if(nospinb) {
2889       ld length;
2890       chainAnimation(c, c->move(c->mondir), Vb, Vparam * currentmap->adj(c, c->mondir), length);
2891       Vb = Vb * pispin;
2892       Vb = Vb * xpush(cgi.tentacle_length - cellgfxdist(c, c->mondir));
2893       }
2894     else if(NONSTDVAR) {
2895       transmatrix T = currentmap->adj(c, c->mondir);
2896       Vb = Vb * T * rspintox(tC0(iso_inverse(T))) * xpush(cgi.tentacle_length);
2897       }
2898     else {
2899       Vb = Vb * ddspin(c, c->mondir, M_PI);
2900       Vb = Vb * xpush(cgi.tentacle_length - cellgfxdist(c, c->mondir));
2901       }
2902     if(c->monmirror) Vb = Vb * Mirror;
2903 
2904       // if(ctof(c) && !masterless) Vb = Vb * xpush(hexhexdist - hcrossf);
2905       // return (!BITRUNCATED) ? tessf * gp::scale : (c->type == 6 && (i&1)) ? hexhexdist : cgi.crossf;
2906     res = res && drawMonsterTypeDH(m, c, Vb, col, darkhistory, footphase, asciicol);
2907     }
2908 
2909   // golems, knights, and hyperbugs don't face the player (mondir-controlled)
2910   // also whatever in the lineview mode, and whatever in the quotient geometry
2911 
2912   else if(isDie(c->monst)) {
2913     transmatrix U = inverse_shift(Vparam, Vs);
2914     U = rgpushxto0(tC0(U));
2915     die_target = Vparam;
2916     res = res && drawMonsterTypeDH(m, c, Vparam * U, col, darkhistory, footphase, asciicol);
2917     }
2918 
2919   else if((hasFacing(c) && c->mondir != NODIR) || history::on || quotient || dont_face_pc) {
2920     if(c->monst == moKrakenH) Vs = Vb, nospins = nospinb;
2921     if(!nospins && c->mondir < c->type) Vs = Vs * ddspin(c, c->mondir, M_PI);
2922     if(c->monst == moPair) Vs = Vs * xpush(-.12);
2923     if(c->monmirror) Vs = Vs * Mirror;
2924     if(isFriendly(c)) drawPlayerEffects(Vs, c, false);
2925     res = res && drawMonsterTypeDH(m, c, Vs, col, darkhistory, footphase, asciicol);
2926     }
2927 
2928   else {
2929     // other monsters face the player
2930 
2931     if(!nospins) {
2932       shiftmatrix& where = (c->monst == moMirrorSpirit && inmirrorcount) ? ocwtV : cwtV;
2933       if(WDIM == 2 || prod) {
2934         hyperpoint V0 = inverse_shift(Vs, tC0(where));
2935         ld z = 0;
2936         if(prod) {
2937           auto d = product_decompose(V0);
2938           z = d.first;
2939           V0 = d.second;
2940           }
2941 
2942         if(hypot_d(2, tC0(unshift(Vs))) > 1e-3) {
2943           Vs = Vs * rspintox(V0);
2944           if(prod) Vs = mscale(Vs, z);
2945           }
2946         }
2947       else if(!sl2) {
2948         hyperpoint V0 = inverse_shift(Vs, tC0(where));
2949         Vs = Vs * rspintox(V0);
2950         // cwtV * rgpushxto0(inverse(cwtV) * tC0(Vs));
2951         }
2952       if(c->monst == moHunterChanging)
2953         Vs = Vs * (hybri ? spin(M_PI) : cspin(WDIM-2, WDIM-1, M_PI));
2954       }
2955     if(c->monmirror) Vs = Vs * Mirror;
2956 
2957     if(c->monst == moShadow)
2958       multi::cpid = c->hitpoints;
2959 
2960     res = res && drawMonsterTypeDH(m, c, Vs, col, darkhistory, footphase, asciicol);
2961     }
2962 
2963   for(int i=0; i<numplayers(); i++) if(c == playerpos(i) && !shmup::on && mapeditor::drawplayer &&
2964     !(hardcore && !canmove)) {
2965     bool mirr = multi::players > 1 ? multi::player[i].mirrored : cwt.mirrored;
2966     if(half_elliptic && mirr != mirrored) continue;
2967     if(!nospins) {
2968       Vs = playerV;
2969       if(multi::players > 1 ? multi::flipped[i] : flipplayer) Vs = Vs * pispin;
2970       }
2971     else {
2972       if(mirr) Vs = Vs * Mirror;
2973       }
2974     multi::cpid = i;
2975 
2976     asciicol = getcs().uicolor >> 8;
2977 
2978     drawPlayerEffects(Vs, c, true);
2979     if(inmirrorcount && !mouseout() && !nospins && GDIM == 2) {
2980       hyperpoint h = inverse_shift(ocwtV, mouseh);
2981       if(flipplayer) h = pispin * h;
2982       shiftpoint P2 = Vs * h;
2983       queuestr(P2, 10, "x", 0xFF00);
2984       }
2985 
2986     if(hide_player()) {
2987       first_cell_to_draw = false;
2988       if(WDIM == 2 && GDIM == 3) {
2989         drawPlayer(moPlayer, c, Vs, col, footphase, true);
2990         res = true;
2991         }
2992       }
2993 
2994     else if(isWorm(c->monst)) {
2995       ld depth = geom3::factor_to_lev(wormhead(c) == c ? cgi.AHEAD : cgi.ABODY);
2996       footphase = 0;
2997       int q = isize(ptds);
2998       res = res | drawMonsterType(moPlayer, c, Vs, col, footphase, asciicol);
2999       pushdown(c, q, Vs, -depth, true, false);
3000       }
3001 
3002     else res = res | drawMonsterType(moPlayer, c, Vs, col, footphase, asciicol);
3003     }
3004 #endif
3005   return res;
3006   }
3007 
3008 #define AURA 180
3009 
3010 array<array<int,4>,AURA+1> aurac;
3011 
3012 int haveaura_cached;
3013 
3014 /** 0 = no aura, 1 = standard aura, 2 = Joukowsky aura */
haveaura()3015 EX int haveaura() {
3016   if(!(vid.aurastr>0 && !svg::in && (auraNOGL || vid.usingGL))) return 0;
3017   if(vrhr::active()) return 0;
3018   if(sphere && mdAzimuthalEqui()) return 0;
3019   if(among(pmodel, mdJoukowsky, mdJoukowskyInverted) && hyperbolic && pconf.model_transition < 1)
3020     return 2;
3021   if(pmodel == mdFisheye) return 1;
3022   return pmodel == mdDisk && (!sphere || pconf.alpha > 10) && !euclid;
3023   }
3024 
3025 vector<pair<int, int> > auraspecials;
3026 
3027 int auramemo;
3028 
clearaura()3029 EX void clearaura() {
3030   haveaura_cached = haveaura();
3031   if(!haveaura_cached) return;
3032   for(int a=0; a<AURA; a++) for(int b=0; b<4; b++)
3033     aurac[a][b] = 0;
3034   auraspecials.clear();
3035   auramemo = 128 * 128 / vid.aurastr;
3036   }
3037 
apply_joukowsky_aura(shiftpoint & h)3038 void apply_joukowsky_aura(shiftpoint& h) {
3039   if(haveaura_cached == 2)  {
3040     hyperpoint ret;
3041     applymodel(h, ret);
3042     h.h = ret;
3043     }
3044   if(nonisotropic) {
3045     h.h = lp_apply(inverse_exp(h, pfNO_DISTANCE));
3046     }
3047   }
3048 
addauraspecial(shiftpoint h,color_t col,int dir)3049 EX void addauraspecial(shiftpoint h, color_t col, int dir) {
3050   if(!haveaura_cached) return;
3051   apply_joukowsky_aura(h);
3052   int r = int(2*AURA + dir + atan2(h[1], h[0]) * AURA / 2 / M_PI) % AURA;
3053   auraspecials.emplace_back(r, col);
3054   }
3055 
addaura(shiftpoint h,color_t col,int fd)3056 EX void addaura(shiftpoint h, color_t col, int fd) {
3057   if(!haveaura_cached) return;
3058   apply_joukowsky_aura(h);
3059 
3060   int r = int(2*AURA + atan2(h[1], h[0]) * AURA / 2 / M_PI) % AURA;
3061   aurac[r][3] += auramemo << fd;
3062   col = darkened(col);
3063   aurac[r][0] += (col>>16)&255;
3064   aurac[r][1] += (col>>8)&255;
3065   aurac[r][2] += (col>>0)&255;
3066   }
3067 
sumaura(int v)3068 void sumaura(int v) {
3069   int auc[AURA];
3070   for(int t=0; t<AURA; t++) auc[t] = aurac[t][v];
3071   int val = 0;
3072   if(vid.aurasmoothen < 1) vid.aurasmoothen = 1;
3073   if(vid.aurasmoothen > AURA) vid.aurasmoothen = AURA;
3074   int SMO = vid.aurasmoothen;
3075   for(int t=0; t<SMO; t++) val += auc[t];
3076   for(int t=0; t<AURA; t++) {
3077     int tt = (t + SMO/2) % AURA;
3078     aurac[tt][v] = val;
3079     val -= auc[t];
3080     val += auc[(t+SMO) % AURA];
3081     }
3082   aurac[AURA][v] = aurac[0][v];
3083   }
3084 
3085 #if CAP_GL
3086 vector<glhr::colored_vertex> auravertices;
3087 #endif
3088 
drawaura()3089 EX void drawaura() {
3090   DEBBI(DF_GRAPH, ("draw aura"));
3091   if(!haveaura()) return;
3092   if(vid.stereo_mode) return;
3093   double rad = current_display->radius;
3094   if(sphere && !mdAzimuthalEqui()) rad /= sqrt(pconf.alpha*pconf.alpha - 1);
3095   if(hyperbolic && pmodel == mdFisheye) {
3096     ld h = 1;
3097     h /= pconf.fisheye_param;
3098     ld nrad = h / sqrt(2 + h*h);
3099     rad *= nrad;
3100     }
3101 
3102   for(int v=0; v<4; v++) sumaura(v);
3103   for(auto& p: auraspecials) {
3104     int r = p.first;
3105     aurac[r][3] = auramemo;
3106     for(int k=0; k<3; k++) aurac[r][k] = (p.second >> (16-8*k)) & 255;
3107     }
3108 
3109 #if CAP_SDL || CAP_GL
3110   ld bak[3];
3111   bak[0] = ((backcolor>>16)&255)/255.;
3112   bak[1] = ((backcolor>>8)&255)/255.;
3113   bak[2] = ((backcolor>>0)&255)/255.;
3114 #endif
3115 
3116 #if CAP_SDL
3117   if(!vid.usingGL) {
3118     SDL_LockSurface(s);
3119     for(int y=0; y<vid.yres; y++)
3120     for(int x=0; x<vid.xres; x++) {
3121 
3122       ld hx = (x * 1. - current_display->xcenter) / rad;
3123       ld hy = (y * 1. - current_display->ycenter) / rad / pconf.stretch;
3124 
3125       if(pconf.camera_angle) camrotate(hx, hy);
3126 
3127       ld fac = sqrt(hx*hx+hy*hy);
3128       if(fac < 1) continue;
3129       ld dd = log((fac - .99999) / .00001);
3130       ld cmul = 1 - dd/10.;
3131       if(cmul>1) cmul=1;
3132       if(cmul<0) cmul=0;
3133 
3134       ld alpha = AURA * atan2(hx,hy) / (2 * M_PI);
3135       if(alpha<0) alpha += AURA;
3136       if(alpha >= AURA) alpha -= AURA;
3137 
3138       int rm = int(alpha);
3139       ld fr = alpha-rm;
3140 
3141       if(rm<0 || rm >= AURA) continue;
3142 
3143       color_t& p = qpixel(s, x, y);
3144       for(int c=0; c<3; c++) {
3145         ld c1 = aurac[rm][2-c] / (aurac[rm][3]+.1);
3146         ld c2 = aurac[rm+1][2-c] / (aurac[rm+1][3]+.1);
3147         const ld one = 1;
3148         part(p, c) = int(255 * min(one, bak[2-c] + cmul * ((c1 + fr * (c2-c1) - bak[2-c]))));
3149         }
3150       }
3151     SDL_UnlockSurface(s);
3152     return;
3153     }
3154 #endif
3155 
3156 #if CAP_GL
3157   float cx[AURA+1][11][5];
3158 
3159   double facs[11] = {1, 1.01, 1.02, 1.04, 1.08, 1.70, 1.95, 1.5, 2, 6, 10};
3160   double cmul[11] = {1,   .8,  .7,  .6,  .5,  .16,  .12,  .08,  .07,  .06, 0};
3161   double d2[11] = {0, 2, 4, 6.5, 7, 7.5, 8, 8.5, 9, 9.5, 10};
3162 
3163   for(int d=0; d<11; d++) {
3164     double dd = d2[d];
3165     cmul[d] = (1- dd/10.);
3166     facs[d] = .99999 +  .00001 * exp(dd);
3167     }
3168   facs[10] = 10;
3169   cmul[1] = cmul[0];
3170 
3171   bool inversion = pconf.alpha <= -1 || pmodel == mdJoukowsky;
3172   bool joukowsky = among(pmodel, mdJoukowskyInverted, mdJoukowsky) && hyperbolic && pconf.model_transition < 1;
3173 
3174   for(int r=0; r<=AURA; r++) for(int z=0; z<11; z++) {
3175     float rr = (M_PI * 2 * r) / AURA;
3176     float rad0 = inversion ? rad / facs[z] : rad * facs[z];
3177     int rm = r % AURA;
3178     ld c = cos(rr);
3179     ld s = sin(rr);
3180 
3181     if(joukowsky) {
3182       ld c1 = c, s1 = s;
3183       if(inversion)
3184         models::apply_orientation(s1, c1);
3185       else
3186         models::apply_orientation(c1, s1);
3187 
3188       ld& mt = pconf.model_transition;
3189       ld mt2 = 1 - mt;
3190 
3191       ld m = sqrt(c1*c1 + s1*s1 / mt2 / mt2);
3192       m *= 2;
3193       if(inversion) rad0 /= m;
3194       else rad0 *= m;
3195       }
3196 
3197     ld x = rad0 * c;
3198     ld y = rad0 * s;
3199 
3200     if(pconf.camera_angle) {
3201       ld z = rad0;
3202 
3203       ld cam = pconf.camera_angle * degree;
3204       GLfloat cc = cos(cam);
3205       GLfloat ss = sin(cam);
3206 
3207       tie(y, z) = make_pair(y * cc - z * ss, z * cc + y * ss);
3208       x *= rad0 / z;
3209       y *= rad0 / z;
3210       }
3211     cx[r][z][0] = x;
3212     cx[r][z][1] = y * pconf.stretch;
3213 
3214     for(int u=0; u<3; u++)
3215       cx[r][z][u+2] = bak[u] + (aurac[rm][u] / (aurac[rm][3]+.1) - bak[u]) * cmul[z];
3216     }
3217 
3218   auravertices.clear();
3219   for(int r=0; r<AURA; r++) for(int z=0;z<10;z++) {
3220     for(int c=0; c<6; c++) {
3221       int br = (c == 1 || c == 3 || c == 5) ? r+1 : r;
3222       int bz = (c == 2 || c == 4 || c == 5) ? z+1 : z;
3223       auravertices.emplace_back(
3224         cx[br][bz][0], cx[br][bz][1], cx[br][bz][2], cx[br][bz][3], cx[br][bz][4]
3225         );
3226       }
3227     }
3228   glflush();
3229   current_display->next_shader_flags = GF_VARCOLOR;
3230   dynamicval<eModel> m(pmodel, mdPixel);
3231   current_display->set_all(0, 0);
3232   glhr::id_modelview();
3233   glhr::prepare(auravertices);
3234   glhr::set_depthtest(false);
3235   glDrawArrays(GL_TRIANGLES, 0, isize(auravertices));
3236 #endif
3237   }
3238 
3239 // int fnt[100][7];
3240 
bugsNearby(cell * c,int dist=2)3241 bool bugsNearby(cell *c, int dist = 2) {
3242   if(!(havewhat&HF_BUG)) return false;
3243   if(isBug(c)) return true;
3244   if(dist) for(int t=0; t<c->type; t++) if(c->move(t) && bugsNearby(c->move(t), dist-1)) return true;
3245   return false;
3246   }
3247 
3248 EX colortable minecolors = {
3249   0xFFFFFF, 0xF0, 0xF060, 0xF00000,
3250   0x60, 0x600000, 0x00C0C0, 0x000000, 0x808080, 0xFFD500
3251   };
3252 
3253 EX colortable distcolors = {
3254   0xFFFFFF, 0xF0, 0xF060, 0xF00000,
3255   0xA0A000, 0xA000A0, 0x00A0A0, 0xFFD500
3256   };
3257 
3258 EX const char* minetexts[8] = {
3259   "No mines next to you.",
3260   "A mine is next to you!",
3261   "Two mines next to you!",
3262   "Three mines next to you!",
3263   "Four mines next to you!",
3264   "Five mines next to you!",
3265   "Six mines next to you!",
3266   "Seven mines next to you!"
3267   };
3268 
countMinesAround(cell * c)3269 EX int countMinesAround(cell *c) {
3270   int mines = 0;
3271   for(cell *c2: adj_minefield_cells(c))
3272     if(c2->wall == waMineMine)
3273       mines++;
3274   return mines;
3275   }
3276 
applyPatterndir(cell * c,const patterns::patterninfo & si)3277 EX transmatrix applyPatterndir(cell *c, const patterns::patterninfo& si) {
3278   if(NONSTDVAR || bt::in()) return Id;
3279   transmatrix V = ddspin(c, si.dir, M_PI);
3280   if(si.reflect) V = V * Mirror;
3281   if(euclid) return V;
3282   return V * iddspin(c, 0, M_PI);
3283   }
3284 
applyDowndir(cell * c,const cellfunction & cf)3285 EX transmatrix applyDowndir(cell *c, const cellfunction& cf) {
3286   return ddspin(c, patterns::downdir(c, cf), M_PI);
3287   }
3288 
draw_movement_arrows(cell * c,const transmatrix & V,int df)3289 void draw_movement_arrows(cell *c, const transmatrix& V, int df) {
3290 
3291   if(viewdists) return;
3292 
3293   string keylist = "";
3294   const ld keysize = .6;
3295 
3296   color_t col = getcs().uicolor;
3297 
3298   for(int d=0; d<8; d++) {
3299 
3300     movedir md = vectodir(spin(-d * M_PI/4) * smalltangent());
3301     cellwalker xc = cwt + md.d;
3302     if(xc.spin != df) continue;
3303     xc += wstep;
3304     if(xc.at == c) {
3305       transmatrix fixrot = sphereflip * rgpushxto0(sphereflip * tC0(V));
3306       // make it more transparent
3307       col -= (col & 0xFF) >> 1;
3308       poly_outline = OUTLINE_DEFAULT;
3309 
3310       char key = 0;
3311       if(vid.axes >= 5)
3312         key = (vid.axes == 5 ? keys_wasd : keys_vi)[d];
3313 
3314       if(vid.axes >= 5) keylist += key;
3315       else
3316         queuepoly(shiftless(fixrot * spin(-d * M_PI/4)), cgi.shArrow, col);
3317 
3318       if((c->type & 1) && (isStunnable(c->monst) || isPushable(c->wall))) {
3319         transmatrix Centered = rgpushxto0(unshift(tC0(cwtV)));
3320         int sd = md.subdir;
3321 
3322         transmatrix T = iso_inverse(Centered) * rgpushxto0(Centered * tC0(V)) * rspintox(Centered*tC0(V)) * spin(-sd * M_PI/S7) * xpush(0.2);
3323 
3324         if(vid.axes >= 5)
3325           queuestr(shiftless(T), keysize, s0 + key, col >> 8, 1);
3326 
3327         else
3328           queuepoly(shiftless(T), cgi.shArrow, col);
3329         }
3330       else if(!confusingGeometry()) break;
3331       }
3332     }
3333   if(keylist != "") queuestr(shiftless(V), keysize, keylist, col >> 8, 1);
3334   }
3335 
celldistAltPlus(cell * c)3336 EX int celldistAltPlus(cell *c) { return 1000000 + celldistAlt(c); }
3337 
drawstaratvec(double dx,double dy)3338 bool drawstaratvec(double dx, double dy) {
3339   return dx*dx+dy*dy > .05;
3340   }
3341 
reptilecolor(cell * c)3342 EX color_t reptilecolor(cell *c) {
3343   int i;
3344 
3345   if(arcm::in())
3346     i = c->master->rval0 & 3;
3347   else {
3348     i = zebra40(c);
3349 
3350     if(!euclid) {
3351       if(i >= 4 && i < 16) i = 0;
3352       else if(i >= 16 && i < 28) i = 1;
3353       else if(i >= 28 && i < 40) i = 2;
3354       else i = 3;
3355       }
3356     }
3357 
3358   color_t reptilecolors[4] = {0xe3bb97, 0xc2d1b0, 0xebe5cb, 0xA0A0A0};
3359   return reptilecolors[i];
3360   }
3361 
wavefun(ld x)3362 ld wavefun(ld x) {
3363   return sin(x);
3364   /* x /= (2*M_PI);
3365   x -= (int) x;
3366   if(x > .5) return (x-.5) * 2;
3367   else return 0; */
3368   }
3369 
3370 // Color components in nestcolors must be less than 0x80 (for addition in drawMonster for Rock Snakes)
3371 // and must be divisible by 4 (for brightening of raised cells in celldrawer::setcolors)
3372 EX colortable nestcolors = { 0x7C0000, 0x007C00, 0x00007C, 0x404040, 0x700070, 0x007070, 0x707000, 0x606060 };
3373 
3374 color_t floorcolors[landtypes];
3375 
init_floorcolors()3376 EX void init_floorcolors() {
3377   for(int i=0; i<landtypes; i++)
3378     floorcolors[i] = linf[i].color;
3379 
3380   floorcolors[laDesert] = 0xEDC9AF;
3381   floorcolors[laKraken] = 0x20A020;
3382   floorcolors[laDocks] = 0x202020;
3383   floorcolors[laCA] = 0x404040;
3384   floorcolors[laMotion] = 0xF0F000;
3385   floorcolors[laGraveyard] = 0x107010;
3386   floorcolors[laWineyard] = 0x006000;
3387   floorcolors[laLivefjord] = 0x306030;
3388 
3389   floorcolors[laMinefield] = 0x80A080;
3390   floorcolors[laCaribbean] = 0x006000;
3391 
3392   floorcolors[laAlchemist] = 0x202020;
3393 
3394   floorcolors[laRlyeh] = 0x004080;
3395   floorcolors[laHell] = 0xC00000;
3396   floorcolors[laCrossroads] = 0xFF0000;
3397   floorcolors[laJungle] = 0x008000;
3398 
3399   floorcolors[laZebra] = 0xE0E0E0;
3400 
3401   floorcolors[laCaves] = 0x202020;
3402   floorcolors[laEmerald] = 0x202020;
3403   floorcolors[laDeadCaves] = 0x202020;
3404 
3405   floorcolors[laPalace] = 0x806020;
3406 
3407   floorcolors[laHunting] = 0x40E0D0 / 2;
3408 
3409   floorcolors[laBlizzard] = 0x5050C0;
3410   floorcolors[laCocytus] = 0x80C0FF;
3411   floorcolors[laIce] = 0x8080FF;
3412   floorcolors[laCamelot] = 0xA0A0A0;
3413 
3414   floorcolors[laOvergrown] = 0x00C020;
3415   floorcolors[laClearing] = 0x60E080;
3416   floorcolors[laHaunted] = 0x609F60;
3417   floorcolors[laCursed] = 0x481848;
3418   floorcolors[laDice] = 0xC0C0FF;
3419 
3420   floorcolors[laMirror] = floorcolors[laMirrorWall] = floorcolors[laMirrorOld] = 0x808080;
3421   }
3422 
magma_color(int id)3423 EX color_t magma_color(int id) {
3424   if(id == 95/4-1) return 0x200000;
3425   else if(id == 95/4) return 0x100000;
3426   else if(id < 48/4) return gradient(0xF0F000, 0xF00000, 0, id, 48/4);
3427   else if(id < 96/4) return gradient(0xF00000, 0x400000, 48/4, id, 95/4-2);
3428   else return winf[waMagma].color;
3429   }
3430 
noAdjacentChasms(cell * c)3431 bool noAdjacentChasms(cell *c) {
3432   forCellEx(c2, c) if(c2->wall == waChasm) return false;
3433   return true;
3434   }
3435 
3436 // does the current geometry allow nice duals
has_nice_dual()3437 EX bool has_nice_dual() {
3438   #if CAP_IRR
3439   if(IRREGULAR) return irr::bitruncations_performed > 0;
3440   #endif
3441   #if CAP_ARCM
3442   if(arcm::in()) return geosupport_football() >= 2;
3443   #endif
3444   if(bt::in()) return false;
3445   if(BITRUNCATED) return true;
3446   if(a4) return false;
3447   if((S7 & 1) == 0) return true;
3448   if(PURE) return false;
3449   #if CAP_GP
3450   return (gp::param.first + gp::param.second * 2) % 3 == 0;
3451   #else
3452   return false;
3453   #endif
3454   }
3455 
3456 // does the current geometry allow nice duals
is_nice_dual(cell * c)3457 EX bool is_nice_dual(cell *c) {
3458   return c->land == laDual && has_nice_dual();
3459   }
3460 
use_swapped_duals()3461 EX bool use_swapped_duals() {
3462   return (euclid && !a4) || GOLDBERG;
3463   }
3464 
3465 #if CAP_SHAPES
floorShadow(cell * c,const shiftmatrix & V,color_t col)3466 EX void floorShadow(cell *c, const shiftmatrix& V, color_t col) {
3467   if(model_needs_depth() || noshadow)
3468     return; // shadows break the depth testing
3469   dynamicval<color_t> p(poly_outline, OUTLINE_TRANS);
3470   if(qfi.shape) {
3471     queuepolyat(V * qfi.spin * cgi.shadowmulmatrix, *qfi.shape, col, PPR::WALLSHADOW);
3472     }
3473   else if(qfi.usershape >= 0)
3474     mapeditor::drawUserShape(V * qfi.spin * cgi.shadowmulmatrix, mapeditor::sgFloor, qfi.usershape, col, c, PPR::WALLSHADOW);
3475   else
3476     draw_shapevec(c, V, qfi.fshape->shadow, col, PPR::WALLSHADOW);
3477   }
3478 
use_warp_graphics()3479 EX bool use_warp_graphics() {
3480   if(shmup::on) return false;
3481   if(geosupport_football() != 2) return false;
3482   if(ls::chaoticity() >= 75) return false;
3483   return true;
3484   }
3485 
escherSidewall(cell * c,int sidepar,const shiftmatrix & V,color_t col)3486 EX void escherSidewall(cell *c, int sidepar, const shiftmatrix& V, color_t col) {
3487   if(sidepar >= SIDE_SLEV && sidepar <= SIDE_SLEV+2) {
3488     int sl = sidepar - SIDE_SLEV;
3489     for(int z=1; z<=4; z++) if(z == 1 || (z == 4 && detaillevel == 2))
3490       draw_qfi(c, mscale(V, zgrad0(cgi.slev * sl, cgi.slev * (sl+1), z, 4)), col, PPR::REDWALL-4+z+4*sl);
3491     }
3492   else if(sidepar == SIDE_WALL) {
3493     const int layers = 2 << detaillevel;
3494     for(int z=1; z<layers; z++)
3495       draw_qfi(c, mscale(V, zgrad0(0, geom3::actual_wall_height(), z, layers)), col, PPR::WALL3+z-layers);
3496     }
3497   else if(sidepar == SIDE_LAKE) {
3498     const int layers = 1 << (detaillevel-1);
3499     if(detaillevel) for(int z=0; z<layers; z++)
3500       draw_qfi(c, mscale(V, zgrad0(-vid.lake_top, 0, z, layers)), col, PPR::FLOOR+z-layers);
3501     }
3502   else if(sidepar == SIDE_LTOB) {
3503     const int layers = 1 << (detaillevel-1);
3504     if(detaillevel) for(int z=0; z<layers; z++)
3505       draw_qfi(c, mscale(V, zgrad0(-vid.lake_bottom, -vid.lake_top, z, layers)), col, PPR::INLAKEWALL+z-layers);
3506     }
3507   else if(sidepar == SIDE_BTOI) {
3508     const int layers = 1 << detaillevel;
3509     draw_qfi(c, mscale(V, cgi.INFDEEP), col, PPR::MINUSINF);
3510     for(int z=1; z<layers; z++)
3511       draw_qfi(c, mscale(V, zgrad0(-vid.lake_bottom, -vid.lake_top, -z, 1)), col, PPR::LAKEBOTTOM+z-layers);
3512     }
3513   }
3514 
placeSidewall(cell * c,int i,int sidepar,const shiftmatrix & V,color_t col)3515 EX bool placeSidewall(cell *c, int i, int sidepar, const shiftmatrix& V, color_t col) {
3516 
3517   if(!qfi.fshape || !qfi.fshape->is_plain || !cgi.validsidepar[sidepar] || qfi.usershape >= 0) if(GDIM == 2) {
3518     escherSidewall(c, sidepar, V, col);
3519     return true;
3520     }
3521   if(!qfi.fshape) return true;
3522 
3523   if(qfi.fshape == &cgi.shBigTriangle && pseudohept(c->move(i))) return false;
3524   if(qfi.fshape == &cgi.shTriheptaFloor && !pseudohept(c) && !pseudohept(c->move(i))) return false;
3525 
3526   PPR prio;
3527   /* if(mirr) prio = PPR::GLASS - 2;
3528   else */ if(sidepar == SIDE_WALL) prio = PPR::WALL3 - 2;
3529   else if(sidepar == SIDE_WTS3) prio = PPR::WALL3 - 2;
3530   else if(sidepar == SIDE_LAKE) prio = PPR::LAKEWALL;
3531   else if(sidepar == SIDE_LTOB) prio = PPR::INLAKEWALL;
3532   else if(sidepar == SIDE_BTOI) prio = PPR::BELOWBOTTOM;
3533   else if(sidepar == SIDE_ASHA) prio = PPR::ASHALLOW;
3534   else if(sidepar == SIDE_BSHA) prio = PPR::BSHALLOW;
3535   else prio = PPR::REDWALL-2+4*(sidepar-SIDE_SLEV);
3536 
3537   dynamicval<bool> ncor(approx_nearcorner, true);
3538   shiftmatrix V2 = V * ddspin_side(c, i);
3539 
3540   if(NONSTDVAR || !standard_tiling()) {
3541     #if CAP_ARCM
3542     if(arcm::in() && !PURE)
3543       i = gmod(i + arcm::parent_index_of(c->master)/DUALMUL, c->type);
3544     #endif
3545     if(currentmap->strict_tree_rules()) {
3546       i = rulegen::get_arb_dir(c, i);
3547       }
3548     draw_shapevec(c, V2, qfi.fshape->gpside[sidepar][i], col, prio);
3549     return false;
3550     }
3551 
3552   queuepolyat(V2, qfi.fshape->side[sidepar][shvid(c)], col, prio);
3553   return false;
3554   }
3555 #endif
3556 
openorsafe(cell * c)3557 bool openorsafe(cell *c) {
3558   #if CAP_COMPLEX2
3559   return c->wall == waMineOpen || mine::marked_safe(c);
3560   #else
3561   return false;
3562   #endif
3563   }
3564 
3565 #define Dark(x) darkena(x,0,0xFF)
3566 
3567 EX color_t stdgridcolor = 0x202020FF;
3568 
gridcolor(cell * c1,cell * c2)3569 EX int gridcolor(cell *c1, cell *c2) {
3570   if(cmode & sm::DRAW && !mapeditor::drawing_tool) return Dark(forecolor);
3571   if(!c2)
3572     return 0x202020 >> darken;
3573   int rd1 = rosedist(c1), rd2 = rosedist(c2);
3574   if(rd1 != rd2) {
3575     int r = rd1+rd2;
3576     if(r == 1) return Dark(0x802020);
3577     if(r == 3) return Dark(0xC02020);
3578     if(r == 2) return Dark(0xF02020);
3579     }
3580   if(chasmgraph(c1) != chasmgraph(c2) && c1->land != laAsteroids && c2->land != laAsteroids)
3581     return Dark(0x808080);
3582   if(c1->land == laAlchemist && c2->land == laAlchemist && c1->wall != c2->wall && !c1->item && !c2->item)
3583     return Dark(0xC020C0);
3584   if((c1->land == laWhirlpool || c2->land == laWhirlpool) && (celldistAlt(c1) != celldistAlt(c2)))
3585     return Dark(0x2020A0);
3586   if(c1->land == laMinefield && c2->land == laMinefield && (openorsafe(c1) != openorsafe(c2)))
3587     return Dark(0xA0A0A0);
3588   if(!darken) return stdgridcolor;
3589   return Dark(0x202020);
3590   }
3591 
3592 #if CAP_SHAPES
pushdown(cell * c,int & q,const shiftmatrix & V,double down,bool rezoom,bool repriority)3593 EX void pushdown(cell *c, int& q, const shiftmatrix &V, double down, bool rezoom, bool repriority) {
3594 
3595   #if MAXMDIM >= 4
3596   if(GDIM == 3) {
3597     for(int i=q; i<isize(ptds); i++) {
3598       auto pp = dynamic_cast<dqi_poly*> (&*ptds[q++]);
3599       if(!pp) continue;
3600       auto& ptd = *pp;
3601       ptd.V = ptd.V * zpush(+down);
3602       }
3603     return;
3604     }
3605   #endif
3606 
3607   // since we might be changing priorities, we have to make sure that we are sorting correctly
3608   if(down > 0 && repriority) {
3609     int qq = q+1;
3610     while(qq < isize(ptds))
3611       if(qq > q && ptds[qq]->prio < ptds[qq-1]->prio) {
3612         swap(ptds[qq], ptds[qq-1]);
3613         qq--;
3614         }
3615       else qq++;
3616     }
3617 
3618   while(q < isize(ptds)) {
3619     auto pp = dynamic_cast<dqi_poly*> (&*ptds[q++]);
3620     if(!pp) continue;
3621     auto& ptd = *pp;
3622 
3623     double z2;
3624 
3625     double z = zlevel(tC0(ptd.V.T));
3626     double lev = geom3::factor_to_lev(z);
3627     double nlev = lev - down;
3628 
3629     double xyscale = rezoom ? geom3::scale_at_lev(lev) / geom3::scale_at_lev(nlev) : 1;
3630     z2 = geom3::lev_to_factor(nlev);
3631     double zscale = z2 / z;
3632 
3633     // xyscale = xyscale + (zscale-xyscale) * (1+sin(ticks / 1000.0)) / 2;
3634 
3635     ptd.V.T = xyzscale( V.T, xyscale*zscale, zscale)
3636       * z_inverse(V.T) * unshift(ptd.V, V.shift);
3637 
3638     if(!repriority) ;
3639     else if(nlev < -vid.lake_bottom-1e-3) {
3640       ptd.prio = PPR::BELOWBOTTOM_FALLANIM;
3641       if(c->wall != waChasm)
3642         ptd.color = 0; // disappear!
3643       }
3644     else if(nlev < -vid.lake_top-1e-3)
3645       ptd.prio = PPR::INLAKEWALL_FALLANIM;
3646     else if(nlev < 0)
3647       ptd.prio = PPR::LAKEWALL_FALLANIM;
3648     }
3649   }
3650 #endif
3651 
3652 // 1 : (floor, water); 2 : (water, bottom); 4 : (bottom, inf)
3653 
shallow(cell * c)3654 EX int shallow(cell *c) {
3655   if(cellUnstable(c)) return 0;
3656   else if(
3657     c->wall == waReptile) return 1;
3658   else if(c->wall == waReptileBridge ||
3659     c->wall == waGargoyleFloor ||
3660     c->wall == waGargoyleBridge ||
3661     c->wall == waTempFloor ||
3662     c->wall == waTempBridge ||
3663     c->wall == waPetrifiedBridge ||
3664     c->wall == waFrozenLake)
3665     return 5;
3666   return 7;
3667   }
3668 
allemptynear(cell * c)3669 bool allemptynear(cell *c) {
3670   if(c->wall) return false;
3671   forCellEx(c2, c) if(c2->wall) return false;
3672   return true;
3673   }
3674 
3675 EX bool bright;
3676 
3677 // how much to darken
getfd(cell * c)3678 EX int getfd(cell *c) {
3679   if(bright) return 0;
3680   if(among(c->land, laAlchemist, laHell, laVariant, laEclectic) && WDIM == 2 && GDIM == 3) return 0;
3681   switch(c->land) {
3682     case laRedRock:
3683     case laReptile:
3684     case laCanvas:
3685       return 0;
3686 
3687     case laSnakeNest:
3688       return realred(c->wall) ? 0 : 1;
3689 
3690     case laTerracotta:
3691     case laMercuryRiver:
3692       return (c->wall == waMercury && wmspatial) ? 0 : 1;
3693 
3694     case laKraken:
3695     case laDocks:
3696     case laBurial:
3697     case laIvoryTower:
3698     case laDungeon:
3699     case laMountain:
3700     case laEndorian:
3701     case laCaribbean:
3702     case laWhirlwind:
3703     case laRose:
3704     case laWarpSea:
3705     case laTortoise:
3706     case laDragon:
3707     case laHalloween:
3708     case laHunting:
3709     case laOcean:
3710     case laLivefjord:
3711     case laWhirlpool:
3712     case laAlchemist:
3713     case laIce:
3714     case laGraveyard:
3715     case laBlizzard:
3716     case laRlyeh:
3717     case laTemple:
3718     case laWineyard:
3719     case laDeadCaves:
3720     case laPalace:
3721     case laCA:
3722     case laDual:
3723     case laBrownian:
3724       return 1;
3725 
3726     case laVariant:
3727       if(isWateryOrBoat(c)) return 1;
3728       return 2;
3729 
3730     case laTrollheim:
3731     default:
3732       return 2;
3733     }
3734   }
3735 
3736 EX bool just_gmatrix;
3737 
colorhash(color_t i)3738 EX int colorhash(color_t i) {
3739   return (i * 0x471211 + i*i*0x124159 + i*i*i*0x982165) & 0xFFFFFF;
3740   }
3741 
isWall3(cell * c,color_t & wcol)3742 EX bool isWall3(cell *c, color_t& wcol) {
3743   if(isWall(c)) return true;
3744   if(c->wall == waChasm && c->land == laMemory) { wcol = 0x606000; return true; }
3745   if(c->wall == waInvisibleFloor) return false;
3746   // if(chasmgraph(c)) return true;
3747   if(among(c->wall, waMirror, waCloud, waMineUnknown, waMineMine)) return true;
3748   return false;
3749   }
3750 
isWall3(cell * c)3751 EX bool isWall3(cell *c) { color_t dummy; return isWall3(c, dummy); }
3752 
isSulphuric(eWall w)3753 EX bool isSulphuric(eWall w) { return among(w, waSulphur, waSulphurC); }
3754 
3755 // 'land color', but a bit twisted for Alchemist Lab
lcolor(cell * c)3756 color_t lcolor(cell *c) {
3757   if(isAlch(c->wall) && !c->item) return winf[c->wall].color;
3758   return floorcolors[c->land];
3759   }
3760 
transcolor(cell * c,cell * c2,color_t wcol)3761 EX color_t transcolor(cell *c, cell *c2, color_t wcol) {
3762   color_t dummy;
3763   if(isWall3(c2, dummy)) return 0;
3764   if(c->land != c2->land && c->land != laNone && c2->land != laNone) {
3765     if(c>c2) return 0;
3766     if(c->land == laBarrier) return darkena3(lcolor(c2), 0, 0x40);
3767     if(c2->land == laBarrier) return darkena3(lcolor(c), 0, 0x40);
3768     return darkena3(gradient(lcolor(c), lcolor(c2), 0, 1, 2), 0, 0x40);
3769     }
3770   if(sol && c->land == laWineyard && c2->master->distance < c->master->distance)
3771     return 0x00800040;
3772   if(isAlch(c) && !c->item && (c2->item || !isAlch(c2))) return darkena3(winf[c->wall].color, 0, 0x40);
3773   if(c->wall == c2->wall) return 0;
3774   if(isFire(c) && !isFire(c2)) return darkena3(wcol, 0, 0x30);
3775   if(c->wall == waLadder) return darkena3(wcol, 0, 0x30);
3776 
3777   if(c->land == laZebra && c2->land == laZebra && c2->wall == waTrapdoor) return 0x202020A0;
3778 
3779   if(c->wall == waChasm && c2->wall != waChasm) return 0x606060A0;
3780   if(isWateryOrBoat(c) && !isWateryOrBoat(c2)) return 0x0000C060;
3781   if(isSulphuric(c->wall) && !isSulphuric(c2->wall)) return darkena3(winf[c->wall].color, 0, 0x40);
3782   if(among(c->wall, waCanopy, waSolidBranch, waWeakBranch) && !among(c2->wall, waCanopy, waSolidBranch, waWeakBranch)) return 0x00C00060;
3783   if(c->wall == waFloorA && c2->wall == waFloorB && !c->item && !c2->item) return darkena3(0xFF00FF, 0, 0x80);
3784   if(realred(c->wall) || realred(c2->wall)) {
3785     int l = snakelevel(c) - snakelevel(c2);
3786     if(l > 0) return darkena3(floorcolors[laRedRock], 0, 0x30 * l);
3787     }
3788   if(among(c->wall, waRubble, waDeadfloor2) && !snakelevel(c2)) return darkena3(winf[c->wall].color, 0, 0x40);
3789   if(c->wall == waMagma && c2->wall != waMagma) return darkena3(magma_color(lavatide(c, -1)/4), 0, 0x80);
3790   return 0;
3791   }
3792 
3793 // how much should be the d-th wall darkened in 3D
get_darkval(cell * c,int d)3794 EX int get_darkval(cell *c, int d) {
3795   if(hybri) {
3796     return d >= c->type - 2 ? 4 : 0;
3797     }
3798   const int darkval_hbt[9] = {0,2,2,0,6,6,8,8,0};
3799   const int darkval_s12[12] = {0,1,2,3,4,5,0,1,2,3,4,5};
3800   const int darkval_e6[6] = {0,4,6,0,4,6};
3801   const int darkval_e12[12] = {0,4,6,0,4,6,0,4,6,0,4,6};
3802   const int darkval_e14[14] = {0,0,0,4,6,4,6,0,0,0,6,4,6,4};
3803   const int darkval_hh[14] = {0,0,0,1,1,1,2,2,2,3,3,3,1,0};
3804   const int darkval_hrec[7] = {0,0,2,4,2,4,0};
3805   const int darkval_sol[8] = {0,2,4,4,0,2,4,4};
3806   const int darkval_arnold[12] = {0,2,0,2,4,5,0,2,0,2,4,5};
3807   const int darkval_kite[12] = {0, 2, 0, 2, 4, 4, 6, 6, 6, 6, 6, 6};
3808   const int darkval_nil[8] = {6,6,0,3,6,6,0,3};
3809   const int darkval_nih[11] = {0,2,0,2,4,6,6,6,6,6,6};
3810   if(among(variation, eVariation::dual_subcubes, eVariation::bch, eVariation::bch_oct)) {
3811     int v = reg3::get_face_vertex_count(c, d);
3812     return v-3;
3813     }
3814   if(sphere) return darkval_s12[d];
3815   if(euclid && S7 == 6) return darkval_e6[d];
3816   if(euclid && S7 == 12) return darkval_e12[d];
3817   if(euclid && S7 == 14) return darkval_e14[d];
3818   if(geometry == gHoroHex) return darkval_hh[d];
3819   if(geometry == gHoroRec) return darkval_hrec[d];
3820   if(kite::in()) return darkval_kite[d];
3821   if(asonov::in()) return darkval_arnold[d];
3822   if(sol) return darkval_sol[d];
3823   if(nih) return darkval_nih[d];
3824   if(bt::in()) return darkval_hbt[d];
3825   if(hyperbolic && S7 == 6) return darkval_e6[d];
3826   if(hyperbolic && S7 == 12) return darkval_s12[d];
3827   if(nil) return darkval_nil[d];
3828   return 0;
3829   }
3830 
mousedist(shiftmatrix T)3831 EX ld mousedist(shiftmatrix T) {
3832   if(GDIM == 2) return hdist(mouseh, tC0(T));
3833   shiftpoint T1 = tC0(mscale(T, cgi.FLOOR));
3834   if(mouseaim_sensitivity) return sqhypot_d(2, T1.h) + (point_behind(T1) ? 1e10 : 0);
3835   hyperpoint h1;
3836   applymodel(T1, h1);
3837   h1 = h1 - hpxy((mousex - current_display->xcenter) / current_display->radius, (mousey - current_display->ycenter) / current_display->radius);
3838   return sqhypot_d(2, h1) + (point_behind(T1) ? 1e10 : 0);
3839   }
3840 
3841 vector<vector<hyperpoint>> clipping_plane_sets;
3842 EX int noclipped;
3843 
3844 EX bool frustum_culling = true;
3845 
3846 EX ld threshold, xyz_threshold;
3847 
3848 EX bool clip_checked = false;
3849 
make_clipping_planes()3850 void make_clipping_planes() {
3851 #if MAXMDIM >= 4
3852   clip_checked = false;
3853   if(!frustum_culling || PIU(sphere) || experimental || vid.stereo_mode == sODS || panini_alpha || stereo_alpha || prod) return;
3854 
3855   if(WDIM == 3 && pmodel == mdPerspective && !nonisotropic && !in_s2xe())
3856     threshold = sin_auto(cgi.corner_bonus), xyz_threshold = 0, clip_checked = true;
3857   else if(pmodel == mdGeodesic && sn::in())
3858     threshold = .6, xyz_threshold = 3, clip_checked = true;
3859   else if(pmodel == mdGeodesic && nil)
3860     threshold = 2, xyz_threshold = 3, clip_checked = true;
3861   else return;
3862 
3863   clipping_plane_sets.clear();
3864 
3865   auto add_clipping_plane_txy = [] (transmatrix T, const transmatrix& nlp, ld x1, ld y1, ld x2, ld y2) {
3866     ld z1 = 1, z2 = 1;
3867     hyperpoint sx = point3(y1 * z2 - y2 * z1, z1 * x2 - z2 * x1, x1 * y2 - x2 * y1);
3868     sx /= hypot_d(3, sx);
3869     sx[3] = 0;
3870     sx = T * sx;
3871     if(nisot::local_perspective_used()) sx = ortho_inverse(nlp) * sx;
3872     clipping_plane_sets.back().push_back(sx);
3873     };
3874 
3875   #if CAP_VR
3876   auto add_clipping_plane_proj = [&] (transmatrix T, const transmatrix& nlp, const transmatrix& iproj, ld x1, ld y1, ld x2, ld y2) {
3877     hyperpoint h1 = iproj * point31(x1, y1, .5);
3878     hyperpoint h2 = iproj * point31(x2, y2, .5);
3879     h1 /= h1[2]; h2 /= h2[2];
3880     add_clipping_plane_txy(T, nlp, h1[0], h1[1], h2[0], h2[1]);
3881     };
3882   #endif
3883 
3884   auto clipping_planes_screen = [&] (const transmatrix& T, const transmatrix& nlp) {
3885     ld tx = current_display->tanfov;
3886     ld ty = tx * current_display->ysize / current_display->xsize;
3887     clipping_plane_sets.push_back({});
3888     add_clipping_plane_txy(T, nlp, +tx, +ty, -tx, +ty);
3889     add_clipping_plane_txy(T, nlp, -tx, +ty, -tx, -ty);
3890     add_clipping_plane_txy(T, nlp, -tx, -ty, +tx, -ty);
3891     add_clipping_plane_txy(T, nlp, +tx, -ty, +tx, +ty);
3892     };
3893 
3894   bool stdview = true;
3895 
3896   #if CAP_VR
3897   if(vrhr::active()) {
3898     for(auto p: vrhr::frusta) {
3899       if(p.screen)
3900         clipping_planes_screen(inverse(p.pre), p.nlp);
3901       else {
3902         auto iproj = inverse(p.proj);
3903         auto ipre = inverse(p.pre);
3904         clipping_plane_sets.push_back({});
3905         add_clipping_plane_proj(ipre, p.nlp, iproj, 1, 1, 0, 1);
3906         add_clipping_plane_proj(ipre, p.nlp, iproj, 0, 1, 0, 0);
3907         add_clipping_plane_proj(ipre, p.nlp, iproj, 0, 0, 1, 0);
3908         add_clipping_plane_proj(ipre, p.nlp, iproj, 1, 0, 1, 1);
3909         }
3910       stdview = false;
3911       }
3912     }
3913   #endif
3914   if(stdview) clipping_planes_screen(Id, NLP);
3915 #endif
3916   }
3917 
clipped_by(const hyperpoint & H,const vector<hyperpoint> & v)3918 bool clipped_by(const hyperpoint& H, const vector<hyperpoint>& v) {
3919   for(auto& cpoint: v) if((H|cpoint) < -threshold) return true;
3920   return false;
3921   }
3922 
clipped_by(const hyperpoint & H,const vector<vector<hyperpoint>> & vv)3923 bool clipped_by(const hyperpoint& H, const vector<vector<hyperpoint>>& vv) {
3924   for(auto& cps: vv) if(!clipped_by(H, cps)) return false;
3925   return true;
3926   }
3927 
cell_clipped()3928 bool celldrawer::cell_clipped() {
3929 
3930   if(!clip_checked) return false;
3931 
3932   hyperpoint H = unshift(tC0(V));
3933 
3934   if(xyz_threshold && abs(H[0]) <= xyz_threshold && abs(H[1]) <= xyz_threshold && abs(H[2]) <= xyz_threshold) {
3935     noclipped++;
3936     return false;
3937     }
3938 
3939   if(clipped_by(H, clipping_plane_sets)) {
3940     drawcell_in_radar();
3941     return true;
3942     }
3943 
3944   noclipped++;
3945   return false;
3946   }
3947 
3948 EX ld precise_width = .5;
3949 
3950 int grid_depth = 0;
3951 
3952 EX bool fat_edges = false;
3953 
gridline(const shiftmatrix & V1,const hyperpoint h1,const shiftmatrix & V2,const hyperpoint h2,color_t col,int prec)3954 EX void gridline(const shiftmatrix& V1, const hyperpoint h1, const shiftmatrix& V2, const hyperpoint h2, color_t col, int prec) {
3955   transmatrix U2 = unshift(V2, V1.shift);
3956   ld d = hdist(V1.T*h1, U2*h2);
3957 
3958   #if MAXMDIM >= 4
3959   if(WDIM == 3 && fat_edges) {
3960     shiftmatrix T = V1 * rgpushxto0(h1);
3961     transmatrix S = rspintox(inverse_shift(T, V2) * h2);
3962     auto& p = queuepoly(T * S, cgi.generate_pipe(d, vid.linewidth), col);
3963     p.intester = xpush0(d/2);
3964     return;
3965     }
3966   #endif
3967 
3968   while(d > precise_width && d < 100 && grid_depth < 10) {
3969     if(V1.shift != V2.shift || !eqmatrix(V1.T, V2.T, 1e-6)) { gridline(V1, h1, V1, inverse_shift(V1, V2) * h2, col, prec); return; }
3970     hyperpoint h = midz(h1, h2);
3971     grid_depth++;
3972     gridline(V1, h1, V1, h, col, prec);
3973     gridline(V1, h, V1, h2, col, prec);
3974     grid_depth--;
3975     return;
3976     }
3977 #if MAXMDIM >= 4
3978   if(WDIM == 2 && GDIM == 3) {
3979     ld eps = cgi.human_height/100;
3980     queueline(V1*orthogonal_move(h1,cgi.FLOOR+eps), V2*orthogonal_move(h2,cgi.FLOOR+eps), col, prec);
3981     queueline(V1*orthogonal_move(h1,cgi.WALL-eps), V2*orthogonal_move(h2,cgi.WALL-eps), col, prec);
3982     }
3983   else
3984 #endif
3985     queueline(V1*h1, V2*h2, col, prec);
3986   }
3987 
gridline(const shiftmatrix & V,const hyperpoint h1,const hyperpoint h2,color_t col,int prec)3988 EX void gridline(const shiftmatrix& V, const hyperpoint h1, const hyperpoint h2, color_t col, int prec) {
3989   gridline(V, h1, V, h2, col, prec);
3990   }
3991 
generate_subcellshape_if_needed(cell * c,int id)3992 EX subcellshape& generate_subcellshape_if_needed(cell *c, int id) {
3993   if(isize(cgi.subshapes) <= id) cgi.subshapes.resize(id+1);
3994 
3995   auto& ss = cgi.subshapes[id];
3996   if(!ss.faces.empty()) return ss;
3997 
3998   cell *c1 = hybri ? hybrid::get_where(c).first : c;
3999 
4000   if(prod || WDIM == 2) for(int i=0; i<c1->type; i++) {
4001     hyperpoint w;
4002     auto f = [&] {
4003       /* mirror image of C0 in the axis h1-h2 */
4004       hyperpoint h1 = get_corner_position(c1, i);
4005       hyperpoint h2 = get_corner_position(c1, i+1);
4006       transmatrix T = gpushxto0(h1);
4007       T = spintox(T * h2) * T;
4008       w = T * C0;
4009       w[1] = -w[1];
4010       w = iso_inverse(T) * w;
4011       };
4012     if(prod) PIU(f());
4013     else f();
4014     ss.walltester.push_back(w);
4015     }
4016 
4017   for(int i=0; i<c1->type; i++)
4018     ss.faces.push_back({hybrid::get_corner(c1, i, 0, -1), hybrid::get_corner(c1, i, 0, +1), hybrid::get_corner(c1, i, 1, +1), hybrid::get_corner(c1, i, 1, -1)});
4019 
4020   for(int a: {0,1}) {
4021     vector<hyperpoint> l;
4022     int z = a ? 1 : -1;
4023     hyperpoint ctr = zpush0(z * cgi.plevel/2);
4024     for(int i=0; i<c1->type; i++)
4025       if(prod || WDIM == 2)
4026         l.push_back(hybrid::get_corner(c1, i, 0, z));
4027       else {
4028         l.push_back(ctr);
4029         l.push_back(hybrid::get_corner(c1, i, 0, z));
4030         l.push_back(hybrid::get_corner(c1, i+1, 1, z));
4031         l.push_back(ctr);
4032         l.push_back(hybrid::get_corner(c1, i, 1, z));
4033         l.push_back(hybrid::get_corner(c1, i, 0, z));
4034         }
4035     if(a == 0) std::reverse(l.begin()+1, l.end());
4036     ss.faces.push_back(l);
4037     }
4038 
4039   ss.compute_hept();
4040   return ss;
4041   }
4042 
wall_offset(cell * c)4043 int hrmap::wall_offset(cell *c) {
4044   int id = currentmap->full_shvid(c);
4045 
4046   if(WDIM == 3 && !hybri && !reg3::in()) return 0;
4047 
4048   if(isize(cgi.walloffsets) <= id) cgi.walloffsets.resize(id+1, {-1, nullptr});
4049   auto &wop = cgi.walloffsets[id];
4050   int &wo = wop.first;
4051   if(!wop.second) wop.second = c;
4052   if(wo == -1) {
4053     auto& ss = generate_subcellshape_if_needed(c, id);
4054     wo = isize(cgi.shWall3D);
4055 
4056     if(!cgi.wallstart.empty()) cgi.wallstart.pop_back();
4057     cgi.reserve_wall3d(wo + isize(ss.faces));
4058 
4059 
4060     for(int i=0; i<isize(ss.faces); i++) {
4061       cgi.make_wall(wo + i, ss.faces[i]);
4062       cgi.walltester[wo + i] = ss.walltester[i];
4063       }
4064 
4065     cgi.wallstart.push_back(isize(cgi.raywall));
4066     cgi.compute_cornerbonus();
4067     cgi.extra_vertices();
4068     }
4069   return wo;
4070   }
4071 
queue_transparent_wall(const shiftmatrix & V,hpcshape & sh,color_t color)4072 EX void queue_transparent_wall(const shiftmatrix& V, hpcshape& sh, color_t color) {
4073   auto& poly = queuepolyat(V, sh, color, PPR::TRANSPARENT_WALL);
4074   shiftpoint h = V * sh.intester;
4075   if(in_perspective())
4076     poly.subprio = int(hdist0(h) * 100000);
4077   else {
4078     hyperpoint h2;
4079     applymodel(h, h2);
4080     poly.subprio = int(h2[2] * 100000);
4081     }
4082   }
4083 
4084 #if MAXMDIM >= 4
ceiling_category(cell * c)4085 EX int ceiling_category(cell *c) {
4086   switch(c->land) {
4087     case laNone:
4088     case laMemory:
4089     case laMirrorWall2:
4090     case laMirrored:
4091     case laMirrored2:
4092     case landtypes:
4093       return 0;
4094 
4095     /* starry levels */
4096     case laIce:
4097     case laCrossroads:
4098     case laCrossroads2:
4099     case laCrossroads3:
4100     case laCrossroads4:
4101     case laCrossroads5:
4102     case laJungle:
4103     case laGraveyard:
4104     case laMotion:
4105     case laRedRock:
4106     case laZebra:
4107     case laHunting:
4108     case laEAir:
4109     case laStorms:
4110     case laMountain:
4111     case laHaunted:
4112     case laHauntedWall:
4113     case laHauntedBorder:
4114     case laWhirlwind:
4115     case laBurial:
4116     case laHalloween:
4117     case laReptile:
4118     case laVolcano:
4119     case laBlizzard:
4120     case laDual:
4121     case laWestWall:
4122     case laAsteroids:
4123       return 1;
4124 
4125     case laPower:
4126     case laWineyard:
4127     case laDesert:
4128     case laAlchemist:
4129     case laDryForest:
4130     case laCaribbean:
4131     case laMinefield:
4132     case laOcean:
4133     case laWhirlpool:
4134     case laLivefjord:
4135     case laEWater:
4136     case laOceanWall:
4137     case laWildWest:
4138     case laOvergrown:
4139     case laClearing:
4140     case laRose:
4141     case laWarpCoast:
4142     case laWarpSea:
4143     case laEndorian:
4144     case laTortoise:
4145     case laPrairie:
4146     case laDragon:
4147     case laSnakeNest:
4148     case laDocks:
4149     case laKraken:
4150     case laBrownian:
4151     case laHell:
4152     case laVariant:
4153     case laFrog:
4154     case laWet:
4155       return 2;
4156 
4157     case laBarrier:
4158     case laCaves:
4159     case laMirror:
4160     case laMirrorOld:
4161     case laCocytus:
4162     case laEmerald:
4163     case laDeadCaves:
4164     case laHive:
4165     case laCamelot:
4166     case laIvoryTower:
4167     case laEFire:
4168     case laEEarth:
4169     case laElementalWall:
4170     case laTrollheim:
4171     case laDungeon:
4172     case laBull:
4173     case laCA:
4174     case laMirrorWall:
4175     case laTerracotta:
4176     case laMercuryRiver:
4177     case laMagnetic:
4178     case laSwitch:
4179     case laEclectic:
4180       return 3;
4181 
4182     case laCanvas:
4183       if(canvas_default_wall == waInvisibleFloor) return 0;
4184       return 3;
4185 
4186     case laPalace:
4187     case laPrincessQuest:
4188     default:
4189       return 4;
4190 
4191     case laRuins:
4192       return 6;
4193 
4194     case laTemple:
4195     case laRlyeh:
4196       return 7;
4197     }
4198   }
4199 
4200 #endif
4201 
set_detail_level(const shiftmatrix & V)4202 EX void set_detail_level(const shiftmatrix& V) {
4203   ld dist0 = hdist0(tC0(V)) - 1e-6;
4204   if(vid.use_smart_range) detaillevel = 2;
4205   else if(dist0 < vid.highdetail) detaillevel = 2;
4206   else if(dist0 < vid.middetail) detaillevel = 1;
4207   else detaillevel = 0;
4208 
4209   if((cmode & sm::NUMBER) && (dialog::editingDetail())) {
4210     color_t col =
4211       dist0 < vid.highdetail ? 0xFF80FF80 :
4212       dist0 >= vid.middetail ? 0xFFFF8080 :
4213       0XFFFFFF80;
4214     queuepoly(V, cgi.shHeptaMarker, darkena(col & 0xFFFFFF, 0, 0xFF));
4215     }
4216   }
4217 
4218 struct flashdata {
4219   int t;
4220   int size;
4221   cell *where;
4222   double angle;
4223   double angle2;
4224   int spd; // 0 for flashes, >0 for particles
4225   color_t color;
4226   string text;
flashdatahr::flashdata4227   flashdata(int _t, int _s, cell *_w, color_t col, int sped) {
4228     t=_t; size=_s; where=_w; color = col;
4229     angle = rand() % 1000; spd = sped;
4230     if(GDIM == 3) angle2 = acos((rand() % 1000 - 499.5) / 500);
4231     }
4232   };
4233 
4234 vector<flashdata> flashes;
4235 
drawBubble(cell * c,color_t col,string s,ld size)4236 EX void drawBubble(cell *c, color_t col, string s, ld size) {
4237   LATE( drawBubble(c, col, s, size); )
4238   auto fd = flashdata(ticks, 1000, c, col, 0);
4239   fd.text = s;
4240   fd.angle = size;
4241   flashes.push_back(fd);
4242   }
4243 
drawFlash(cell * c)4244 EX void drawFlash(cell *c) {
4245   flashes.push_back(flashdata(ticks, 1000, c, iinf[itOrbFlash].color, 0));
4246   }
drawBigFlash(cell * c)4247 EX void drawBigFlash(cell *c) {
4248   flashes.push_back(flashdata(ticks, 2000, c, 0xC0FF00, 0));
4249   }
4250 
drawParticleSpeed(cell * c,color_t col,int speed)4251 EX void drawParticleSpeed(cell *c, color_t col, int speed) {
4252   LATE( drawParticleSpeed(c, col, speed); )
4253   if(vid.particles && !confusingGeometry())
4254     flashes.push_back(flashdata(ticks, rand() % 16, c, col, speed));
4255   }
4256 EX void drawParticle(cell *c, color_t col, int maxspeed IS(100)) {
4257   drawParticleSpeed(c, col, 1 + rand() % maxspeed);
4258   }
4259 
4260 EX void drawDirectionalParticle(cell *c, int dir, color_t col, int maxspeed IS(100)) {
4261   LATE( drawDirectionalParticle(c, dir, col, maxspeed); )
4262   if(vid.particles && !confusingGeometry()) {
4263     int speed = 1 + rand() % maxspeed;
4264     auto fd = flashdata(ticks, rand() % 16, c, col, speed);
4265     fd.angle = -atan2(tC0(currentmap->adj(c, dir)));
4266     fd.angle += 2 * M_PI * (rand() % 100 - rand() % 100) / 100 / c->type;
4267     flashes.push_back(fd);
4268     }
4269   }
4270 
4271 
4272 EX void drawParticles(cell *c, color_t col, int qty, int maxspeed IS(100)) {
4273   if(vid.particles)
4274     while(qty--) drawParticle(c,col, maxspeed);
4275   }
4276 EX void drawFireParticles(cell *c, int qty, int maxspeed IS(100)) {
4277   if(vid.particles)
4278     for(int i=0; i<qty; i++)
4279       drawParticle(c, firegradient(i / (qty-1.)), maxspeed);
4280   }
fallingFloorAnimation(cell * c,eWall w IS (waNone),eMonster m IS (moNone))4281 EX void fallingFloorAnimation(cell *c, eWall w IS(waNone), eMonster m IS(moNone)) {
4282   if(!wmspatial) return;
4283   LATE( fallingFloorAnimation(c, w, m); )
4284   fallanim& fa = fallanims[c];
4285   fa.t_floor = ticks;
4286   fa.walltype = w; fa.m = m;
4287   // drawParticles(c, darkenedby(linf[c->land].color, 1), 4, 50);
4288   }
fallingMonsterAnimation(cell * c,eMonster m,int id IS (multi::cpid))4289 EX void fallingMonsterAnimation(cell *c, eMonster m, int id IS(multi::cpid)) {
4290   if(!mmspatial) return;
4291   LATE( fallingMonsterAnimation(c, m, id); )
4292   fallanim& fa = fallanims[c];
4293   fa.t_mon = ticks;
4294   fa.m = m;
4295   fa.pid = id;
4296   // drawParticles(c, darkenedby(linf[c->land].color, 1), 4, 50);
4297   }
4298 
draw_fallanims()4299 void celldrawer::draw_fallanims() {
4300   poly_outline = OUTLINE_NONE;
4301   if(fallanims.count(c)) {
4302      int q = isize(ptds);
4303      int maxtime = euclid || sphere ? 20000 : 1500;
4304      fallanim& fa = fallanims[c];
4305      bool erase = true;
4306      if(fa.t_floor) {
4307        int t = (ticks - fa.t_floor);
4308        if(t <= maxtime) {
4309          erase = false;
4310          if(GDIM == 3)
4311            draw_shapevec(c, V, qfi.fshape->levels[0], darkena(fcol, fd, 0xFF), PPR::WALL);
4312          else if(fa.walltype == waNone) {
4313            draw_qfi(c, V, darkena(fcol, fd, 0xFF), PPR::FLOOR);
4314            }
4315          else {
4316            celldrawer ddalt;
4317            eWall w = c->wall; int p = c->wparam;
4318            c->wall = fa.walltype; c->wparam = fa.m;
4319            ddalt.c = c;
4320            ddalt.setcolors();
4321            int starcol = c->wall == waVinePlant ? 0x60C000 : ddalt.wcol;
4322            c->wall = w; c->wparam = p;
4323            draw_qfi(c, mscale(V, cgi.WALL), darkena(starcol, fd, 0xFF), PPR::WALL3);
4324            queuepolyat(mscale(V, cgi.WALL), cgi.shWall[ct6], darkena(ddalt.wcol, 0, 0xFF), PPR::WALL3A);
4325            forCellIdEx(c2, i, c)
4326              if(placeSidewall(c, i, SIDE_WALL, V, darkena(ddalt.wcol, 1, 0xFF))) break;
4327            }
4328          pushdown(c, q, V, t*t / 1000000. + t / 1000., true, true);
4329          }
4330        }
4331      if(fa.t_mon) {
4332        dynamicval<int> d(multi::cpid, fa.pid);
4333        int t = (ticks - fa.t_mon);
4334        if(t <= maxtime) {
4335          erase = false;
4336          c->stuntime = 0;
4337          shiftmatrix V2 = V;
4338          double footphase = t / 200.0;
4339          applyAnimation(c, V2, footphase, LAYER_SMALL);
4340          drawMonsterType(fa.m, c, V2, minf[fa.m].color, footphase, NOCOLOR);
4341          pushdown(c, q, V2, t*t / 1000000. + t / 1000., true, true);
4342          }
4343        }
4344      if(erase) fallanims.erase(c);
4345      }
4346    }
4347 
4348 #if CAP_QUEUE
queuecircleat1(cell * c,const shiftmatrix & V,double rad,color_t col)4349 EX void queuecircleat1(cell *c, const shiftmatrix& V, double rad, color_t col) {
4350   if(WDIM == 3) {
4351     dynamicval<color_t> p(poly_outline, col);
4352     int ofs = currentmap->wall_offset(c);
4353     for(int i=0; i<c->type; i++) {
4354       queuepolyat(V, cgi.shWireframe3D[ofs + i], 0, PPR::SUPERLINE);
4355       }
4356     return;
4357     }
4358   if(spatial_graphics || GDIM == 3) {
4359     vector<shiftmatrix> corners(c->type+1);
4360     for(int i=0; i<c->type; i++) corners[i] = V * rgpushxto0(get_corner_position(c, i, 3 / rad));
4361     corners[c->type] = corners[0];
4362     for(int i=0; i<c->type; i++) {
4363       queueline(mscale(corners[i], cgi.FLOOR) * C0, mscale(corners[i+1], cgi.FLOOR) * C0, col, 2, PPR::SUPERLINE);
4364       queueline(mscale(corners[i], cgi.WALL) * C0, mscale(corners[i+1], cgi.WALL) * C0, col, 2, PPR::SUPERLINE);
4365       queueline(mscale(corners[i], cgi.FLOOR) * C0, mscale(corners[i], cgi.WALL) * C0, col, 2, PPR::SUPERLINE);
4366       }
4367     return;
4368     }
4369   #if CAP_SHAPES
4370   if(vid.stereo_mode || sphere) {
4371     dynamicval<color_t> p(poly_outline, col);
4372     queuepolyat(V * spintick(100), cgi.shGem[1], 0, PPR::LINE);
4373     return;
4374     }
4375   #endif
4376   queuecircle(V, rad, col);
4377   if(!wmspatial) return;
4378   if(highwall(c))
4379     queuecircle(mscale(V, cgi.WALL), rad, col);
4380   int sl;
4381   if((sl = snakelevel(c))) {
4382     queuecircle(mscale(V, cgi.SLEV[sl]), rad, col);
4383     }
4384   if(chasmgraph(c))
4385     queuecircle(mscale(V, cgi.LAKE), rad, col);
4386   }
4387 
queuecircleat(cell * c,double rad,color_t col)4388 EX void queuecircleat(cell *c, double rad, color_t col) {
4389   if(!c) return;
4390   for (const shiftmatrix& V : hr::span_at(current_display->all_drawn_copies, c))
4391     queuecircleat1(c, V, rad, col);
4392   }
4393 #endif
4394 
4395 #if ISMOBILE
4396 #define MOBON (clicked)
4397 #else
4398 #define MOBON true
4399 #endif
4400 
forwardcell()4401 EX cell *forwardcell() {
4402   #if CAP_VR
4403   if(vrhr::active()) {
4404     return vrhr::forward_cell;
4405     }
4406   #endif
4407   movedir md = vectodir(move_destination_vec(6));
4408   cellwalker xc = cwt + md.d + wstep;
4409   return xc.at;
4410   }
4411 
4412 EX bool draw_centerover = true;
4413 
should_draw_mouse_cursor()4414 EX bool should_draw_mouse_cursor() {
4415   if(!mousing || inHighQual) return false;
4416   if(outofmap(mouseh.h)) return false;
4417   if(rug::rugged && !rug::renderonce) return true;
4418   return false;
4419   }
4420 
drawMarkers()4421 EX void drawMarkers() {
4422 
4423   if(!(cmode & sm::NORMAL)) return;
4424 
4425   if(should_draw_mouse_cursor()) {
4426     for(int i: player_indices()) {
4427       queueline(ggmatrix(playerpos(i)) * (WDIM == 2 && GDIM == 3 ? zpush0(cgi.WALL) : C0), mouseh, 0xFF00FF, grid_prec() + 1);
4428       }
4429     }
4430 
4431   callhooks(hooks_markers);
4432   #if CAP_SHAPES
4433   viewmat();
4434   #endif
4435 
4436   #if CAP_QUEUE
4437   for(cell *c1: crush_now)
4438     queuecircleat(c1, .8, darkena(minf[moCrusher].color, 0, 0xFF));
4439   #endif
4440 
4441   if(!inHighQual) {
4442 
4443     bool ok = !ISPANDORA || mousepressed;
4444 
4445     ignore(ok);
4446 
4447     #if CAP_QUEUE
4448     if(haveMount())
4449       for (const shiftmatrix& V : hr::span_at(current_display->all_drawn_copies, dragon::target)) {
4450         queuestr(V, 1, "X",
4451           gradient(0, iinf[itOrbDomination].color, -1, sintick(dragon::whichturn == turncount ? 75 : 150), 1));
4452         }
4453     #endif
4454 
4455     /* for(int i=0; i<12; i++) if(c->type == 5 && c->master == &dodecahedron[i])
4456       queuestr(xc, yc, sc, 4*vid.fsize, s0+('A'+i), iinf[itOrbDomination].color); */
4457 
4458     if(1) {
4459       using namespace yendor;
4460       if(yii < isize(yi) && !yi[yii].found) {
4461         cell *keycell = NULL;
4462         int i;
4463         for(i=0; i<YDIST; i++)
4464           if(yi[yii].path[i]->cpdist <= get_sightrange_ambush()) {
4465             keycell = yi[yii].path[i];
4466             }
4467         if(keycell) {
4468           for(; i<YDIST; i++) {
4469             cell *c = yi[yii].path[i];
4470             if(inscreenrange(c))
4471               keycell = c;
4472             }
4473           shiftpoint H = tC0(ggmatrix(keycell));
4474           #if CAP_QUEUE
4475           queuestr(H, 2*vid.fsize, "X", 0x10101 * int(128 + 100 * sintick(150)));
4476           int cd = celldistance(yi[yii].key(), cwt.at);
4477           if(cd == DISTANCE_UNKNOWN) for(int i2 = 0; i2<YDIST; i2++) {
4478             int cd2 = celldistance(cwt.at, yi[yii].path[i2]);
4479             if(cd2 != DISTANCE_UNKNOWN) {
4480               cd = cd2 + (YDIST-1-i2);
4481               println(hlog, "i2 = ", i2, " cd = ", celldistance(cwt.at, keycell));
4482               }
4483             }
4484           queuestr(H, vid.fsize, its(cd), 0x10101 * int(128 - 100 * sintick(150)));
4485           #endif
4486           addauraspecial(H, iinf[itOrbYendor].color, 0);
4487           }
4488         }
4489       }
4490 
4491     #if CAP_RACING
4492     racing::markers();
4493     #endif
4494 
4495     #if CAP_QUEUE
4496     if(lmouseover && vid.drawmousecircle && ok && DEFAULTCONTROL && MOBON && WDIM == 2) {
4497       cell *at = lmouseover;
4498       #if CAP_VR
4499       if(vrhr::active() && vrhr::forward_cell)
4500         at = vrhr::forward_cell;
4501       #endif
4502       queuecircleat(at, .8, darkena(lmouseover->cpdist > 1 ? 0x00FFFF : 0xFF0000, 0, 0xFF));
4503       }
4504 
4505     if(global_pushto && vid.drawmousecircle && ok && DEFAULTCONTROL && MOBON && WDIM == 2) {
4506       queuecircleat(global_pushto, .6, darkena(0xFFD500, 0, 0xFF));
4507       }
4508     #endif
4509 
4510 #if CAP_SDLJOY && CAP_QUEUE
4511     if(joydir.d >= 0 && WDIM == 2)
4512       queuecircleat(cwt.at->modmove(joydir.d+cwt.spin), .78 - .02 * sintick(199),
4513         darkena(0x00FF00, 0, 0xFF));
4514 #endif
4515 
4516     bool m = true;
4517     ignore(m);
4518 #if CAP_MODEL
4519     m = netgen::mode == 0;
4520 #endif
4521 
4522     #if CAP_QUEUE
4523     if(centerover && !playermoved && m && !anims::any_animation() && WDIM == 2 && draw_centerover)
4524       queuecircleat(centerover, .70 - .06 * sintick(200),
4525         darkena(int(175 + 25 * sintick(200)), 0, 0xFF));
4526 
4527     if(multi::players > 1 || multi::alwaysuse) for(int i=0; i<numplayers(); i++) {
4528       multi::cpid = i;
4529       if(multi::players == 1) multi::player[i] = cwt;
4530       cell *ctgt = multi::multiPlayerTarget(i);
4531       queuecircleat(ctgt, .40 - .06 * sintick(200, i / numplayers()), getcs().uicolor);
4532       }
4533     #endif
4534 
4535     // process mouse
4536     #if CAP_SHAPES
4537     if((vid.axes >= 4 || (vid.axes == 1 && !mousing)) && !shmup::on && GDIM == 2) {
4538       if(multi::players == 1) {
4539         forCellIdAll(c2, d, cwt.at) if(gmatrix.count(cwt.at)) draw_movement_arrows(c2, unshift(gmatrix[cwt.at]) * currentmap->adj(cwt.at, d), d);
4540         }
4541       else if(multi::players > 1) for(int p=0; p<multi::players; p++) {
4542         if(multi::playerActive(p) && (vid.axes >= 4 || !drawstaratvec(multi::mdx[p], multi::mdy[p])))
4543         forCellIdAll(c2, d, multi::player[p].at) if(gmatrix.count(cwt.at)) {
4544           multi::cpid = p;
4545           dynamicval<shiftmatrix> ttm(cwtV, multi::whereis[p]);
4546           dynamicval<cellwalker> tcw(cwt, multi::player[p]);
4547           draw_movement_arrows(c2, unshift(gmatrix[cwt.at]) * currentmap->adj(cwt.at, d), d);
4548           }
4549         }
4550       }
4551 
4552     if(GDIM == 3 && !inHighQual && !shmup::on && vid.axes3 && playermoved) {
4553       cell *c = forwardcell();
4554       if(c) queuecircleat(c, .8, getcs().uicolor);
4555       }
4556 
4557     #endif
4558 
4559     if(hybri && !shmup::on) {
4560 
4561       using namespace sword;
4562       int& ang = sword::dir[multi::cpid].angle;
4563       ang %= sword_angles;
4564 
4565       int adj = 1 - ((sword_angles/cwt.at->type)&1);
4566 
4567       if(items[itOrbSword])
4568         for (const shiftmatrix& V : hr::span_at(current_display->all_drawn_copies, cwt.at))
4569           queuestr(V * spin(M_PI+(-adj-2*ang)*M_PI/sword_angles) * xpush0(cgi.sword_size), vid.fsize*2, "+", iinf[itOrbSword].color);
4570       if(items[itOrbSword2])
4571         for (const shiftmatrix& V : hr::span_at(current_display->all_drawn_copies, cwt.at))
4572           queuestr(V * spin((-adj-2*ang)*M_PI/sword_angles) * xpush0(-cgi.sword_size), vid.fsize*2, "+", iinf[itOrbSword2].color);
4573       }
4574     if(SWORDDIM == 3 && !shmup::on) {
4575       if(items[itOrbSword])
4576         for (const shiftmatrix& V : hr::span_at(current_display->all_drawn_copies, cwt.at))
4577           queuestr(V * sword::dir[multi::cpid].T * xpush0(cgi.sword_size), vid.fsize*2, "+", iinf[itOrbSword].color);
4578       if(items[itOrbSword2])
4579         for (const shiftmatrix& V : hr::span_at(current_display->all_drawn_copies, cwt.at))
4580           queuestr(V * sword::dir[multi::cpid].T * xpush0(-cgi.sword_size), vid.fsize*2, "+", iinf[itOrbSword2].color);
4581       }
4582     }
4583 
4584   monsterToSummon = moNone;
4585   orbToTarget = itNone;
4586 
4587   if(mouseover && targetclick) {
4588     multi::cpid = 0;
4589     orbToTarget = targetRangedOrb(mouseover, roCheck);
4590     #if CAP_QUEUE
4591     if(orbToTarget == itOrbSummon) {
4592       monsterToSummon = summonedAt(mouseover);
4593       queuestr(mousex, mousey, 0, vid.fsize, s0+minf[monsterToSummon].glyph, minf[monsterToSummon].color);
4594       queuecircleat(mouseover, 0.6, darkena(minf[monsterToSummon].color, 0, 0xFF));
4595       }
4596     else if(orbToTarget) {
4597       queuestr(mousex, mousey, 0, vid.fsize, "@", iinf[orbToTarget].color);
4598       queuecircleat(mouseover, 0.6, darkena(iinf[orbToTarget].color, 0, 0xFF));
4599       }
4600     #endif
4601     #if CAP_SHAPES
4602     if(orbToTarget && rand() % 200 < ticks - lastt) {
4603       if(orbToTarget == itOrbDragon)
4604         drawFireParticles(mouseover, 2);
4605       else if(orbToTarget == itOrbSummon) {
4606         drawParticles(mouseover, iinf[orbToTarget].color, 1);
4607         drawParticles(mouseover, minf[monsterToSummon].color, 1);
4608         }
4609       else {
4610         drawParticles(mouseover, iinf[orbToTarget].color, 2);
4611         }
4612       }
4613     if(items[itOrbAir] && mouseover->cpdist > 1) {
4614       cell *c1 = mouseover;
4615       for(int it=0; it<10; it++) {
4616         int di;
4617         auto mib = blowoff_destination(c1, di);
4618         if(!mib.proper()) break;
4619         auto& c2 = mib.t;
4620         shiftmatrix T1 = ggmatrix(c1);
4621         shiftmatrix T2 = ggmatrix(c2);
4622         shiftmatrix T = T1 * rspintox(inverse_shift(T1,T2*C0)) * xpush(hdist(T1*C0, T2*C0) * fractick(50, 0));
4623         color_t aircol = (orbToTarget == itOrbAir ? 0x8080FF40 : 0x8080FF20);
4624         queuepoly(T, cgi.shDisk, aircol);
4625         c1 = c2;
4626         }
4627       }
4628     #endif
4629     }
4630   }
4631 
drawFlashes()4632 void drawFlashes() {
4633   #if CAP_QUEUE
4634   for(int k=0; k<isize(flashes); k++) {
4635     bool kill = true;
4636     flashdata& f = flashes[k];
4637     bool copies = false;
4638     for (const shiftmatrix& V : hr::span_at(current_display->all_drawn_copies, f.where)) {
4639       copies = true;
4640       draw_flash(f, V, kill);
4641       }
4642     forCellIdEx(c2, id, f.where) {
4643       if(!copies) {
4644         for (const shiftmatrix& V : hr::span_at(current_display->all_drawn_copies, c2)) {
4645           draw_flash(f, V * currentmap->iadj(f.where, id), kill);
4646           copies = true;
4647           }
4648         }
4649       }
4650     if(f.t > ticks - 800 && !copies) {
4651       kill = false;
4652       }
4653     if(kill) {
4654       f = flashes[isize(flashes)-1];
4655       flashes.pop_back(); k--;
4656       }
4657     }
4658   #endif
4659   }
4660 
4661 #if CAP_QUEUE
4662 always_false static_bubbles;
4663 
draw_flash(struct flashdata & f,const shiftmatrix & V,bool & kill)4664 EX void draw_flash(struct flashdata& f, const shiftmatrix& V, bool& kill) {
4665   int tim = ticks - f.t;
4666 
4667   if(tim <= f.size && !f.spd) kill = false;
4668 
4669   if(f.text != "") {
4670     if(static_bubbles) {
4671       tim = 0; kill = false;
4672       }
4673     color_t col = f.color;
4674     dynamicval<color_t> p(poly_outline, poly_outline);
4675     int r = 2;
4676     apply_neon(col, r);
4677     if(GDIM == 3 || sphere)
4678       queuestr(V, (1 - tim * 1. / f.size) * f.angle, f.text, col, r);
4679     else if(!kill) {
4680       shiftpoint h = tC0(V);
4681       if(hdist0(h) > .1) {
4682         transmatrix V2 = rspintox(h.h) * xpush(hdist0(h.h) * (1 / (1 - tim * 1. / f.size)));
4683         queuestr(shiftless(V2, h.shift), f.angle, f.text, col, r);
4684         }
4685       }
4686     if(static_bubbles) {
4687       ld rad[25];
4688       for(int a=0; a<24; a++) rad[a] = (0.5 + randd() * .3 + 0.5 * (a&1)) / (2.8 + celldistance(f.where, cwt.at) * .2);
4689       rad[24] = rad[0];
4690       for(int a=0; a<24; a++) curvepoint(xspinpush0(15 * degree * a, rad[a]));
4691       queuecurve(V, 0xFF, 0xFF0000FF, PPR::SUPERLINE);
4692       }
4693     }
4694 
4695   else if(f.spd) {
4696     #if CAP_SHAPES
4697     if(tim <= 300) kill = false;
4698     int partcol = darkena(f.color, 0, GDIM == 3 ? 255 : max(255 - tim*255/300, 0));
4699     poly_outline = OUTLINE_DEFAULT;
4700     ld t = f.spd * tim * cgi.scalefactor / 50000.;
4701     shiftmatrix T =
4702       GDIM == 2 ? V * spin(f.angle) * xpush(t) :
4703       V * cspin(0, 1, f.angle) * cspin(0, 2, f.angle2) * cpush(2, t);
4704     queuepoly(T, cgi.shParticle[f.size], partcol);
4705     #endif
4706     }
4707 
4708   else if(f.size == 1000) {
4709     for(int u=0; u<=tim; u++) {
4710       if((u-tim)%50) continue;
4711       if(u < tim-150) continue;
4712       ld rad = u * 3 / 1000.;
4713       rad = rad * (5-rad) / 2;
4714       rad *= cgi.hexf;
4715       int flashcol = f.color;
4716       if(u > 500) flashcol = gradient(flashcol, 0, 500, u, 1100);
4717       flashcol = darkena(flashcol, 0, 0xFF);
4718 #if MAXMDIM >= 4
4719       if(GDIM == 3)
4720         queueball(V * zpush(cgi.GROIN1), rad, flashcol, itDiamond);
4721       else
4722 #endif
4723       {
4724         PRING(a) curvepoint(xspinpush0(a * M_PI / cgi.S42, rad));
4725         queuecurve(V, flashcol, 0x8080808, PPR::LINE);
4726         }
4727       }
4728     }
4729   else if(f.size == 2000) {
4730     for(int u=0; u<=tim; u++) {
4731       if((u-tim)%50) continue;
4732       if(u < tim-250) continue;
4733       ld rad = u * 3 / 2000.;
4734       rad = rad * (5-rad) * 1.25;
4735       rad *= cgi.hexf;
4736       int flashcol = f.color;
4737       if(u > 1000) flashcol = gradient(flashcol, 0, 1000, u, 2200);
4738       flashcol = darkena(flashcol, 0, 0xFF);
4739 #if MAXMDIM >= 4
4740       if(GDIM == 3)
4741         queueball(V * zpush(cgi.GROIN1), rad, flashcol, itRuby);
4742       else
4743 #endif
4744       {
4745         PRING(a) curvepoint(xspinpush0(a * M_PI / cgi.S42, rad));
4746         queuecurve(V, flashcol, 0x8080808, PPR::LINE);
4747         }
4748       }
4749     }
4750   }
4751 #endif
4752 
allowIncreasedSight()4753 EX bool allowIncreasedSight() {
4754   if(cheater || autocheat) return true;
4755   if(peace::on) return true;
4756 #if CAP_TOUR
4757   if(tour::on) return true;
4758 #endif
4759   if(randomPatternsMode) return true;
4760   if(racing::on) return true;
4761   if(quotient || !hyperbolic || arcm::in() || arb::in()) return true;
4762   if(WDIM == 3) return true;
4763   if(!canmove) return true;
4764   return false;
4765   }
4766 
allowChangeRange()4767 EX bool allowChangeRange() {
4768   if(cheater || peace::on || randomPatternsMode) return true;
4769 #if CAP_TOUR
4770   if(tour::on) return true;
4771 #endif
4772   if(racing::on) return true;
4773   if(arcm::in() || arb::in()) return true;
4774   if(WDIM == 3) return true;
4775   return false;
4776   }
4777 
4778 EX purehookset hooks_drawmap;
4779 
4780 EX transmatrix actual_view_transform;
4781 
wall_radar(cell * c,transmatrix T,transmatrix LPe,ld max)4782 EX ld wall_radar(cell *c, transmatrix T, transmatrix LPe, ld max) {
4783   if(!in_perspective() || !vid.use_wall_radar) return max;
4784   transmatrix ori;
4785   if(prod) ori = ortho_inverse(LPe);
4786   ld step = max / 20;
4787   ld fixed_yshift = 0;
4788   for(int i=0; i<20; i++) {
4789     T = parallel_transport(T, ori, ztangent(-step));
4790     virtualRebase(c, T);
4791     color_t col;
4792     if(isWall3(c, col) || (WDIM == 2 && GDIM == 3 && tC0(T)[2] > cgi.FLOOR)) {
4793       T = parallel_transport(T, ori, ztangent(step));
4794       step /= 2; i = 17;
4795       if(step < 1e-3) break;
4796       }
4797     else fixed_yshift += step;
4798     }
4799   return fixed_yshift;
4800   }
4801 
make_actual_view()4802 EX void make_actual_view() {
4803   sphereflip = Id;
4804   if(sphereflipped()) sphereflip[LDIM][LDIM] = -1;
4805   actual_view_transform = sphereflip;
4806   if(vid.yshift && WDIM == 2) actual_view_transform = ypush(vid.yshift) * actual_view_transform;
4807   #if MAXMDIM >= 4
4808   if(GDIM == 3) {
4809     ld max = WDIM == 2 ? vid.camera : vid.yshift;
4810     if(max) {
4811       transmatrix Start = view_inverse(actual_view_transform * View);
4812       ld d = wall_radar(centerover, Start, NLP, max);
4813       actual_view_transform = get_shift_view_of(ztangent(d), actual_view_transform * View) * view_inverse(View);
4814       }
4815     camera_level = asin_auto(tC0(view_inverse(actual_view_transform * View))[2]);
4816     }
4817   if(nonisotropic) {
4818     transmatrix T = actual_view_transform * View;
4819     transmatrix T2 = eupush( tC0(view_inverse(T)) );
4820     NLP = T * T2;
4821     actual_view_transform = ortho_inverse(NLP) * actual_view_transform;
4822     }
4823   #endif
4824   #if MAXMDIM >= 4
4825   if(GDIM == 3 && WDIM == 2) {
4826     transmatrix T = actual_view_transform * View;
4827     transmatrix U = view_inverse(T);
4828 
4829     if(T[0][2])
4830       T = spin(-atan2(T[0][2], T[1][2])) * T;
4831     if(T[1][2] && T[2][2])
4832       T = cspin(1, 2, -atan2(T[1][2], T[2][2])) * T;
4833 
4834     ld z = -asin_auto(tC0(view_inverse(T)) [2]);
4835     T = zpush(-z) * T;
4836 
4837     radar_transform = T * U;
4838     }
4839   #endif
4840   Viewbase = View;
4841   }
4842 
4843 EX shiftmatrix cview(ld base_shift IS(0)) {
4844   return shiftless(actual_view_transform * View, base_shift);
4845   }
4846 
precise_mouseover()4847 EX void precise_mouseover() {
4848   if(WDIM == 3) {
4849     mouseover2 = mouseover = centerover;
4850     ld best = HUGE_VAL;
4851     shiftpoint h = shiftless(direct_exp(lp_iapply(ztangent(0.01))));
4852 
4853     shiftmatrix cov = ggmatrix(mouseover2);
4854     forCellIdEx(c1, i, mouseover2) {
4855       shiftpoint h1 = tC0(cov * currentmap->adj(mouseover2, i));
4856       ld dist = geo_dist(h, h1) - geo_dist(shiftless(C0), h1);
4857       if(dist < best) mouseover = c1, best = dist;
4858       }
4859     return;
4860     }
4861   if(!mouseover) return;
4862   if(GDIM == 3) return;
4863   cell *omouseover = mouseover;
4864   for(int loop = 0; loop < 10; loop++) {
4865     bool found = false;
4866     if(!gmatrix.count(mouseover)) return;
4867     hyperpoint r_mouseh = inverse_shift(gmatrix[mouseover], mouseh);
4868     for(int i=0; i<mouseover->type; i++) {
4869       hyperpoint h1 = get_corner_position(mouseover, gmod(i-1, mouseover->type));
4870       hyperpoint h2 = get_corner_position(mouseover, i);
4871       if(det3(build_matrix(h1, h2, C0, C0)) * det3(build_matrix(h1, h2, r_mouseh, C0)) < 0) {
4872         mouseover2 = mouseover;
4873         mouseover = mouseover->move(i);
4874         found = true;
4875         break;
4876         }
4877       }
4878     if(!found) return;
4879     }
4880   // probably some error... just return the original
4881   mouseover = omouseover;
4882   }
4883 
4884 EX transmatrix Viewbase;
4885 
4886 EX bool no_wall_rendering;
4887 
drawthemap()4888 EX void drawthemap() {
4889   check_cgi();
4890   cgi.require_shapes();
4891 
4892   DEBBI(DF_GRAPH, ("draw the map"));
4893 
4894   last_firelimit = firelimit;
4895   firelimit = 0;
4896 
4897   make_clipping_planes();
4898   radarpoints.clear();
4899   radarlines.clear();
4900   callhooks(hooks_drawmap);
4901 
4902   frameid++;
4903   cells_drawn = 0;
4904   cells_generated = 0;
4905   noclipped = 0;
4906   first_cell_to_draw = true;
4907 
4908   if(sightrange_bonus > 0 && !allowIncreasedSight())
4909     sightrange_bonus = 0;
4910 
4911   swap(gmatrix0, gmatrix);
4912   gmatrix.clear();
4913   current_display->all_drawn_copies.clear();
4914 
4915   wmspatial = vid.wallmode == 4 || vid.wallmode == 5;
4916   wmescher = vid.wallmode == 3 || vid.wallmode == 5;
4917   wmplain = vid.wallmode == 2 || vid.wallmode == 4;
4918   wmascii = vid.wallmode == 0 || vid.wallmode == 6;
4919   wmascii3 = vid.wallmode == 6;
4920   wmblack = vid.wallmode == 1;
4921 
4922   mmitem = vid.monmode >= 1;
4923   mmmon = vid.monmode >= 2;
4924   mmspatial = vid.monmode >= 3;
4925 
4926   mmhigh = vid.highlightmode >= 1;
4927   if(hiliteclick) mmhigh = !mmhigh;
4928 
4929   spatial_graphics = wmspatial || mmspatial;
4930   spatial_graphics = spatial_graphics && GDIM == 2;
4931   #if CAP_RUG
4932   if(rug::rugged && !rug::spatial_rug) spatial_graphics = false;
4933   #endif
4934   if(non_spatial_model())
4935     spatial_graphics = false;
4936   if(pmodel == mdDisk && abs(pconf.alpha) < 1e-6) spatial_graphics = false;
4937 
4938   if(!spatial_graphics) wmspatial = mmspatial = false;
4939   if(GDIM == 3) wmspatial = mmspatial = true;
4940 
4941   for(int m=0; m<motypes; m++) if(isPrincess(eMonster(m)))
4942     minf[m].name = princessgender() ? "Princess" : "Prince";
4943 
4944   #if CAP_RAY
4945   ray::in_use = ray::requested();
4946   #endif
4947   no_wall_rendering = ray::in_use;
4948   // ray::comparison_mode = true;
4949   if(ray::comparison_mode) no_wall_rendering = false;
4950 
4951   iinf[itSavedPrincess].name = minf[moPrincess].name;
4952 
4953   for(int i=0; i<NUM_GS; i++) {
4954     genderswitch_t& g = genderswitch[i];
4955     if(g.gender != princessgender()) continue;
4956     minf[g.m].help = g.desc;
4957     minf[g.m].name = g.name;
4958     }
4959 
4960   if(mapeditor::autochoose) mapeditor::ew = mapeditor::ewsearch;
4961   mapeditor::ewsearch.dist = 1e30;
4962   modist = 1e20; mouseover = NULL;
4963   modist2 = 1e20; mouseover2 = NULL;
4964 
4965   compute_graphical_distance();
4966 
4967   for(int i=0; i<multi::players; i++) {
4968     multi::ccdist[i] = 1e20; multi::ccat[i] = NULL;
4969     }
4970 
4971   downseek.reset();
4972 
4973   #if ISMOBILE
4974   mouseovers = XLAT("No info about this...");
4975   #endif
4976   if(mouseout() && !mousepan)
4977     modist = -5;
4978   playerfound = false;
4979   // playerfoundL = false;
4980   // playerfoundR = false;
4981 
4982   arrowtraps.clear();
4983 
4984   make_actual_view();
4985   currentmap->draw_all();
4986   drawWormSegments();
4987   drawBlizzards();
4988   drawArrowTraps();
4989 
4990   precise_mouseover();
4991 
4992   ivoryz = false;
4993 
4994   linepatterns::drawAll();
4995 
4996   callhooks(hooks_frame);
4997 
4998   drawMarkers();
4999   drawFlashes();
5000 
5001   mapeditor::draw_dtshapes();
5002 
5003   if(multi::players > 1 && !shmup::on) {
5004     if(multi::centerplayer != -1)
5005       cwtV = multi::whereis[multi::centerplayer];
5006     else {
5007       hyperpoint h;
5008       for(int i=0; i<3; i++) h[i] = 0;
5009       for(int p=0; p<multi::players; p++) if(multi::playerActive(p)) {
5010         hyperpoint h1 = unshift(tC0(multi::whereis[p]));
5011         for(int i=0; i<3; i++) h[i] += h1[i];
5012         }
5013       h = mid(h, h);
5014       cwtV = shiftless(rgpushxto0(h));
5015       }
5016     }
5017 
5018   if(shmup::on) {
5019     if(multi::players == 1)
5020       cwtV = shmup::pc[0]->pat;
5021     else if(multi::centerplayer != -1)
5022       cwtV = shmup::pc[multi::centerplayer]->pat;
5023     else {
5024       hyperpoint h;
5025       for(int i=0; i<3; i++) h[i] = 0;
5026       for(int p=0; p<multi::players; p++) {
5027         hyperpoint h1 = unshift(tC0(shmup::pc[p]->pat));
5028         for(int i=0; i<3; i++) h[i] += h1[i];
5029         }
5030       h = mid(h, h);
5031       cwtV = shiftless(rgpushxto0(h));
5032       }
5033     }
5034 
5035   #if CAP_SDL
5036   const Uint8 *keystate = SDL12_GetKeyState(NULL);
5037   lmouseover = mouseover;
5038   lmouseover_distant = lmouseover;
5039   bool useRangedOrb = (!(vid.shifttarget & 1) && haveRangedOrb() && lmouseover && lmouseover->cpdist > 1) || (keystate[SDL12(SDLK_RSHIFT, SDL_SCANCODE_RSHIFT)] | keystate[SDL12(SDLK_LSHIFT, SDL_SCANCODE_LSHIFT)]);
5040   if(!useRangedOrb && !(cmode & sm::MAP) && !(cmode & sm::DRAW) && DEFAULTCONTROL && !mouseout() && !dual::state) {
5041     dynamicval<eGravity> gs(gravity_state, gravity_state);
5042     void calcMousedest();
5043     calcMousedest();
5044     cellwalker cw = cwt; bool f = flipplayer;
5045     items[itWarning]+=2;
5046 
5047     bool recorduse[ittypes];
5048     for(int i=0; i<ittypes; i++) recorduse[i] = orbused[i];
5049     movepcto(mousedest.d, mousedest.subdir, true);
5050     for(int i=0; i<ittypes; i++) orbused[i] = recorduse[i];
5051     items[itWarning] -= 2;
5052     if(cw.spin != cwt.spin) mirror::act(-mousedest.d, mirror::SPINSINGLE);
5053     cwt = cw; flipplayer = f;
5054     lmouseover = mousedest.d >= 0 ? cwt.at->modmove(cwt.spin + mousedest.d) : cwt.at;
5055     }
5056   #endif
5057   }
5058 
drawmovestar(double dx,double dy)5059 EX void drawmovestar(double dx, double dy) {
5060 
5061   DEBBI(DF_GRAPH, ("draw movestar"));
5062   if(viewdists) return;
5063   if(GDIM == 3) return;
5064 
5065   if(!playerfound) return;
5066 
5067   if(shmup::on) return;
5068 #if CAP_RUG
5069   if(rug::rugged && multi::players == 1 && !multi::alwaysuse) return;
5070 #endif
5071 
5072   shiftpoint H = tC0(cwtV);
5073   ld R = sqrt(H[0] * H[0] + H[1] * H[1]);
5074   shiftmatrix Centered;
5075 
5076   if(euclid)
5077     Centered = shiftless(eupush(H.h));
5078   else if(R > 1e-9) Centered = rgpushxto0(H);
5079   else Centered = shiftless(Id);
5080 
5081   Centered = Centered * rgpushxto0(hpxy(dx*5, dy*5));
5082   if(multi::cpid >= 0) multi::crosscenter[multi::cpid] = Centered;
5083 
5084   int rax = vid.axes;
5085   if(rax == 1) rax = drawstaratvec(dx, dy) ? 2 : 0;
5086 
5087   if(rax == 0 || vid.axes >= 4) return;
5088 
5089   int starcol = getcs().uicolor;
5090   ignore(starcol);
5091 
5092   if(0);
5093 
5094 #if CAP_SHAPES
5095   else if(vid.axes == 3)
5096     queuepoly(Centered, cgi.shMovestar, starcol);
5097 #endif
5098 
5099   else for(int d=0; d<8; d++) {
5100 #if CAP_QUEUE
5101     color_t col = starcol;
5102 #if ISPANDORA
5103     if(leftclick && (d == 2 || d == 6 || d == 1 || d == 7)) col &= 0xFFFFFF3F;
5104     if(rightclick && (d == 2 || d == 6 || d == 3 || d == 5)) col &= 0xFFFFFF3F;
5105     if(!leftclick && !rightclick && (d&1)) col &= 0xFFFFFF3F;
5106 #endif
5107     queueline(tC0(Centered), Centered * xspinpush0(d * M_PI / 4, cgi.scalefactor/2), col, 3 + vid.linequality);
5108 #endif
5109     }
5110   }
5111 
5112 // old style joystick control
5113 
5114 EX bool dronemode;
5115 
5116 purehookset hooks_calcparam;
5117 
5118 EX int corner_centering;
5119 
5120 EX bool permaside;
5121 
calcparam()5122 EX void calcparam() {
5123 
5124   DEBBI(DF_GRAPH, ("calc param"));
5125   auto cd = current_display;
5126 
5127   cd->xtop = vid.xres * cd->xmin;
5128   cd->ytop = vid.yres * cd->ymin;
5129 
5130   cd->xsize = vid.xres * (cd->xmax - cd->xmin);
5131   cd->ysize = vid.yres * (cd->ymax - cd->ymin);
5132 
5133   cd->xcenter = cd->xtop + cd->xsize / 2;
5134   cd->ycenter = cd->ytop + cd->ysize / 2;
5135 
5136   if(pconf.scale > -1e-2 && pconf.scale < 1e-2) pconf.scale = 1;
5137 
5138   ld realradius = min(cd->xsize / 2, cd->ysize / 2);
5139 
5140   cd->scrsize = realradius - (inHighQual ? 0 : ISANDROID ? 2 : ISIOS ? 40 : 40);
5141 
5142   current_display->sidescreen = permaside;
5143 
5144   if(vid.xres < vid.yres - 2 * vid.fsize && !inHighQual && !in_perspective()) {
5145     cd->ycenter = lerp(vid.fsize + cd->scrsize, vid.yres - cd->scrsize - vid.fsize, .8);
5146     }
5147   else {
5148     bool ok = !vrhr::active();
5149     if(vid.xres > vid.yres * 4/3+16 && (cmode & sm::SIDE) && ok)
5150       current_display->sidescreen = true;
5151 #if CAP_TOUR
5152     if(tour::on && (tour::slides[tour::currentslide].flags & tour::SIDESCREEN) && ok)
5153       current_display->sidescreen = true;
5154 #endif
5155 
5156     if(current_display->sidescreen) cd->xcenter = vid.yres/2;
5157     }
5158 
5159   cd->radius = pconf.scale * cd->scrsize;
5160   if(GDIM == 3 && in_perspective()) cd->radius = cd->scrsize;
5161   realradius = min(realradius, cd->radius);
5162 
5163   ld aradius = sphere ? cd->radius / (pconf.alpha - 1) : cd->radius;
5164   #if MAXMDIM >= 4
5165   if(euclid && rots::drawing_underlying) aradius *= 2.5;
5166   #endif
5167 
5168   if(dronemode) { cd->ycenter -= cd->radius; cd->ycenter += vid.fsize/2; cd->ycenter += vid.fsize/2; cd->radius *= 2; }
5169 
5170   if(corner_centering) {
5171     cd->ycenter = cd->ytop + cd->ysize - vid.fsize - aradius;
5172     if(corner_centering == 1)
5173       cd->xcenter = cd->xtop + vid.fsize + aradius;
5174     if(corner_centering == 2)
5175       cd->xcenter = cd->xtop + cd->xsize - vid.fsize - aradius;
5176     }
5177 
5178   cd->xcenter += cd->scrsize * pconf.xposition;
5179   cd->ycenter += cd->scrsize * pconf.yposition;
5180 
5181   ld fov = vid.fov * degree / 2;
5182   cd->tanfov = sin(fov) / (cos(fov) + (panini_alpha ? panini_alpha : stereo_alpha));
5183 
5184   callhooks(hooks_calcparam);
5185   reset_projection();
5186   }
5187 
5188 EX function<void()> wrap_drawfullmap = drawfullmap;
5189 
5190 bool force_sphere_outline = false;
5191 
drawfullmap()5192 EX void drawfullmap() {
5193 
5194   DEBBI(DF_GRAPH, ("draw full map"));
5195 
5196   check_cgi();
5197   cgi.require_shapes();
5198 
5199   ptds.clear();
5200 
5201 
5202   /*
5203   if(models::on) {
5204     char ch = 'A';
5205     for(auto& v: history::v) {
5206       queuepoly(ggmatrix(v->base) * v->at, cgi.shTriangle, 0x306090C0);
5207       queuestr(ggmatrix(v->base) * v->at * C0, 10, s0+(ch++), 0xFF0000);
5208       }
5209     }
5210   */
5211 
5212   #if CAP_QUEUE
5213   draw_boundary(0);
5214   draw_boundary(1);
5215 
5216   draw_model_elements();
5217   #if MAXMDIM >= 4 && CAP_GL
5218   prepare_sky();
5219   #endif
5220   #endif
5221 
5222   /* if(vid.wallmode < 2 && !euclid && !patterns::whichShape) {
5223     int ls = isize(lines);
5224     if(ISMOBILE) ls /= 10;
5225     for(int t=0; t<ls; t++) queueline(View * lines[t].P1, View * lines[t].P2, lines[t].col >> (darken+1));
5226     } */
5227 
5228   clearaura();
5229   if(!nomap) drawthemap();
5230   if(!inHighQual) {
5231     if((cmode & sm::NORMAL) && !rug::rugged) {
5232       if(multi::players > 1) {
5233         auto bcwtV = cwtV;
5234         for(int i=0; i<multi::players; i++) if(multi::playerActive(i))
5235           cwtV = multi::whereis[i], multi::cpid = i, drawmovestar(multi::mdx[i], multi::mdy[i]);
5236         cwtV = bcwtV;
5237         }
5238       else if(multi::alwaysuse)
5239         drawmovestar(multi::mdx[0], multi::mdy[0]);
5240       else
5241         drawmovestar(0, 0);
5242       }
5243 #if CAP_EDIT
5244     if(cmode & sm::DRAW) mapeditor::drawGrid();
5245 #endif
5246     }
5247 
5248   drawaura();
5249   #if CAP_QUEUE
5250   drawqueue();
5251   #endif
5252   }
5253 
5254 #if ISMOBILE
5255 extern bool wclick;
5256 #endif
5257 
5258 EX bool just_refreshing;
5259 
gamescreen(int _darken)5260 EX void gamescreen(int _darken) {
5261 
5262   if(just_refreshing) return;
5263 
5264   if(subscreens::split([=] () {
5265     calcparam();
5266     compute_graphical_distance();
5267     gamescreen(_darken);
5268     })) {
5269     if(racing::on) return;
5270     // create the gmatrix
5271     View = subscreens::player_displays[0].view_matrix;
5272     centerover = subscreens::player_displays[0].precise_center;
5273     just_gmatrix = true;
5274     currentmap->draw_all();
5275     just_gmatrix = false;
5276     return;
5277     }
5278 
5279   auto gx = vid.xres;
5280   auto gy = vid.yres;
5281 
5282   if(dual::split([=] () {
5283     vid.xres = gx;
5284     vid.yres = gy;
5285     dual::in_subscreen([=] () { gamescreen(_darken); });
5286     })) {
5287     calcparam();
5288     return;
5289     }
5290 
5291   calcparam();
5292 
5293   if((cmode & sm::MAYDARK) && !current_display->sidescreen && !inHighQual) {
5294     _darken += 2;
5295     }
5296 
5297   darken = _darken;
5298   if(vrhr::active()) darken = 0;
5299 
5300   if(history::includeHistory) history::restore();
5301 
5302   anims::apply();
5303 #if CAP_RUG
5304   if(rug::rugged) {
5305     rug::actDraw();
5306     } else
5307 #endif
5308   wrap_drawfullmap();
5309   anims::rollback();
5310 
5311   if(history::includeHistory) history::restoreBack();
5312 
5313   poly_outline = OUTLINE_DEFAULT;
5314 
5315   #if ISMOBILE
5316 
5317   buttonclicked = false;
5318 
5319   if((cmode & sm::NORMAL) && vid.stereo_mode != sLR && !inHighQual) {
5320     if(andmode == 0 && shmup::on) {
5321       using namespace shmupballs;
5322       calc();
5323       drawCircle(xmove, yb, rad, OUTLINE_FORE);
5324       drawCircle(xmove, yb, rad/2, OUTLINE_FORE);
5325       drawCircle(xfire, yb, rad, 0xFF0000FF);
5326       drawCircle(xfire, yb, rad/2, 0xFF0000FF);
5327       }
5328     else {
5329       if(!haveMobileCompass()) displayabutton(-1, +1, andmode == 0 && useRangedOrb ? XLAT("FIRE") : andmode == 0 && WDIM == 3 && wclick ? XLAT("WAIT") : XLAT("MOVE"),  andmode == 0 ? BTON : BTOFF);
5330       displayabutton(+1, +1, rug::rugged ? XLAT("RUG") :andmode == 1 ?  XLAT("BACK") : GDIM == 3 ? XLAT("CAM") : XLAT("DRAG"),  andmode == 1 ? BTON : BTOFF);
5331       }
5332     displayabutton(-1, -1, XLAT("INFO"),  andmode == 12 ? BTON : BTOFF);
5333     displayabutton(+1, -1, XLAT("MENU"), andmode == 3 ? BTON : BTOFF);
5334     }
5335 
5336   #endif
5337 
5338   darken = 0;
5339 
5340 #if CAP_TEXTURE
5341   if(texture::config.tstate == texture::tsAdjusting)
5342     texture::config.drawRawTexture();
5343 #endif
5344 
5345   #if CAP_VR
5346   vrhr::size_and_draw_ui_box();
5347   #endif
5348   }
5349 
emptyscreen()5350 EX void emptyscreen() {
5351   ptds.clear();
5352   ray::in_use = false;
5353   drawqueue();
5354   }
5355 
5356 EX bool nohelp;
5357 EX bool no_find_player;
5358 
normalscreen()5359 EX void normalscreen() {
5360   help = "@";
5361 
5362   mouseovers = standard_help();
5363 
5364 #if CAP_TOUR
5365   if(tour::on) mouseovers = tour::tourhelp;
5366 #endif
5367 
5368   if(GDIM == 3 || !outofmap(mouseh.h)) getcstat = '-';
5369   cmode = sm::NORMAL | sm::DOTOUR | sm::CENTER;
5370   if(viewdists && show_distance_lists) cmode |= sm::SIDE | sm::MAYDARK;
5371   gamescreen((vid.highlightmode == (hiliteclick ? 0 : 2)) ? 1 : 0); drawStats();
5372   if(nomenukey || ISMOBILE)
5373     ;
5374 #if CAP_TOUR
5375   else if(tour::on)
5376     displayButton(vid.xres-8, vid.yres-vid.fsize, XLAT("(ESC) tour menu"), SDLK_ESCAPE, 16);
5377   else
5378 #endif
5379     displayButton(vid.xres-8, vid.yres-vid.fsize, XLAT("(v) menu"), 'v', 16);
5380   keyhandler = handleKeyNormal;
5381 
5382   if(!playerfound && !anims::any_on() && !sphere && !no_find_player && mapeditor::drawplayer)
5383     displayButton(current_display->xcenter, current_display->ycenter, mousing ? XLAT("find the player") : XLAT("press SPACE to find the player"), ' ', 8);
5384 
5385   describeMouseover();
5386   }
5387 
5388 EX vector< function<void()> > screens = { normalscreen };
5389 
5390 #if HDR
pushScreen(const T & x)5391 template<class T> void pushScreen(const T& x) { screens.push_back(x); }
popScreen()5392 inline void popScreen() { if(isize(screens)>1) screens.pop_back(); }
popScreenAll()5393 inline void popScreenAll() { while(isize(screens)>1) popScreen(); }
5394 typedef void (*cfunction)();
5395 #endif
5396 
current_screen_cfunction()5397 EX cfunction current_screen_cfunction() {
5398   auto tgt = screens.back().target<cfunction>();
5399   if(!tgt) return nullptr;
5400   return *tgt;
5401   }
5402 
5403 #if HDR
5404 namespace sm {
5405   static const int NORMAL = 1;
5406   static const int MISSION = 2;
5407   static const int HELP = 4;
5408   static const int MAP = 8;
5409   static const int DRAW = 16;
5410   static const int NUMBER = 32;
5411   static const int SHMUPCONFIG = 64;
5412   static const int OVERVIEW = 128;
5413   static const int SIDE = 256;
5414   static const int DOTOUR = 512;
5415   static const int CENTER = 1024;
5416   static const int ZOOMABLE = 4096;
5417   static const int TORUSCONFIG = 8192;
5418   static const int MAYDARK = 16384;
5419   static const int DIALOG_STRICT_X = 32768; // do not interpret dialog clicks outside of the X region
5420   static const int EXPANSION = (1<<16);
5421   static const int HEXEDIT = (1<<17);
5422   static const int VR_MENU = (1<<18); // always show the menu in VR
5423   }
5424 #endif
5425 
5426 EX int cmode;
5427 
drawscreen()5428 EX void drawscreen() {
5429 
5430   DEBBI(DF_GRAPH, ("drawscreen"));
5431   #if CAP_GL
5432   GLWRAP;
5433   #endif
5434 
5435   if(vid.xres == 0 || vid.yres == 0) return;
5436 
5437   calcparam();
5438   // rug::setVidParam();
5439 
5440 #if CAP_GL
5441   if(vid.usingGL) setGLProjection();
5442 #endif
5443 
5444 #if CAP_XGD
5445   if(!vid.usingGL) {
5446     gdpush(5); gdpush(backcolor);
5447     }
5448 #endif
5449 
5450 #if CAP_VR
5451   vrhr::clear();
5452 #endif
5453 
5454 
5455   #if CAP_SDL
5456   // SDL_LockSurface(s);
5457   // unsigned char *b = (unsigned char*) s->pixels;
5458   // int n = vid.xres * vid.yres * 4;
5459   // while(n) *b >>= 1, b++, n--;
5460   // memset(s->pixels, 0, vid.xres * vid.yres * 4);
5461   #if CAP_GL
5462   if(!vid.usingGL)
5463   #endif
5464     SDL_FillRect(s, NULL, backcolor);
5465   #endif
5466 
5467   // displaynum(vx,100, 0, 24, 0xc0c0c0, celldist(cwt.at), ":");
5468 
5469   lgetcstat = getcstat;
5470   getcstat = 0; inslider = false;
5471 
5472   mouseovers = " ";
5473 
5474   cmode = 0;
5475   keyhandler = [] (int sym, int uni) {};
5476   #if CAP_SDL
5477   joyhandler = [] (SDL_Event& ev) { return false; };
5478   #endif
5479   if(!isize(screens)) pushScreen(normalscreen);
5480   screens.back()();
5481 
5482 #if !ISMOBILE
5483   color_t col = linf[cwt.at->land].color;
5484   if(cwt.at->land == laRedRock) col = 0xC00000;
5485   if(!nohelp)
5486     displayfr(vid.xres/2, vid.fsize,   2, vid.fsize, mouseovers, col, 8);
5487 #endif
5488 
5489   drawmessages();
5490 
5491   bool normal = cmode & sm::NORMAL;
5492 
5493   if((havewhat&HF_BUG) && darken == 0 && normal) for(int k=0; k<3; k++)
5494     displayfr(vid.xres/2 + vid.fsize * 5 * (k-1), vid.fsize*2,   2, vid.fsize,
5495       its(hive::bugcount[k]), minf[moBug0+k].color, 8);
5496 
5497   bool minefieldNearby = false;
5498   int mines[MAXPLAYER], tmines=0;
5499   for(int p=0; p<numplayers(); p++) {
5500     mines[p] = 0;
5501     cell *c = playerpos(p);
5502     if(!c) continue;
5503     for(cell *c2: adj_minefield_cells(c)) {
5504       if(c2->land == laMinefield)
5505         minefieldNearby = true;
5506       if(c2->wall == waMineMine) {
5507         bool ep = false;
5508         if(!ep) mines[p]++, tmines++;
5509         }
5510       }
5511     }
5512 
5513   if((minefieldNearby || tmines) && !items[itOrbAether] && !last_gravity_state && darken == 0 && normal) {
5514     string s;
5515     if(tmines > 9) tmines = 9;
5516     color_t col = minecolors[tmines%10];
5517 
5518     if(tmines == 7) seenSevenMines = true;
5519 
5520     for(int p: player_indices())
5521       displayfr(vid.xres * (p+.5) / numplayers(),
5522         current_display->ycenter - current_display->radius * 3/4, 2,
5523         vid.fsize,
5524         mines[p] > 7 ? its(mines[p]) : XLAT(minetexts[mines[p]]), minecolors[mines[p]%10], 8);
5525 
5526     if(minefieldNearby && !shmup::on && cwt.at->land != laMinefield && cwt.peek()->land != laMinefield) {
5527       displayfr(vid.xres/2, current_display->ycenter - current_display->radius * 3/4 - vid.fsize*3/2, 2,
5528         vid.fsize,
5529         XLAT("WARNING: you are entering a minefield!"),
5530         col, 8);
5531       }
5532     }
5533 
5534   // SDL_UnlockSurface(s);
5535 
5536   glflush();
5537   DEBB(DF_GRAPH, ("swapbuffers"));
5538 
5539   #if CAP_VR
5540   vrhr::submit();
5541   #endif
5542 
5543   #if CAP_SDL
5544   present_screen();
5545   #endif
5546 
5547 #if CAP_VR
5548   vrhr::handoff();
5549 #endif
5550 
5551 //printf("\ec");
5552   }
5553 
restartGraph()5554 EX void restartGraph() {
5555   DEBBI(DF_INIT, ("restartGraph"));
5556 
5557   if(!autocheat) linepatterns::clearAll();
5558   if(currentmap) {
5559     resetview();
5560     if(sphere) View = spin(-M_PI/2);
5561     }
5562   }
5563 
clearAnimations()5564 EX void clearAnimations() {
5565   for(int i=0; i<ANIMLAYERS; i++) animations[i].clear();
5566   flashes.clear();
5567   fallanims.clear();
5568   }
5569 
__anonf255e62f0d02() 5570 auto graphcm = addHook(hooks_clearmemory, 0, [] () {
5571   DEBBI(DF_MEMORY, ("clear graph memory"));
5572   mouseover = centerover = lmouseover = NULL;
5573   gmatrix.clear(); gmatrix0.clear(); current_display->all_drawn_copies.clear();
5574   clearAnimations();
5575   })
__anonf255e62f0e02(gamedata* gd) 5576 + addHook(hooks_gamedata, 0, [] (gamedata* gd) {
5577   gd->store(mouseover);
5578   gd->store(lmouseover);
5579   gd->store(animations);
5580   gd->store(flashes);
5581   gd->store(fallanims);
5582   gd->store(radar_transform);
5583   gd->store(actual_view_transform);
5584   });
5585 
5586 //=== animation
5587 
5588 #if HDR
5589 struct animation {
5590   int ltick;
5591   double footphase;
5592   transmatrix wherenow;
5593   int attacking; /** 0 = no attack animation, 1 = first phase, 2 = second phase, 3 = hugging */
5594   transmatrix attackat;
5595   bool mirrored;
5596   eItem thrown_item; /** for thrown items */
5597   };
5598 
5599 // we need separate animation layers for Orb of Domination and Tentacle+Ghost,
5600 // and also to mark Boats
5601 #define ANIMLAYERS 4
5602 #define LAYER_BIG   0 // for worms and krakens
5603 #define LAYER_SMALL 1 // for others
5604 #define LAYER_BOAT  2 // mark that a boat has moved
5605 #define LAYER_THROW 3 // for thrown items
5606 #endif
5607 
5608 EX array<map<cell*, animation>, ANIMLAYERS> animations;
5609 
revhint(cell * c,int hint)5610 EX int revhint(cell *c, int hint) {
5611   if(hint >= 0 && hint < c->type) return c->c.spin(hint);
5612   else return hint;
5613   }
5614 
adj(const movei & m)5615 EX transmatrix adj(const movei& m) {
5616   if(m.proper()) return currentmap->adj(m.s, m.d);
5617   else return currentmap->relative_matrix(m.t, m.s, C0);
5618   }
5619 
iadj(const movei & m)5620 EX transmatrix iadj(const movei& m) {
5621   if(m.proper()) return currentmap->iadj(m.s, m.d);
5622   else return currentmap->relative_matrix(m.s, m.t, C0);
5623   }
5624 
animateMovement(const movei & m,int layer)5625 EX void animateMovement(const movei& m, int layer) {
5626   if(vid.mspeed >= 5) return; // no animations!
5627   LATE ( animateMovement(m, layer); )
5628   transmatrix T = iadj(m);
5629   bool found_s = animations[layer].count(m.s);
5630   animation& a = animations[layer][m.t];
5631   if(found_s) {
5632     a = animations[layer][m.s];
5633     a.wherenow = T * a.wherenow;
5634     if(m.s != m.t)
5635       animations[layer].erase(m.s);
5636     a.attacking = 0;
5637     }
5638   else {
5639     a.ltick = ticks;
5640     a.wherenow = T;
5641     a.footphase = 0;
5642     a.mirrored = false;
5643     }
5644   if(m.proper() && m.s->c.mirror(m.d))
5645     a.mirrored = !a.mirrored;
5646   }
5647 
animate_item_throw(cell * from,cell * to,eItem it)5648 EX void animate_item_throw(cell *from, cell *to, eItem it) {
5649 
5650   bool steps = false;
5651   again:
5652   if(from != to) {
5653     forCellIdEx(c1, i, from) if(celldistance(c1, to) < celldistance(from, to)) {
5654       animateMovement(movei(from, i), LAYER_THROW);
5655       from = c1;
5656       steps = true;
5657       goto again;
5658       }
5659     }
5660 
5661   if(steps) {
5662     animation& a = animations[LAYER_THROW][to];
5663     a.thrown_item = it;
5664     }
5665   }
5666 
animateAttackOrHug(const movei & m,int layer,int phase,ld ratio,ld delta)5667 EX void animateAttackOrHug(const movei& m, int layer, int phase, ld ratio, ld delta) {
5668   LATE( animateAttack(m, layer); )
5669   if(vid.mspeed >= 5) return; // no animations!
5670   transmatrix T = iadj(m);
5671   bool newanim = !animations[layer].count(m.s);
5672   animation& a = animations[layer][m.s];
5673   a.attacking = phase;
5674   if(phase == 3) println(hlog, "distance = ", hdist0(T * C0));
5675   a.attackat = rspintox(tC0(iso_inverse(T))) * xpush(hdist0(T*C0) * ratio + delta);
5676   if(newanim) a.wherenow = Id, a.ltick = ticks, a.footphase = 0;
5677   }
5678 
animateAttack(const movei & m,int layer)5679 EX void animateAttack(const movei& m, int layer) {
5680   animateAttackOrHug(m, layer, 1, 1/3., 0);
5681   }
5682 
animateHug(const movei & m,int layer)5683 EX void animateHug(const movei& m, int layer) {
5684   animateAttackOrHug(m, layer, 3, 0.5, -0.0713828 * cgi.scalefactor);
5685   }
5686 
5687 vector<pair<cell*, animation> > animstack;
5688 
indAnimateMovement(const movei & m,int layer)5689 EX void indAnimateMovement(const movei& m, int layer) {
5690   if(vid.mspeed >= 5) return; // no animations!
5691   LATE( indAnimateMovement(m, layer); )
5692   if(animations[layer].count(m.t)) {
5693     animation res = animations[layer][m.t];
5694     animations[layer].erase(m.t);
5695     animateMovement(m, layer);
5696     if(animations[layer].count(m.t))
5697       animstack.push_back(make_pair(m.t, animations[layer][m.t]));
5698     animations[layer][m.t] = res;
5699     }
5700   else {
5701     animateMovement(m, layer);
5702     if(animations[layer].count(m.t)) {
5703       animstack.push_back(make_pair(m.t, animations[layer][m.t]));
5704       animations[layer].erase(m.t);
5705       }
5706     }
5707   }
5708 
commitAnimations(int layer)5709 EX void commitAnimations(int layer) {
5710   LATE( commitAnimations(layer); )
5711   for(int i=0; i<isize(animstack); i++)
5712     animations[layer][animstack[i].first] = animstack[i].second;
5713   animstack.clear();
5714   }
5715 
drawBug(const cellwalker & cw,color_t col)5716 EX void drawBug(const cellwalker& cw, color_t col) {
5717 #if CAP_SHAPES
5718   initquickqueue();
5719   shiftmatrix V = ggmatrix(cw.at);
5720   if(cw.spin) V = V * ddspin(cw.at, cw.spin, M_PI);
5721   queuepoly(V, cgi.shBugBody, col);
5722   quickqueue();
5723 #endif
5724   }
5725 
inscreenrange(cell * c)5726 EX bool inscreenrange(cell *c) {
5727   if(sphere) return true;
5728   if(euclid) return celldistance(centerover, c) <= get_sightrange_ambush();
5729   if(nonisotropic) return gmatrix.count(c);
5730   if(geometry == gCrystal344) return gmatrix.count(c);
5731   return heptdistance(centerover, c) <= 8;
5732   }
5733 
5734 #if MAXMDIM >= 4
__anonf255e62f0f02null5735 auto hooksw = addHook(hooks_swapdim, 100, [] { clearAnimations(); gmatrix.clear(); gmatrix0.clear(); current_display->all_drawn_copies.clear(); });
5736 #endif
5737 
5738 }
5739