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