1 // Hyperbolic Rogue -- shoot'em up mode
2 // Copyright (C) 2011-2019 Zeno Rogue, see 'hyper.cpp' for details
3 
4 /** \file shmup.cpp
5  *  \brief shoot'em up mode
6  */
7 
8 #include "hyper.h"
9 namespace hr {
10 
11 // joysticks for controlling the mobile shmup mode
12 EX namespace shmupballs {
13   EX int xmove, xfire, yb, rad;
14 
calc()15   EX void calc() {
16     int rr = int(realradius());
17     rad = int(rr * (vid.mobilecompasssize ? vid.mobilecompasssize : 14) / 100);
18     xmove = max(current_display->xcenter - rr - rad, rad);
19     xfire = min(current_display->xcenter + rr + rad, vid.xres - rad);
20     yb = current_display->ycenter + rr - rad;
21     }
22 EX }
23 
sqdist(shiftpoint a,shiftpoint b)24 ld sqdist(shiftpoint a, shiftpoint b) {
25   if(prod) return pow(hdist(a, b), 2);
26   else return intval(a.h, unshift(b, a.shift));
27   }
28 
29 #if HDR
30 #define SCALE cgi.scalefactor
31 #define SCALE2 (SCALE*SCALE)
32 #endif
33 
34 EX namespace shmup {
35 
36 #if HDR
37 struct monster {
38   eMonster type;
39   cell *base;
40   cell *torigin;
41     // tortoises: origin
42     // butterflies: last position
43   transmatrix at;
44   shiftmatrix pat;
45   /** orientation for the product geometry */
46   transmatrix ori;
47   eMonster stk;
48   bool dead;
49   bool notpushed;
50   bool inBoat;
51   bool no_targetting;
52   monster *parent; // who shot this missile
53   int nextshot;    // when will it be able to shot (players/flailers)
54   int pid;         // player ID
55   int hitpoints;   // hitpoints; or time elapsed in Asteroids
56   int stunoff;
57   int blowoff;
58   double swordangle; // sword angle wrt at
59   double vel;        // velocity, for flail balls
60   double footphase;
61   bool isVirtual;  // off the screen: gmatrix is unknown, and pat equals at
62   hyperpoint inertia;// for frictionless lands
63 
64   int refs;         // +1 for every reference (parent, lists of active monsters)
65 
monsterhr::shmup::monster66   monster() {
67     dead = false; inBoat = false; parent = NULL; nextshot = 0;
68     stunoff = 0; blowoff = 0; footphase = 0; no_targetting = false;
69     swordangle = 0; inertia = Hypc; ori = Id; refs = 1;
70     }
71 
get_parenttypehr::shmup::monster72   eMonster get_parenttype() { return parent ? parent->type : moNone; }
73 
74   void store();
75 
76   void findpat();
77 
78   cell *findbase(const shiftmatrix& T, int maxsteps);
79 
80   void rebasePat(const shiftmatrix& new_pat, cell *tgt);
81 
remove_referencehr::shmup::monster82   void remove_reference() {
83     refs--;
84     if(!refs) {
85       if(parent) parent->remove_reference();
86       delete this;
87       }
88     }
89 
set_parenthr::shmup::monster90   void set_parent(monster *par) {
91     if(parent) parent->remove_reference();
92     parent = par;
93     parent->refs++;
94     }
95 
96   };
97 #endif
98 
99 using namespace multi;
100 
101 eItem targetRangedOrbKey(enum orbAction a);
102 eItem keyresult[MAXPLAYER];
103 
fabsl(ld x)104 ld fabsl(ld x) { return x>0?x:-x; }
105 
106 EX bool on = false;
107 EX bool delayed_safety = false;
108 EX eLand delayed_safety_land;
109 
110 bool lastdead = false;
111 
112 EX multimap<cell*, monster*> monstersAt;
113 
114 #if HDR
115 typedef multimap<cell*, monster*>::iterator mit;
116 #endif
117 
118 vector<monster*> active, nonvirtual, additional;
119 
findbaseAround(shiftpoint p,cell * around,int maxsteps)120 cell *findbaseAround(shiftpoint p, cell *around, int maxsteps) {
121 
122   if(fake::split()) {
123     auto p0 = inverse_shift(ggmatrix(around), p);
124     virtualRebase(around, p0);
125     return around;
126     }
127 
128   cell *best = around;
129   shiftmatrix T = ggmatrix(around);
130   horo_distance d0(p, T);
131 
132   for(int k=0; k<maxsteps; k++) {
133     for(int i=0; i<around->type; i++) {
134       cell *c2 = around->move(i);
135       if(c2) {
136         shiftmatrix U = ggmatrix(c2);
137         horo_distance d1(p, U);
138         if(d1 < d0) { best = c2; d0 = d1; }
139         }
140       }
141     if(best == around) break;
142     around = best;
143     }
144   return around;
145   }
146 
findbaseAround(const shiftmatrix & H,cell * around,int maxsteps)147 cell *findbaseAround(const shiftmatrix& H, cell *around, int maxsteps) {
148   return findbaseAround(tC0(H), around, maxsteps);
149   }
150 
151 /* double distance(hyperpoint h) {
152   h = spintox(h) * h;
153   return asinh(h[2]);
154   } */
155 
store()156 void monster::store() {
157   monstersAt.insert(make_pair(base, this));
158   }
159 
findpat()160 void monster::findpat() {
161   isVirtual = !gmatrix.count(base) || invalid_matrix(gmatrix[base].T);
162   if(!isVirtual) pat = gmatrix[base] * at;
163   else pat = shiftless(at);
164   }
165 
findbase(const shiftmatrix & T,int maxsteps)166 cell *monster::findbase(const shiftmatrix& T, int maxsteps) {
167   if(isVirtual) {
168     cell *c = base;
169     auto cT = T.T;
170     virtualRebase(c, cT);
171     return c;
172     }
173   else return findbaseAround(T, base, maxsteps);
174   }
175 
fix_to_2(transmatrix & T)176 void fix_to_2(transmatrix& T) {
177   if(GDIM == 3 && WDIM == 2) {
178     for(int i=0; i<4; i++) T[i][2] = 0, T[2][i] = 0;
179     T[2][2] = 1;
180     }
181   if(nonisotropic) {
182     hyperpoint h = tC0(T);
183     transmatrix rot = gpushxto0(h) * T;
184     fix_rotation(rot);
185     T = rgpushxto0(h) * rot;
186     }
187   else
188     fixmatrix(T);
189   fixelliptic(T);
190   }
191 
rebasePat(const shiftmatrix & new_pat,cell * c2)192 void monster::rebasePat(const shiftmatrix& new_pat, cell *c2) {
193   if(isVirtual) {
194     at = new_pat.T;
195     virtualRebase(this);
196     fix_to_2(at);
197     pat = shiftless(at);
198     if(multi::players == 1 && this == shmup::pc[0])
199       current_display->which_copy = back_to_view(ggmatrix(base));
200     return;
201     }
202   if(quotient || fake::split()) {
203     at = inverse_shift(gmatrix[base], new_pat);
204     transmatrix old_at = at;
205     virtualRebase(this);
206     fix_to_2(at);
207     if(base != c2) {
208       if(fake::split()) println(hlog, "fake error");
209       else {
210         auto T = calc_relative_matrix(c2, base, tC0(at));
211         base = c2;
212         at = iso_inverse(T) * at;
213         }
214       }
215     if(multi::players == 1 && this == shmup::pc[0] && !eqmatrix(old_at, at))
216       current_display->which_copy = current_display->which_copy * old_at * iso_inverse(at);
217     return;
218     }
219   if(multi::players == 1 && this == shmup::pc[0])
220     current_display->which_copy = current_display->which_copy * inverse_shift(gmatrix[base], gmatrix[c2]);
221   pat = new_pat;
222   // if(c2 != base) printf("rebase %p -> %p\n", base, c2);
223   base = c2;
224   at = inverse_shift(gmatrix[c2], pat);
225   fix_to_2(at);
226   fixelliptic(at);
227   }
228 
trackroute(monster * m,shiftmatrix goal,double spd)229 bool trackroute(monster *m, shiftmatrix goal, double spd) {
230   cell *c = m->base;
231 
232   // queuepoly(goal, shGrail, 0xFFFFFFC0);
233 
234   transmatrix mat = inverse_shift(m->pat, goal);
235 
236   transmatrix mat2 = spintox(mat*C0) * mat;
237 
238   double d = 0, dist = asinh(mat2[0][2]);
239 
240   while(d < dist) {
241     d += spd;
242     shiftmatrix nat = m->pat * rspintox(mat * C0) * xpush(d);
243 
244     // queuepoly(nat, cgi.shKnife, 0xFFFFFFC0);
245 
246     cell *c2 = findbaseAround(nat, c, 1);
247     if(c2 != c && !passable_for(m->type, c2, c, P_CHAIN | P_ONPLAYER)) {
248       return false;
249       }
250     c = c2;
251     }
252   return true;
253   }
254 
255 EX monster *pc[MAXPLAYER], *mousetarget, *lmousetarget;
256 
257 EX int curtime, nextmove, nextdragon;
258 
isBullet(monster * m)259 bool isBullet(monster *m) {
260   return isBulletType(m->type);
261   }
isPlayer(monster * m)262 bool isPlayer(monster *m) { return m->type == moPlayer; }
isMonster(monster * m)263 bool isMonster(monster *m) { return m->type != moPlayer && m->type != moBullet; }
264 
265 EX hookset<bool(shmup::monster*)> hooks_kill;
266 
killMonster(monster * m,eMonster who_kills,flagtype flags=0)267 void killMonster(monster* m, eMonster who_kills, flagtype flags = 0) {
268   int tk = tkills();
269   if(callhandlers(false, hooks_kill, m)) return;
270   if(m->dead) return;
271   m->dead = true;
272   if(isBullet(m) || isPlayer(m)) return;
273   m->stk = m->base->monst;
274   if(m->inBoat && isWatery(m->base)) {
275     m->base->wall = waBoat;
276     m->base->mondir = 0;
277     m->inBoat = false;
278     }
279   else if(m->inBoat && m->base->wall == waNone && (
280     (m->base->land == laOcean || m->base->land == laLivefjord))) {
281     m->base->wall = waStrandedBoat;
282     m->base->mondir = 0;
283     m->inBoat = false;
284     }
285   m->base->monst = m->type;
286   killMonster(m->base, who_kills, flags);
287   m->base->monst = m->stk;
288   if(multi::cpid >= 0)
289     multi::kills[multi::cpid] += tkills() - tk;
290   }
291 
pushmonsters()292 EX void pushmonsters() {
293   for(monster *m: nonvirtual) {
294     m->notpushed = isPlayer(m) || m->dead || (m->base->monst && m->base->monst != m->type);
295     if(!m->notpushed) {
296       m->stk = m->base->monst;
297       m->base->monst = m->type;
298       }
299     }
300   }
301 
popmonsters()302 EX void popmonsters() {
303   for(int i=isize(nonvirtual)-1; i>=0; i--) {
304     monster *m = nonvirtual[i];
305     if(!m->notpushed) {
306       if(m->type == m->base->monst)
307         m->base->monst = m->stk;
308       else {
309         m->dead = true; // already killed
310         // also kill all the other monsters pushed there
311         for(int j=0; j<i; j++) {
312           monster *m2 = active[j];
313           if(m2->base == m->base && !m2->notpushed)
314             killMonster(m2, moNone);
315           }
316         }
317       }
318     }
319   }
320 
degradeDemons()321 EX void degradeDemons() {
322   for(monster* m: nonvirtual) {
323     if(m->type == moGreater) m->type = moLesser;
324     if(m->stk == moGreater) m->stk = moLesser;
325     }
326   }
327 
328 // we need these for the Mimics!
329 EX double playerturn[MAXPLAYER], playergo[MAXPLAYER], playerstrafe[MAXPLAYER], playerturny[MAXPLAYER], playergoturn[MAXPLAYER], godir[MAXPLAYER];
330 EX transmatrix playersmallspin[MAXPLAYER];
331 bool playerfire[MAXPLAYER];
332 
awakenMimics(monster * m,cell * c2)333 void awakenMimics(monster *m, cell *c2) {
334   for(auto& mi: mirror::mirrors) {
335     cell *c = mi.second.at;
336 
337     transmatrix mirrortrans = Id;
338     if(mi.second.mirrored) mirrortrans[0][0] = -1;
339 
340     if(!gmatrix.count(c)) continue;
341     monster *m2 = new monster;
342     m2->base = c;
343 
344     if(isBullet(m)) {
345       m2->type = m->type;
346       m2->vel = m->vel;
347       m2->set_parent(m->parent);
348       m2->pid = m->pid;
349       }
350     else
351       m2->type = moMimic;
352 
353     hyperpoint H = inverse_shift(gmatrix[c2], tC0(gmatrix[c]));
354 
355     transmatrix xfer = rgpushxto0(H);
356 
357     if(mi.second.mirrored) {
358       hyperpoint H2 = spintox(H) * H;
359       xfer = rspintox(H) * rpushxto0(H2) * mirrortrans * spintox(H);
360       }
361 
362     m2->pat = gmatrix[c2] * xfer * inverse_shift(gmatrix[c2], m->pat);
363 
364     m2->at = inverse_shift(gmatrix[c], m2->pat);
365     m2->pid = cpid;
366 
367     additional.push_back(m2);
368 
369     // if you don't understand it, don't worry,
370     // I don't understand it either
371     }
372   mirror::mirrors.clear();
373   }
374 
375 int visibleAt;
376 
visibleFor(int t)377 EX void visibleFor(int t) {
378   visibleAt = max(visibleAt, curtime + t);
379   }
380 
bullet_velocity(eMonster t)381 ld bullet_velocity(eMonster t) {
382   switch(t) {
383     case moBullet:
384       return 1/300.;
385     case moFireball:
386       return 1/500.;
387     case moCrushball:
388       return 1/1000.;
389     case moAirball:
390       return 1/200.;
391     case moArrowTrap:
392       return 1/200.;
393     case moTongue:
394       return 1/1500.;
395     default:
396       return 1/300.;
397     }
398   }
399 
frontdir()400 int frontdir() { return WDIM == 2 ? 0 : 2; }
401 
shootBullet(monster * m)402 void shootBullet(monster *m) {
403   monster* bullet = new monster;
404   bullet->base = m->base;
405   bullet->at = m->at;
406   if(WDIM == 3) bullet->at = bullet->at * cpush(2, 0.15 * SCALE);
407   if(prod) bullet->ori = m->ori;
408   bullet->type = moBullet;
409   bullet->set_parent(m);
410   bullet->pid = m->pid;
411   bullet->inertia = m->inertia;
412   bullet->inertia[frontdir()] += bullet_velocity(m->type) * SCALE;
413   bullet->hitpoints = 0;
414 
415   additional.push_back(bullet);
416 
417   eItem orbdir[8] = {
418     itNone, itOrbSide1, itOrbThorns, itOrbSide2, itOrbSide3, itOrbSide2, itOrbThorns, itOrbSide1
419     };
420 
421   for(int i=1; i<8; i++) if(markOrb(orbdir[i])) {
422     monster* bullet = new monster;
423     bullet->base = m->base;
424     bullet->at = m->at * cspin(0, WDIM-1, M_PI/4*i);
425     if(prod) bullet->ori = m->ori;
426     if(WDIM == 3) bullet->at = bullet->at * cpush(2, 0.15 * SCALE);
427     bullet->type = moBullet;
428     bullet->set_parent(m);
429     bullet->pid = m->pid;
430     bullet->hitpoints = 0;
431     bullet->inertia = cspin(0, WDIM-1, -M_PI/4 * i) * m->inertia;
432     bullet->inertia[frontdir()] += bullet_velocity(m->type) * SCALE;
433     additional.push_back(bullet);
434     }
435   }
436 
killThePlayer(eMonster m)437 EX void killThePlayer(eMonster m) {
438   if(cpid >= 0 && cpid < MAXPLAYER && pc[cpid])
439     pc[cpid]->dead = true;
440   }
441 
playerCrash(monster * who,shiftpoint where)442 monster *playerCrash(monster *who, shiftpoint where) {
443   if(who->isVirtual) return NULL;
444   // in the racing mode, neither crashing nor getting too far away is a problem
445   if(racing::on) return NULL;
446   for(int j=0; j<players; j++) if(pc[j] && pc[j]!=who) {
447     if(pc[j]->isVirtual) continue;
448     double d = sqdist(pc[j]->pat*C0, where);
449     if(d < 0.1 * SCALE2 || d > 100 || (WDIM == 3 && hdist(tC0(pc[j]->pat), where) > sightranges[geometry]/2)) return pc[j];
450     }
451   return NULL;
452   }
453 
oceanCurrents(shiftmatrix & nat,monster * m,int delta)454 void oceanCurrents(shiftmatrix& nat, monster *m, int delta) {
455   cell *c = m->base;
456   if(c->land == laWhirlpool) {
457     for(int i=0; i<c->type; i++) {
458       cell *c2 = c->move(i);
459       if(!c2 || !gmatrix.count(c2)) continue;
460 
461       double spd = 0;
462 
463       if(celldistAlt(c2) < celldistAlt(c))
464         spd = SCALE * delta / 3000.;
465       else if(c2 == whirlpool::get(c, 1))
466         spd = SCALE * delta / 900.;
467 
468       if(spd) {
469         shiftpoint goal = tC0(gmatrix[c2]);
470 
471         // transmatrix t = spintox(H) * xpush(delta/300.) * rspintox(H);
472 
473         hyperpoint H = inverse_shift(m->pat, goal);
474         nat = nat * rspintox(H);
475         nat = nat * xpush(spd);
476         nat = nat * spintox(H);
477         }
478       }
479     }
480   }
481 
airCurrents(shiftmatrix & nat,monster * m,int delta)482 bool airCurrents(shiftmatrix& nat, monster *m, int delta) {
483   bool carried = false;
484   cell *c = m->base;
485   #if CAP_COMPLEX2
486   if(false && c->land == laWestWall) {
487     cell *c2 = ts::left_of(c, westwall::coastvalEdge1);
488 
489     double spd = SCALE * delta / 900.;
490 
491     if(m->type == moVoidBeast) spd = -spd;
492     if(spd) {
493       shiftpoint goal = tC0(gmatrix[c2]);
494 
495       // transmatrix t = spintox(H) * xpush(delta/300.) * rspintox(H);
496 
497       hyperpoint H = inverse_shift(m->pat, goal);
498       nat = nat * rspintox(H);
499       nat = nat * xpush(spd);
500       nat = nat * spintox(H);
501       carried = true;
502       }
503     }
504   #endif
505   if(c->land == laWhirlwind) {
506     whirlwind::calcdirs(c);
507     for(int i=0; i<whirlwind::qdirs; i++) {
508       cell *c2 = c->move(whirlwind::dto[i]);
509       if(!c2 || !gmatrix.count(c2)) continue;
510 
511       double spd = SCALE * delta / 900.;
512 
513       if(m->type == moVoidBeast) spd = -spd;
514       if(spd) {
515         shiftpoint goal = tC0(gmatrix[c2]);
516 
517         // transmatrix t = spintox(H) * xpush(delta/300.) * rspintox(H);
518 
519         hyperpoint H = inverse_shift(m->pat, goal);
520         nat = nat * rspintox(H);
521         nat = nat * xpush(spd);
522         nat = nat * spintox(H);
523         carried = true;
524         }
525       }
526     }
527   #if CAP_FIELD
528   if(c->land == laBlizzard) {
529     int wmc = windmap::at(c);
530     forCellEx(c2, c) {
531       if(!c2 || !gmatrix.count(c2)) continue;
532       int z = (windmap::at(c2) - wmc) & 255;
533       if(z >= 128) z -= 256;
534       if(m->type == moVoidBeast) z = -z;
535       if(z < windmap::NOWINDFROM && z > -windmap::NOWINDFROM) {
536         shiftmatrix goal = gmatrix[c2];
537 
538         // transmatrix t = spintox(H) * xpush(delta/300.) * rspintox(H);
539 
540         hyperpoint H = inverse_shift(m->pat, goal) * C0;
541         nat = nat * rspintox(H);
542         nat = nat * xpush(z * SCALE * delta / 50000.);
543         nat = nat * spintox(H);
544         carried = true;
545         }
546       }
547     }
548   #endif
549   return carried;
550   }
551 
roseCurrents(shiftmatrix & nat,monster * m,int delta)552 void roseCurrents(shiftmatrix& nat, monster *m, int delta) {
553   if(ignoresSmell(m->type)) return;
554   cell *c = m->base;
555 
556   int qty = 0;
557 
558   for(int i=0; i<c->type; i++) {
559     cell *c2 = c->move(i);
560     if(c2 && rosedist(c2) == 2) qty++;
561     }
562 
563   for(int i=0; i<c->type; i++) {
564     cell *c2 = c->move(i);
565     if(!c2 || !gmatrix.count(c2)) continue;
566     if(rosedist(c2) != 2) continue;
567 
568     double spd = SCALE * delta / 300. / qty;
569 
570     if(spd) {
571       shiftpoint goal = tC0(gmatrix[c2]);
572 
573       // transmatrix t = spintox(H) * xpush(delta/300.) * rspintox(H);
574 
575       hyperpoint H = inverse_shift(m->pat, goal);
576       nat = nat * rspintox(H);
577       nat = nat * xpush(spd);
578       nat = nat * spintox(H);
579       }
580     }
581   }
582 
keytarget(int i)583 shiftpoint keytarget(int i) {
584   double d = 2 + sin(curtime / 350.);
585   return pc[i]->pat * cpush0(WDIM == 3 ? 2 : 0, d * cgi.scalefactor);
586   }
587 
588 /* int charidof(int pid) {
589   if(players == 1) return bak_charid;
590   if(players == 2 || players == 4) return pid;
591   if(players == 3) return pid < 2 ? pid : 2+(bak_charid&1);
592   return 0;
593   } */
594 
getSwordSize()595 ld getSwordSize() { return cgi.sword_size; }
getHornsSize()596 ld getHornsSize() { return cgi.scalefactor * 0.33; }
597 
598 // used in 3D
599 EX transmatrix swordmatrix[MAXPLAYER];
600 
swordpos(int id,bool rev,double frac)601 shiftpoint swordpos(int id, bool rev, double frac) {
602   if(WDIM == 3)
603     return pc[id]->pat * swordmatrix[id] * cpush0(2, (rev?-frac:frac) * getSwordSize());
604   else
605     return pc[id]->pat * xspinpush0(pc[id]->swordangle, (rev?-frac:frac) * getSwordSize());
606   }
607 
hornpos(int id)608 shiftpoint hornpos(int id) {
609   return pc[id]->pat * xpush0(getHornsSize());
610   }
611 
612 #define IGO 9
613 
614 double igospan[IGO+1] = { 0,
615   M_PI/6, -M_PI/6,
616   M_PI/4, -M_PI/4,
617   M_PI/3, -M_PI/3,
618   M_PI/2.1, -M_PI/2.1,
619   0
620   };
621 
swordKills(eMonster m)622 bool swordKills(eMonster m) {
623   return
624     m != moHedge && m != moMetalBeast && m != moMetalBeast2
625     && m != moTortoise && m != moGreater && m != moRoseBeauty
626     && m != moReptile && !isBull(m) && m != moButterfly &&
627     m != moSalamander && m != moTerraWarrior && m != moBrownBug;
628   }
629 
hornKills(eMonster m)630 bool hornKills(eMonster m) {
631   return
632     m != moHedge && m != moMetalBeast && m != moMetalBeast2
633     && m != moTortoise && m != moGreater && m != moSkeleton
634     && m != moDraugr && m != moRoseBeauty
635     && m != moReptile && !isBull(m) && m != moButterfly && !isBulletType(m)
636     && m != moPalace && m != moFatGuard && m != moVizier &&
637     m != moSalamander && m != moTerraWarrior && m != moBrownBug;
638   }
639 
640 queue<pair<int, cell*>> traplist, firetraplist;
641 
activateArrow(cell * c)642 EX void activateArrow(cell *c) {
643   if(isCentralTrap(c))
644     traplist.emplace(ticks + 500, c);
645   }
646 
647 monster arrowtrap_fakeparent, dragon_fakeparent;
648 
doTraps()649 void doTraps() {
650   while(true) {
651     if(traplist.empty()) break;
652     auto t = traplist.front();
653     if(t.first > ticks) break;
654     int d = t.second->wparam;
655     if(d == 2) {
656       auto tl = traplimits(t.second);
657       for(int i=1; i<4; i++) if(tl[i]) tl[i]->wparam = 3;
658       traplist.emplace(t.first + 500, t.second);
659 
660       for(int i=0; i<5; i += 4) try {
661         shiftmatrix& tu = gmatrix.at(tl[i]);
662         shiftmatrix& tv = gmatrix.at(tl[4-i]);
663         monster* bullet = new monster;
664         bullet->base = tl[i];
665         bullet->at = rspintox(inverse_shift(tu, tC0(tv)));
666         bullet->type = moArrowTrap;
667         bullet->set_parent(&arrowtrap_fakeparent);
668         arrowtrap_fakeparent.type = moArrowTrap;
669         bullet->pid = 0;
670         additional.push_back(bullet);
671         }
672       catch(out_of_range&) {}
673       }
674     else if(d == 3) {
675       auto tl = traplimits(t.second);
676       for(int i=1; i<4; i++) if(tl[i]) tl[i]->wparam = 0;
677       }
678     traplist.pop();
679     }
680 
681   while(true) {
682     if(firetraplist.empty()) break;
683     auto t = firetraplist.front();
684     if(t.first > ticks) return;
685     int d = t.second->wparam;
686     if(d == 2) {
687       t.second->wparam = 0;
688       t.second->wall = waNone;
689       explosion(t.second, 5, 10);
690       }
691     firetraplist.pop();
692     }
693   }
694 
hornStuns(eMonster m)695 bool hornStuns(eMonster m) {
696   return !isBulletType(m) && m != moRoseBeauty;
697   }
698 
noncrashable(monster * m,monster * by)699 bool noncrashable(monster *m, monster *by) {
700   eMonster mt = m->type;
701   if(mt == moGreater) return true;
702   if(mt == moDraugr && by->type != moDraugr) return true;
703   if(isBull(mt)) return true;
704   if(mt == moReptile) return true;
705   if(mt == moSalamander) return true;
706   if(mt == moRoseBeauty && by->type != moRoseLady) return true;
707   if(mt == moTortoise) return true;
708   if(mt == moTerraWarrior) return true;
709   if(mt == moSkeleton) return true;
710   return false;
711   }
712 
713 int bulltime[MAXPLAYER];
714 
715 // set to P_MIRRORWALL to allow the PCs to go through mirrors
716 static const int reflectflag = P_MIRRORWALL;
717 
movePlayer(monster * m,int delta)718 void movePlayer(monster *m, int delta) {
719 
720   bool falling = isGravityLand(m->base) && cellEdgeUnstable(m->base);
721   if(m->base->land == laWestWall) falling = true;
722   if(items[itOrbAether]) falling = false;
723 
724   bool inertia_based = falling || m->base->land == laAsteroids;
725 
726   cpid = m->pid;
727 
728   #if CAP_RACING
729   if(racing::on && cpid != subscreens::current_player) return;
730   #endif
731 
732   double mturn = 0, mgo = 0, mdx = 0, mdy = 0;
733 
734   bool shotkey = false, dropgreen = false, facemouse = false;
735   if(facemouse) {
736     // silence warning that facemouse unused
737     }
738 
739   int b = 16*tableid[cpid];
740 
741     for(int i=(WDIM == 3 ? 4 : 0); i<8; i++) if(actionspressed[b+i]) playermoved = true;
742 
743   int jb = 4*tableid[cpid];
744   for(int i=0; i<4; i++) if(axespressed[jb+i]) playermoved = true;
745 
746 #if !ISMOBILE
747   mgo = actionspressed[b+pcForward] - actionspressed[b+pcBackward] + axespressed[jb+2]/30000.;
748   mturn = actionspressed[b+pcTurnLeft] - actionspressed[b+pcTurnRight] + axespressed[jb+3]/30000.;
749   mdx = actionspressed[b+pcMoveRight] - actionspressed[b+pcMoveLeft] + axespressed[jb]/30000.;
750   mdy = actionspressed[b+pcMoveDown] - actionspressed[b+pcMoveUp] + axespressed[jb+1]/30000.;
751 
752   shotkey = actionspressed[b+pcFire] || actionspressed[b+pcFaceFire];
753   facemouse = actionspressed[b+pcFace] || actionspressed[b+pcFaceFire];
754   dropgreen = actionspressed[b+pcDrop];
755 
756 #else
757   mdx = mdy = mgo = mturn = 0;
758   facemouse = shotkey = false;
759   dropgreen = getcstat == 'g';
760   using namespace shmupballs;
761 
762   if(clicked && hypot(mousex - xfire, mousey - yb) < rad) {
763     shotkey = true;
764     mdx = (mousex - xfire) / (rad/2.);
765     mdy = (mousey - yb) / (rad/2.);
766     }
767   if(clicked && hypot(mousex - xmove, mousey - yb) < rad) {
768     mdx = (mousex - xmove) / (rad/2.);
769     mdy = (mousey - yb) / (rad/2.);
770     }
771   #endif
772 
773   #if CAP_VR
774   if(vrhr::active()) {
775     if(GDIM == 3) {
776       mouseaim_x += vrhr::vraim_x * delta / 400;
777       mouseaim_y -= vrhr::vraim_y * delta / 400;
778       mdy -= vrhr::vrgo_y;
779       mdx += vrhr::vrgo_x;
780       }
781     else {
782       println(hlog, "2dim");
783       mturn -= vrhr::vraim_x + vrhr::vrgo_x;
784       mgo += vrhr::vrgo_y + vrhr::vraim_y;
785       }
786     }
787   #endif
788 
789   if(actionspressed[b+pcOrbPower] && !lactionpressed[b+pcOrbPower] && mouseover && !m->dead) {
790     cwt.at = m->base;
791     targetRangedOrb(mouseover, roKeyboard);
792     }
793 
794 #if !ISMOBILE
795   if(haveRangedOrb() && !m->dead) {
796     cwt.at = m->base;
797     if(actionspressed[b+pcOrbKey] && !lactionpressed[b+pcOrbKey])
798       keyresult[cpid] = targetRangedOrbKey(roKeyboard);
799     else
800       keyresult[cpid] = targetRangedOrbKey(roCheck);
801     }
802   else
803 #endif
804     keyresult[cpid] = itNone;
805 
806   bool stdracing = racing::on && !inertia_based;
807 
808   if(actionspressed[b+pcCenter]) {
809     if(!racing::on) {
810       centerplayer = cpid; centerpc(100); playermoved = true;
811       }
812     #if CAP_RACING
813     if(racing::on)
814       racing::player_relative = !racing::player_relative;
815     #endif
816     }
817 
818   shiftmatrix nat = m->pat;
819 
820   // if(ka == b+pcOrbPower) dropgreen = true;
821 
822   // if(mturn > 1) mturn = 1;
823   // if(mturn < -1) mturn = -1;
824 
825   #if CAP_RACING
826   if(stdracing) {
827     if(WDIM == 2) {
828       if(abs(mdy) > abs(mgo)) mgo = -mdy;
829       if(abs(mdx) > abs(mturn)) mturn = -mdx;
830       mdx = mdy = 0;
831       }
832     facemouse = shotkey = dropgreen = false;
833     if(ticks < racing::race_start_tick || !racing::race_start_tick) (WDIM == 2 ? mgo : mdy) = 0;
834     }
835   else {
836     if(racing::on && (ticks < racing::race_start_tick || !racing::race_start_tick)) mgo = mdx = mdy = 0;
837     }
838   #endif
839 
840   playerturn[cpid] = mturn * delta / 150.0;
841 
842   godir[cpid] = 0;
843 
844   if(WDIM == 2 && GDIM == 3 && (mdx || mdy)) {
845     double mdd = hypot(mdx, mdy);
846     godir[cpid] = -atan2(mdx, -mdy);
847     mgo += mdd;
848     }
849 
850   else if(WDIM == 2) {
851     double mdd = hypot(mdx, mdy);
852 
853     if(mdd > 1e-6) {
854       hyperpoint jh = hpxy(mdx/100.0, mdy/100.0);
855       shiftpoint ctr = m->pat * C0;
856 
857       if(sphere && pconf.alpha > 1.001) for(int i=0; i<3; i++) ctr[i] = -ctr[i];
858 
859       hyperpoint h = inverse_shift(m->pat, rgpushxto0(ctr) * jh);
860 
861       playerturn[cpid] = -atan2(h[1], h[0]);
862       mgo += mdd;
863       }
864     }
865 
866 #if CAP_SDL
867   const Uint8 *keystate = SDL12_GetKeyState(NULL);
868   #if CAP_SDL2
869   bool forcetarget = (keystate[SDL_SCANCODE_RSHIFT] | keystate[SDL_SCANCODE_LSHIFT]);
870   #else
871   bool forcetarget = (keystate[SDLK_RSHIFT] | keystate[SDLK_LSHIFT]);
872   #endif
873   if(((mousepressed && !forcetarget) || facemouse) && delta > 0 && !mouseout() && !stdracing && GDIM == 2) {
874     // playermoved = true;
875     hyperpoint h = inverse_shift(m->pat, mouseh);
876     playerturn[cpid] = -atan2(h[1], h[0]);
877     // nat = nat * spin(alpha);
878     // mturn += alpha * 150. / delta;
879     }
880 #endif
881 
882   bool blown = m->blowoff > curtime;
883 
884   #if CAP_MOUSEGRAB
885   if(WDIM == 2 && GDIM == 3 && !lctrlclick && cpid == 0) {
886     if(!stdracing) playerturn[cpid] -= mouseaim_x;
887     playerturny[cpid] -= mouseaim_y;
888     mouseaim_x = 0;
889     mouseaim_y = 0;
890     }
891   #endif
892 
893   if(playerturn[cpid] && canmove && !blown && WDIM == 2) {
894     m->swordangle -= playerturn[cpid];
895     rotate_object(nat.T, m->ori, spin(playerturn[cpid]));
896     if(inertia_based) m->inertia = spin(-playerturn[cpid]) * m->inertia;
897     }
898   shiftmatrix nat0 = nat;
899 
900   if(m->base->land == laWhirlpool && !markOrb(itOrbWater))
901     oceanCurrents(nat, m, delta);
902 
903   airCurrents(nat, m, delta);
904 
905   if(rosedist(m->base) == 1)
906     roseCurrents(nat, m, delta);
907 
908   if(!canmove) mgo = 0;
909 
910   if(WDIM == 2) {
911     if(mgo > 1) mgo = 1;
912     if(mgo < -1) mgo = -1;
913     if(stdracing) {
914       // braking is more efficient
915       if(m->vel * mgo < 0) mgo *= 3;
916       m->vel += mgo * delta / 600;
917       playergo[cpid] = m->vel * SCALE * delta / 600;
918       }
919 
920     else {
921       playergo[cpid] = mgo * SCALE * delta / 600;
922       }
923     }
924 
925   else if(WDIM == 3) {
926     if(mdy > 1) mdy = 1;
927     if(mdy < -1) mdy = -1;
928     if(mdx > 1) mdx = 1;
929 
930     if(mdx < -1) mdx = -1;
931     if(stdracing) {
932       if(m->vel * -mdy < 0) mdy *= 3;
933       m->vel += -mdy * delta / 600;
934       playergo[cpid] = m->vel * SCALE * delta / 600;
935       playerstrafe[cpid] = m->vel * mdx * SCALE * delta / 600;
936       }
937     else {
938       playergo[cpid] = -mdy * SCALE * delta / 600;
939       playerstrafe[cpid] = mdx * SCALE * delta / 600;
940       }
941     playerturn[cpid] = -mturn * SCALE * delta / 200;
942     playerturny[cpid] = -mgo * SCALE * delta / 200;
943 
944     #if CAP_MOUSEGRAB
945     if(!lctrlclick && cpid == 0) {
946       playerturn[cpid] += mouseaim_x;
947       playerturny[cpid] += mouseaim_y;
948       mouseaim_x = mouseaim_y = 0;
949       }
950     #endif
951     }
952 
953   if(playergo[cpid] && markOrb(itOrbDash)) playergo[cpid] *= 1.5;
954   if(playergo[cpid] && markOrb(itCurseFatigue)) playergo[cpid] *= 0.75;
955 
956   bool go = false;
957 
958   cell *c2 = m->base;
959 
960   if(blown) {
961     playergo[cpid] = -SCALE * delta / 1000.;
962     playerturn[cpid] = 0;
963     }
964 
965   m->footphase += playergo[cpid];
966 
967   if(isReptile(m->base->wall)) m->base->wparam = reptilemax();
968 
969   int steps = 1 + abs(int(playergo[cpid] / (.2 * cgi.scalefactor)));
970 
971   playergo[cpid] /= steps;
972 
973   nextstep:
974 
975   shiftmatrix nat1 = nat;
976 
977   hyperpoint avg_inertia;
978 
979   if(inertia_based && canmove) {
980     avg_inertia = m->inertia;
981     ld coef = m->base->land == laWestWall ? 0.65 : falling ? 0.15 : 1;
982     coef /= 1000;
983     if(WDIM == 3) {
984       m->inertia[frontdir()] += coef * playergo[cpid];
985       avg_inertia[frontdir()] += coef * playergo[cpid] / 2;
986       }
987     else {
988       m->inertia[0] += cos(godir[cpid]) * coef * playergo[cpid];
989       avg_inertia[0] += cos(godir[cpid]) * coef * playergo[cpid] / 2;
990       m->inertia[1] -= sin(godir[cpid]) * coef * playergo[cpid];
991       avg_inertia[1] -= sin(godir[cpid]) * coef * playergo[cpid] / 2;
992       }
993     if(falling) {
994       vector<cell*> below;
995       manual_celllister mcl;
996       mcl.add(m->base);
997       for(int i=0; i<isize(mcl.lst); i++) {
998         cell *c = mcl.lst[i];
999         bool go = false;
1000         if(c->land == laMountain) {
1001           int d = celldistAlt(c);
1002           forCellEx(c2, c) if(celldistAlt(c2) > d && gmatrix.count(c2))
1003             go = true, mcl.add(c2);
1004           }
1005         else {
1006           int d = coastvalEdge(c);
1007           forCellEx(c2, c) if(coastvalEdge(c2) < d && gmatrix.count(c2))
1008             go = true, mcl.add(c2);
1009           }
1010         if(!go) below.push_back(c);
1011         }
1012       ld cinertia = hypot_d(WDIM, m->inertia);
1013       hyperpoint drag = m->inertia * cinertia * delta / -1. / SCALE;
1014       m->inertia += drag;
1015       avg_inertia += drag/2;
1016       ld xp = SCALE / 60000. / isize(below) * delta / 15;
1017       ld yp = 0;
1018       if(cwt.at->land == laDungeon) xp = -xp;
1019       if(cwt.at->land == laWestWall) yp = xp * 1, xp *= 0.7;
1020       for(cell *c2: below) if(c2 != m->base) {
1021 
1022         hyperpoint h = rspintox(inverse_shift(m->pat, tC0(gmatrix[c2]))) * hpxy(xp, yp);
1023 
1024         m->inertia += h;
1025         avg_inertia += h/2;
1026         }
1027       }
1028     // if(inertia_based) m->inertia = spin(-playerturn[cpid]) * m->inertia;
1029     }
1030 
1031   int fspin = 0;
1032 
1033   for(int igo=0; igo<IGO && !go; igo++) {
1034 
1035     go = true;
1036 
1037     if(inertia_based) {
1038       playergoturn[cpid] = 0;
1039       if(igo) { go = false; break; }
1040       ld r = hypot_d(WDIM, avg_inertia);
1041       apply_parallel_transport(nat.T, m->ori, rspintox(avg_inertia) * xtangent(r * delta));
1042       if(WDIM == 3) rotate_object(nat.T, m->ori, cspin(0, 2, playerturn[cpid]) * cspin(1, 2, playerturny[cpid]));
1043       m->vel = r * (600/SCALE);
1044       }
1045     else if(WDIM == 3) {
1046       playersmallspin[cpid] = Id;
1047       if(igo) {
1048         fspin += 30;
1049         playersmallspin[cpid] = cspin(0, 1, fspin) * cspin(2, 0, igospan[igo]);
1050         if(fspin < 360) igo--; else fspin = 0;
1051         }
1052       nat.T = parallel_transport(nat1.T, m->ori, playersmallspin[cpid] * point3(playerstrafe[cpid], 0, playergo[cpid]));
1053       rotate_object(nat.T, m->ori, cspin(0, 2, playerturn[cpid]) * cspin(1, 2, playerturny[cpid]));
1054       m->inertia[0] = playerstrafe[cpid] / delta;
1055       m->inertia[1] = 0;
1056       m->inertia[2] = playergo[cpid] / delta;
1057       }
1058     else if(playergo[cpid]) {
1059       playergoturn[cpid] = igospan[igo]+godir[cpid];
1060       nat.T = parallel_transport(nat1.T, m->ori, spin(playergoturn[cpid]) * xtangent(playergo[cpid]));
1061       m->inertia = spin(playergoturn[cpid]) * xtangent(playergo[cpid] / delta);
1062       }
1063 
1064     // spin(span[igo]) * xpush(playergo[cpid]) * spin(-span[igo]);
1065 
1066     c2 = m->findbase(nat, 1);
1067     if(reflectflag & P_MIRRORWALL) reflect(c2, m->base, nat);
1068 
1069     // don't have several players in one spot
1070     // also don't let them run too far from each other!
1071     monster* crashintomon = NULL;
1072 
1073     if(!m->isVirtual) {
1074       crashintomon = playerCrash(m, nat*C0);
1075       for(monster *m2: nonvirtual) if(m2!=m && m2->type == passive_switch) {
1076         double d = sqdist(m2->pat*C0, nat*C0);
1077         if(d < SCALE2 * 0.2) crashintomon = m2;
1078         }
1079       }
1080     if(crashintomon) go = false;
1081 
1082     if(go && c2 != m->base) {
1083 
1084       if(c2->wall == waLake && markOrb(itOrbWinter) && !nonAdjacent(c2, m->base)) {
1085         c2->wall = waFrozenLake;
1086         if(HEAT(c2) > .5) HEAT(c2) = .5;
1087         }
1088 
1089       else if(c2->wall == waBigStatue && canPushStatueOn(m->base, P_ISPLAYER) && !nonAdjacent(c2, m->base)) {
1090         visibleFor(300);
1091         c2->wall = m->base->wall;
1092         if(cellUnstable(cwt.at))
1093           m->base->wall = waChasm;
1094         else {
1095           m->base->wall = waBigStatue;
1096           animateMovement(match(c2, m->base), LAYER_BOAT);
1097           }
1098         }
1099       else if(m->inBoat && !isWateryOrBoat(c2) && passable(c2, m->base, P_ISPLAYER | P_MIRROR | reflectflag)) {
1100         if(boatGoesThrough(c2) && markOrb(itOrbWater)) {
1101           collectItem(c2);
1102           c2->wall = isIcyLand(m->base) ? waLake : waSea;
1103           }
1104         else {
1105           if(isWatery(m->base))
1106             m->base->wall = waBoat, m->base->mondir = neighborId(m->base, c2);
1107           else if(boatStrandable(m->base))
1108             m->base->wall = waStrandedBoat;
1109           else if(boatStrandable(c2))
1110             m->base->wall = waStrandedBoat;
1111           m->inBoat = false;
1112           }
1113         }
1114       else if(isPushable(c2->wall) && !nonAdjacent(c2, m->base)) {
1115         int sd1 = neighborId(c2, m->base);
1116         int sd = m->base->c.spin(sd1);
1117         int subdir = 1;
1118         double bestd = 9999;
1119         for(int di=-1; di<2; di+=2) {
1120           cell *c = c2->modmove(sd+di);
1121           if(!c) continue;
1122           if(m->isVirtual || !gmatrix.count(c)) continue;
1123           double d = sqdist(gmatrix[c] * C0, m->pat * C0);
1124           if(d<bestd) bestd=d, subdir = di;
1125           }
1126         pushmonsters();
1127         auto mip = determinePush(cellwalker(c2, sd1)+wstep, subdir, [m] (movei mi) { return canPushThumperOn(mi, m->base); });
1128         visibleFor(300);
1129         if(!mip.proper()) go = false;
1130         else pushThumper(mip);
1131         popmonsters();
1132         }
1133       else if(c2->wall == waRose && !nonAdjacent(m->base, c2)) {
1134         m->dead = true;
1135         go = false;
1136         }
1137       else if(
1138         (blown ? !passable(c2, m->base, P_ISPLAYER | P_BLOW) : !passable(c2, m->base, P_ISPLAYER | P_MIRROR | reflectflag)) &&
1139         !(isWatery(c2) && m->inBoat && !nonAdjacent(m->base,c2)))
1140         go = false;
1141 
1142       }
1143 
1144     if(go && WDIM == 3) {
1145       for(int i=0; i<27; i++) {
1146         hyperpoint v;
1147         int i0 = i;
1148         for(int a=0; a<3; a++) v[a] = (i0 % 3) - 1, i0 /= 3;
1149         v = v * .1 / hypot_d(3, v);
1150         shiftmatrix T1 = (i == 13) ? nat : shiftless(parallel_transport(nat.T, m->ori, v), nat.shift);
1151         cell *c3 = c2;
1152         while(true) {
1153           cell *c4 = findbaseAround(tC0(T1), c3, 1);
1154           if(c4 == c3) break;
1155           if(!passable(c4, c3, P_ISPLAYER | P_MIRROR | reflectflag)) { go = false; break; }
1156           c3 = c4;
1157           }
1158         }
1159       }
1160     }
1161 
1162   if(!go || abs(playergo[cpid]) < 1e-3 || abs(playerturn[cpid]) > 1e-3) bulltime[cpid] = curtime;
1163 
1164   if(!go) {
1165     playergo[cpid] = playergoturn[cpid] = playerstrafe[cpid] = 0;
1166     if(WDIM == 3) playerturn[cpid] = playerturny[cpid] = 0;
1167     if(falling) m->inertia = m->inertia * -1;
1168     else m->inertia = Hypc;
1169     }
1170 
1171   if(go) {
1172 
1173     if(WDIM == 3) {
1174       swordmatrix[cpid] =
1175         cspin(1, 2, -playerturny[cpid]) * cspin(0, 2, -playerturn[cpid]) * swordmatrix[cpid];
1176       m->inertia = cspin(1, 2, -playerturny[cpid]) * cspin(0, 2, -playerturn[cpid]) * m->inertia;
1177       }
1178 
1179     if(c2 != m->base) {
1180       doPickupItemsWithMagnetism(c2);
1181 
1182       if(cellUnstable(m->base) && !markOrb(itOrbAether))
1183         doesFallSound(m->base);
1184 
1185       if(items[itOrbFire]) {
1186         visibleFor(800);
1187         if(makeflame(m->base, 10, false)) markOrb(itOrbFire);
1188         }
1189 
1190       if(items[itCurseWater]) {
1191         visibleFor(800);
1192         if(makeshallow(m->base, 10, false)) markOrb(itCurseWater);
1193         }
1194 
1195       if(isIcyLand(m->base) && m->base->wall == waNone && markOrb(itOrbWinter)) {
1196         invismove = false;
1197         m->base->wall = waIcewall;
1198         }
1199 
1200       if(items[itOrbDigging]) {
1201         visibleFor(400);
1202         if(earthMove(match(m->base, c2))) markOrb(itOrbDigging);
1203         }
1204 
1205       cwt.at = c2; afterplayermoved();
1206       if(c2->item && c2->land == laAlchemist) c2->wall = m->base->wall;
1207       #if CAP_COMPLEX2
1208       if(m->base->wall == waRoundTable)
1209         camelot::roundTableMessage(c2);
1210       #endif
1211       if(c2->wall == waCloud || c2->wall == waMirror) {
1212         visibleFor(500);
1213         cellwalker cw(c2, 0, false);
1214         mirror::createHere(cw, cpid);
1215         mirror::breakMirror(cw, cpid);
1216         awakenMimics(m, c2);
1217         #if CAP_RACING
1218         if(racing::on) racing::race_won();
1219         #endif
1220         }
1221       if(c2->wall == waGlass && items[itOrbAether]) {
1222         items[itOrbAether] = 0;
1223         addMessage(XLAT("Your Aether powers are drained by %the1!", c2->wall));
1224         }
1225       movecost(m->base, c2, 1);
1226 
1227       if(c2->wall == waMineMine && !markOrb(itOrbAether) && !markOrb(itOrbWinter)) {
1228         items[itOrbLife] = 0;
1229         m->dead = true;
1230         }
1231       #if CAP_COMPLEX2
1232       mine::uncover_full(c2);
1233       #endif
1234 
1235       if(isWatery(c2) && isWatery(m->base) && m->inBoat)
1236         moveItem(m->base, c2, true);
1237 
1238       destroyWeakBranch(m->base, c2, moPlayer);
1239 
1240       if(c2->wall == waClosePlate || c2->wall == waOpenPlate)
1241         toggleGates(c2, c2->wall);
1242 
1243       if(c2->wall == waArrowTrap && c2->wparam == 0 && !markOrb(itOrbAether))
1244         activateArrowTrap(c2);
1245 
1246       if(c2->wall == waFireTrap && c2->wparam == 0 && !markOrb(itOrbAether)) {
1247         c2->wparam = 2;
1248         firetraplist.emplace(ticks + 800, c2);
1249         }
1250 
1251       if(c2->item == itOrbYendor && !peace::on) yendor::check(c2);
1252       collectItem(c2);
1253       movecost(m->base, c2, 2);
1254       }
1255     }
1256 
1257   if(go) m->rebasePat(nat, c2);
1258   else m->rebasePat(nat0, m->base);
1259 
1260   if(m->base->wall == waBoat && !m->inBoat) {
1261     m->inBoat = true; m->base->wall = waSea;
1262     }
1263 
1264   if(m->base->wall == waStrandedBoat && !m->inBoat && markOrb(itOrbWater)) {
1265     m->inBoat = true; m->base->wall = waSea;
1266     }
1267 
1268   if(m->inBoat && boatStrandable(c2)) {
1269     c2->wall = waStrandedBoat;
1270     m->inBoat = false;
1271     }
1272 
1273   if(!markOrb(itOrbAether)) {
1274     if(m->base->wall == waChasm || m->base->wall == waClosedGate)
1275       m->dead = true;
1276 
1277     if(isWatery(m->base) && !m->inBoat && !markOrb(itOrbFish))
1278       m->dead = true;
1279 
1280     if(isFireOrMagma(m->base) && !markOrb(itOrbWinter))
1281       m->dead = true;
1282     }
1283 
1284   landvisited[m->base->land] = true;
1285 
1286   playerfire[cpid] = false;
1287 
1288   if(items[itOrbHorns] && !m->isVirtual) {
1289     shiftpoint H = hornpos(cpid);
1290 
1291     for(monster *m2: nonvirtual) {
1292       if(m2 == m) continue;
1293 
1294       double d = sqdist(m2->pat*C0, H);
1295 
1296       if(d < SCALE2 * 0.1) {
1297         if(hornKills(m2->type))
1298           killMonster(m2, moPlayer);
1299         else if(hornStuns(m2->type))
1300           m2->stunoff = max(m2->stunoff, curtime + 150);
1301         }
1302       }
1303     }
1304 
1305   for(int b=0; b<2; b++) if(sword::orbcount(b) && !m->isVirtual) {
1306 
1307     for(double d=0; d<=1.001; d += .1) {
1308       shiftpoint H = swordpos(cpid, b, d);
1309 
1310       for(monster *m2: nonvirtual) {
1311         if(m2 == m) continue;
1312 
1313         double d = sqdist(m2->pat*C0, H);
1314 
1315         if(d < SCALE2 * 0.1) {
1316           if(swordKills(m2->type) && !(isBullet(m2) && m2->pid == cpid))
1317               killMonster(m2, moPlayer);
1318         }
1319       }
1320 
1321       cell *c3 = findbaseAround(H, m->base, 999);
1322       if(c3->wall == waSmallTree || c3->wall == waBigTree || c3->wall == waBarrowDig || c3->wall == waCavewall ||
1323         (c3->wall == waBarrowWall && items[itBarrow] >= 25))
1324         c3->wall = waNone;
1325 
1326       else if(isWall(c3)) break;
1327       }
1328     }
1329 
1330   if(go) {
1331     // printf("#%3d: at %s\n", steps, display(nat * C0));
1332     steps--;
1333     if(steps > 0) {
1334       nat0 = nat;
1335       go = false;
1336       goto nextstep;
1337       }
1338     }
1339 
1340   #if CAP_RACING
1341   if(!go && stdracing) {
1342     if(GDIM == 3) m->vel = max(m->vel * -.5 - 0.1, 0.);
1343     else m->vel = 0;
1344     }
1345   #endif
1346 
1347   if(shotkey && canmove && curtime >= m->nextshot) {
1348 
1349     visibleFor(500);
1350     if(items[itOrbFlash]) {
1351       pushmonsters();
1352       killMonster(m->base, moNone);
1353       cwt.at = m->base;
1354       activateFlash();
1355       popmonsters();
1356       return;
1357       }
1358 
1359     if(items[itOrbLightning]) {
1360       pushmonsters();
1361       killMonster(m->base, moLightningBolt);
1362       cwt.at = m->base;
1363       activateLightning();
1364       popmonsters();
1365       return;
1366       }
1367 
1368     playerfire[cpid] = true;
1369     m->nextshot = curtime + (250 + 250 * players);
1370 
1371     turncount++;
1372     shootBullet(m);
1373     }
1374 
1375   if(dropgreen && m->base->item == itNone)
1376     dropGreenStone(m->base);
1377   }
1378 
getPlayer()1379 EX monster *getPlayer() {
1380   return pc[cpid];
1381   }
1382 
virtualize(monster * m)1383 void virtualize(monster *m) {
1384   if(doall) forCellCM(c2, m->base) if(!gmatrix.count(c2)) {
1385     m->isVirtual = true;
1386     m->pat = shiftless(m->at);
1387     return;
1388     }
1389   }
1390 
reflectmatrix(shiftmatrix & M,cell * c1,cell * c2,bool onlypos)1391 bool reflectmatrix(shiftmatrix& M, cell *c1, cell *c2, bool onlypos) {
1392   if(!gmatrix.count(c1) || !gmatrix.count(c2)) return false;
1393   transmatrix H = inverse_shift(gmatrix[c1], gmatrix[c2]);
1394   transmatrix S = spintox(tC0(H));
1395   ld d = hdist0(tC0(H));
1396   transmatrix T = xpush(-d/2) * S * inverse_shift(gmatrix[c1], M);
1397   if(onlypos && tC0(T)[0] < 0) return false;
1398   M = gmatrix[c1] * iso_inverse(S) * xpush(d/2) * MirrorX * T;
1399   return true;
1400   }
1401 
reflect(cell * & c2,cell * & mbase,shiftmatrix & nat)1402 EX int reflect(cell*& c2, cell*& mbase, shiftmatrix& nat) {
1403   int reflections = 0;
1404   if(c2 != mbase && c2->wall == waMirrorWall && inmirror(c2)) {
1405     if(reflectmatrix(nat, mbase, c2, false)) {
1406       c2 = mbase;
1407       reflections++;
1408       }
1409     }
1410 
1411   if(c2 == mbase && inmirror(c2)) {
1412     forCellEx(c3, c2) if(c3->land == laMirrorWall) {
1413       cell *c1 = mbase;
1414       mbase = c3;
1415       reflect(c3, mbase, nat);
1416       mbase = c1;
1417       c2 = c3;
1418       reflections++;
1419       }
1420     }
1421 
1422   if(c2 == mbase && c2->wall == waMirrorWall && c2->land == laMirrorWall) {
1423     int d = mirror::mirrordir(c2);
1424     if(d != -1) {
1425       for(int k=0; k<7; k++) {
1426         cell *ca = c2->cmodmove(d-k);
1427         cell *cb = c2->cmodmove(d+k);
1428         if(ca->land == laMirror && inmirror(cb)) {
1429           reflectmatrix(nat, ca, cb, true);
1430           reflections++;
1431           break;
1432           }
1433         }
1434       }
1435     else {
1436       for(int k=0; k<6; k++) {
1437         cell *cb = c2->cmodmove(k+1);
1438         cell *cc = c2->cmodmove(k+2);
1439         if(cb->land != laMirrorWall || cc->land != laMirrorWall) continue;
1440         cell *ca = c2->cmodmove(k);
1441         cell *cd = c2->cmodmove(k+3);
1442         if(reflectmatrix(nat, cc, ca, true)) reflections++;
1443         for(int limit=0; limit<10 && reflectmatrix(nat, cb, cd, true) && (reflections++, reflectmatrix(nat, cc, ca, true)); limit++) reflections+=2;
1444         }
1445       }
1446     }
1447   return reflections;
1448   }
1449 
moveMimic(monster * m)1450 void moveMimic(monster *m) {
1451   virtualize(m);
1452   shiftmatrix nat = m->pat;
1453   cpid = m->pid;
1454   m->footphase = getPlayer()->footphase;
1455 
1456   // no need to care about Mirror images, as they already have their 'at' matrix reversed :|
1457 
1458   if(WDIM == 3) {
1459     nat.T = parallel_transport(nat.T, m->ori, playersmallspin[cpid] * point3(playerstrafe[cpid], 0, playergo[cpid]));
1460     rotate_object(nat.T, m->ori, cspin(0, 2, playerturn[cpid]) * cspin(1, 2, playerturny[cpid]));
1461     }
1462   else
1463     nat = nat * spin(playerturn[cpid] + playergoturn[cpid]) * xpush(playergo[cpid]) * spin(-playergoturn[cpid]);
1464 
1465   cell *c2 = m->findbase(nat, 1);
1466   reflect(c2, m->base, nat);
1467   if(c2 != m->base && !passable(c2, m->base, P_ISPLAYER | P_MIRROR | P_MIRRORWALL))
1468     killMonster(m, moNone);
1469   else {
1470     m->rebasePat(nat, c2);
1471     if(playerfire[cpid]) shootBullet(m);
1472     }
1473 
1474   if(c2->wall == waCloud || c2->wall == waMirror) {
1475     cellwalker cw(c2, 0, false);
1476     mirror::createHere(cw, cpid);
1477     mirror::breakMirror(cw, -1);
1478     awakenMimics(m, c2);
1479     }
1480 
1481   if(!doall && c2->cpdist >= 6)
1482     m->dead = true;
1483   }
1484 
isPlayerOrImage(eMonster m)1485 bool isPlayerOrImage(eMonster m) {
1486   return isMimic(m) || m == moPlayer;
1487   }
1488 
parentOrSelf(monster * m)1489 monster *parentOrSelf(monster *m) {
1490   return m->parent ? m->parent : m;
1491   }
1492 
verifyTeleport()1493 EX bool verifyTeleport() {
1494   if(!on) return true;
1495   if(playerCrash(pc[cpid], mouseh)) return false;
1496   return true;
1497   }
1498 
destroyMimics()1499 void destroyMimics() {
1500   for(monster *m: active)
1501     if(isMimic(m->type))
1502       m->dead = true;
1503   }
1504 
teleported()1505 EX void teleported() {
1506   monster *m = pc[cpid];
1507   m->base = cwt.at;
1508   m->at = rgpushxto0(inverse_shift(gmatrix[cwt.at], mouseh)) * spin(rand() % 1000 * M_PI / 2000);
1509   m->findpat();
1510   destroyMimics();
1511   }
1512 
shoot(eItem it,monster * m)1513 void shoot(eItem it, monster *m) {
1514   monster* bullet = new monster;
1515   bullet->base = m->base;
1516   bullet->at = m->at * rspintox(inverse_shift(m->pat, mouseh));
1517   /* ori */
1518   if(WDIM == 3) bullet->at = bullet->at * cpush(2, 0.15 * SCALE);
1519   bullet->type = it == itOrbDragon ? moFireball : it == itOrbAir ? moAirball : moBullet;
1520   bullet->set_parent(m);
1521   bullet->pid = m->pid;
1522   items[it]--;
1523   additional.push_back(bullet);
1524   }
1525 
targetRangedOrbKey(orbAction a)1526 eItem targetRangedOrbKey(orbAction a) {
1527   shiftpoint h = mouseh;
1528   cell *b = mouseover;
1529   monster *mt = mousetarget;
1530 
1531   mouseh = keytarget(cpid);
1532 
1533   mouseover = pc[cpid]->base;
1534 
1535   mouseover = findbaseAround(mouseh, mouseover, 999);
1536   mousetarget = NULL;
1537 
1538   for(monster *m2: nonvirtual) {
1539     if(m2->dead) continue;
1540     if(m2->no_targetting) continue;
1541     if(!mousetarget || sqdist(mouseh, mousetarget->pat*C0) > sqdist(mouseh, m2->pat*C0))
1542       mousetarget = m2;
1543     }
1544 
1545   eItem r = targetRangedOrb(mouseover, a);
1546   // printf("A%d i %d h %p t %p ov %s => %s\n", a, cpid, mouseover, mousetarget, display(mouseh), dnameof(r));
1547 
1548   mouseh = h;
1549   mousetarget = mt;
1550   mouseover = b;
1551   return r;
1552   }
1553 
targetRangedOrb(orbAction a)1554 EX eItem targetRangedOrb(orbAction a) {
1555   if(!on) return itNone;
1556   monster *wpc = pc[cpid];
1557   if(a != roCheck && !wpc) return itNone;
1558 
1559   if(items[itOrbPsi] && shmup::mousetarget && sqdist(mouseh, shmup::mousetarget->pat*C0) < SCALE2 * .1) {
1560     if(a == roCheck) return itOrbPsi;
1561     addMessage(XLAT("You kill %the1 with a mental blast!", mousetarget->type));
1562     killMonster(mousetarget, moNone);
1563     items[itOrbPsi] -= 30;
1564     if(items[itOrbPsi]<0) items[itOrbPsi] = 0;
1565     return itOrbPsi;
1566     }
1567 
1568   if(items[itOrbStunning] && shmup::mousetarget && sqdist(mouseh, shmup::mousetarget->pat*C0) < SCALE2 * .1) {
1569     if(a == roCheck) return itOrbStunning;
1570     mousetarget->stunoff = curtime + 1000;
1571     items[itOrbStunning] -= 10;
1572     if(items[itOrbStunning]<0) items[itOrbStunning] = 0;
1573     return itOrbStunning;
1574     }
1575 
1576   if(on && items[itOrbDragon]) {
1577     if(a == roCheck) return itOrbDragon;
1578     shoot(itOrbDragon, wpc);
1579     return itOrbDragon;
1580     }
1581 
1582   if(on && items[itOrbAir]) {
1583     if(a == roCheck) return itOrbAir;
1584     shoot(itOrbAir, wpc);
1585     return itOrbAir;
1586     }
1587 
1588   if(on && items[itOrbIllusion]) {
1589     if(a == roCheck) return itOrbIllusion;
1590     shoot(itOrbIllusion, wpc);
1591     return itOrbIllusion;
1592     }
1593 
1594   return itNone;
1595   }
1596 
speedfactor()1597 int speedfactor() {
1598   return items[itOrbSpeed]?2:1;
1599   }
1600 
bulletdir()1601 int bulletdir() {
1602   return (WDIM == 2) ? 0 : 2;
1603   }
1604 
fronttangent(ld x)1605 hyperpoint fronttangent(ld x) {
1606   if(WDIM == 2) return xtangent(x);
1607   else return ztangent(x);
1608   }
1609 
collision_distance(monster * bullet,monster * target)1610 ld collision_distance(monster *bullet, monster *target) {
1611   if(target->type == moAsteroid)
1612     return SCALE * 0.15 + cgi.asteroid_size[target->hitpoints & 7];
1613   return SCALE * 0.3;
1614   }
1615 
spawn_asteroids(monster * bullet,monster * target)1616 void spawn_asteroids(monster *bullet, monster *target) {
1617   if(target->hitpoints <= 1) return;
1618   hyperpoint rnd = random_spin() * point2(SCALE/3000., 0);
1619 
1620   hyperpoint bullet_inertia = inverse_shift(target->pat, bullet->pat * bullet->inertia);
1621 
1622   for(int i=0; i<2; i++) {
1623     monster* child = new monster;
1624     child->base = target->base;
1625     child->at = target->at;
1626     child->ori = target->ori;
1627     child->type = target->type;
1628     child->pid = target->pid;
1629     child->inertia = target->inertia;
1630     child->inertia += bullet_inertia / 5;
1631     child->hitpoints = target->hitpoints - 1;
1632     if(i == 0) child->inertia += rnd;
1633     if(i == 1) child->inertia -= rnd;
1634     additional.push_back(child);
1635     }
1636   }
1637 
protect_pid(int i)1638 EX int protect_pid(int i) {
1639   if(i < 0 || i >= players) return 0;
1640   return i;
1641   }
1642 
moveBullet(monster * m,int delta)1643 void moveBullet(monster *m, int delta) {
1644   cpid = protect_pid(m->pid);
1645   m->findpat();
1646   virtualize(m);
1647 
1648   shiftmatrix nat0 = m->pat;
1649   shiftmatrix nat = m->pat;
1650 
1651   bool inertia_based = m->base->land == laAsteroids;
1652 
1653   if(m->base->land == laAsteroids) {
1654     m->hitpoints += delta;
1655     if(m->hitpoints >= (WDIM == 3 ? 750 : 500)) m->dead = true;
1656     }
1657 
1658   if(isReptile(m->base->wall)) m->base->wparam = reptilemax();
1659 
1660   if(m->type == moFlailBullet) {
1661     m->vel -= delta  / speedfactor() / 600000.0;
1662     if(m->vel < 0 && m->parent) {
1663       // return to the flailer!
1664       nat = spin_towards(m->pat, m->ori, m->parent->pat * C0, bulletdir(), -1);
1665       }
1666     }
1667   else m->vel = bullet_velocity(m->type);
1668 
1669   if(m->type == moTongue && (m->isVirtual || !m->parent || sqdist(nat*C0, m->parent->pat*C0) > SCALE2 * 0.4))
1670     m->dead = true;
1671 
1672   if(inertia_based) {
1673     nat.T = parallel_transport(nat.T, m->ori, m->inertia * delta);
1674     }
1675   else
1676     nat.T = parallel_transport(nat.T, m->ori, fronttangent(delta * SCALE * m->vel / speedfactor()));
1677   cell *c2 = m->findbase(nat, fake::split() ? 10 : 1);
1678 
1679   if(m->parent && isPlayer(m->parent) && markOrb(itOrbLava) && c2 != m->base && !isPlayerOn(m->base))
1680     makeflame(m->base, 5, false);
1681 
1682   if(isActivable(c2)) activateActiv(c2, true);
1683 
1684   // knives break mirrors and clouds
1685   if(c2->wall == waCloud || c2->wall == waMirror) {
1686     cellwalker cw(c2, 0, false);
1687     mirror::createHere(cw, cpid);
1688     mirror::breakMirror(cw, -1);
1689     awakenMimics(m, c2);
1690     }
1691 
1692   reflect(c2, m->base, nat);
1693 
1694   bool godragon = m->type == moFireball && isDragon(c2->monst);
1695 
1696   eMonster ptype = parentOrSelf(m)->type;
1697   bool slayer = m->type == moCrushball ||
1698     (markOrb(itOrbSlaying) && (markOrb(itOrbEmpathy) ? isPlayerOrImage(ptype) : ptype == moPlayer));
1699   bool weak = m->type == moAirball ||
1700     (markOrb(itCurseWeakness) && (markOrb(itOrbEmpathy) ? isPlayerOrImage(ptype) : ptype == moPlayer));
1701 
1702   if(m->type != moTongue && !(godragon || (c2==m->base && m->type == moArrowTrap) || passable(c2, m->base, P_BULLET | P_MIRRORWALL))) {
1703     m->dead = true;
1704     if(!weak && (!isDie(c2->monst) || slayer))
1705       killMonster(c2, m->get_parenttype());
1706     // cell *c = m->base;
1707     if(m->parent && isPlayer(m->parent)) {
1708       if(c2->wall == waBigTree) {
1709         addMessage(XLAT("You start chopping down the tree."));
1710         c2->wall = waSmallTree;
1711         }
1712       else if(c2->wall == waSmallTree) {
1713         addMessage(XLAT("You chop down the tree."));
1714         c2->wall = waNone;
1715         }
1716       else if(isActivable(c2))
1717         activateActiv(c2, true);
1718       else if(c2->wall == waExplosiveBarrel)
1719         explodeBarrel(c2);
1720       }
1721     if(m->type == moCrushball && c2->wall == waRuinWall)
1722       c2->wall = waNone;
1723     if(m->type == moFireball) {
1724       makeflame(c2, 20, false) || makeflame(m->base, 20, false);
1725       }
1726     }
1727   m->rebasePat(nat, c2);
1728 
1729   // destroy stray bullets
1730   if(!doall) for(int i=0; i<m->base->type; i++)
1731     if(!m->base->move(i) || !gmatrix.count(m->base->move(i)))
1732       m->dead = true;
1733 
1734   // items[itOrbWinter] = 100; items[itOrbLife] = 100;
1735 
1736   if(!m->isVirtual) for(monster* m2: nonvirtual) {
1737     if(m2 == m || (m2 == m->parent && m->vel >= 0) || m2->parent == m->parent)
1738       continue;
1739 
1740     if(m2->dead) continue;
1741 
1742     // Flailers only killable by themselves
1743     if(m2->type == moFlailer && m2 != m->parent) continue;
1744     // be nice to your images! would be too hard otherwise...
1745     if(isPlayerOrImage(parentOrSelf(m)->type) && isPlayerOrImage(parentOrSelf(m2)->type) &&
1746       m2->pid == m->pid)
1747       continue;
1748     // fireballs/airballs don't collide
1749     if(m->type == moFireball && m2->type == moFireball) continue;
1750     if(m->type == moAirball && m2->type == moAirball) continue;
1751     double d = hdist(m2->pat*C0, m->pat*C0);
1752 
1753     if(d < collision_distance(m, m2)) {
1754 
1755       if(m2->type == passive_switch) { m->dead = true; continue; }
1756 
1757       if(weak && isBlowableMonster(m2->type)) {
1758 
1759         if(m2->blowoff < curtime) {
1760           hyperpoint h = inverse_shift(m2->pat, nat0 * C0);
1761           if(WDIM == 3)
1762            swordmatrix[m2->pid] = spintox(h) * swordmatrix[m2->pid];
1763           else
1764             m2->swordangle += atan2(h[1], h[0]);
1765           m2->rebasePat(m2->pat * rspintox(h), m2->base);
1766           }
1767         m2->blowoff = curtime + 1000;
1768         continue;
1769         }
1770       // Hedgehog Warriors only killable outside of the 45 degree angle
1771       if(m2->type == moHedge && !peace::on && !slayer) {
1772         hyperpoint h = inverse_shift(m2->pat, m->pat * C0);
1773         if(h[0] > fabsl(h[1])) { m->dead = true; continue; }
1774         }
1775       if(peace::on && !isIvy(m2->type)) {
1776         m->dead = true;
1777         m2->stunoff = curtime + 600;
1778         continue;
1779         }
1780       // multi-HP monsters
1781       if((m2->type == moPalace || m2->type == moFatGuard || m2->type == moSkeleton ||
1782         m2->type == moVizier || isMetalBeast(m2->type) || m2->type == moTortoise || m2->type == moBrownBug ||
1783         m2->type == moReptile || m2->type == moSalamander || m2->type == moTerraWarrior) && m2->hitpoints > 1 && !slayer) {
1784         m2->rebasePat(spin_towards(m2->pat, m->ori, nat0 * C0, 0, 1), m2->base);
1785         if(m2->type != moSkeleton && !isMetalBeast(m2->type) && m2->type != moReptile && m2->type != moSalamander && m2->type != moBrownBug)
1786           m2->hitpoints--;
1787         m->dead = true;
1788         if(m2->type == moVizier) ;
1789         else if(m2->type == moFatGuard)
1790           m2->stunoff = curtime + 600;
1791         else if(m2->type == moTerraWarrior)
1792           m2->stunoff = curtime + 300 * (6 - m2->hitpoints);
1793         else if(m2->type == moMetalBeast || m2->type == moMetalBeast2)
1794           m2->stunoff = curtime + 3000;
1795         else if(m2->type == moReptile)
1796           m2->stunoff = curtime + 3000;
1797         else if(m2->type == moTortoise)
1798           m2->stunoff = curtime + 3000;
1799         else if(m2->type == moSkeleton && m2->base->land != laPalace)
1800           m2->stunoff = curtime + 2100;
1801         else
1802           m2->stunoff = curtime + 900;
1803         continue;
1804         }
1805       // conventional missiles cannot hurt some monsters
1806       bool conv = (m->type == moBullet || m->type == moFlailBullet || m->type == moTongue || m->type == moArrowTrap) && !slayer;
1807 
1808       // Raiders are unaffected
1809       if((m2->type == moCrusher || m2->type == moPair || m2->type == moMonk ||
1810         m2->type == moAltDemon || m2->type == moHexDemon) && conv) {
1811         m->dead = true;
1812         continue;
1813         }
1814       if(m2->type == moGreater && conv) {
1815         m->dead = true;
1816         continue;
1817         }
1818       if(m2->type == moRoseBeauty && conv && !markOrb(itOrbBeauty)) {
1819         m->dead = true;
1820         continue;
1821         }
1822       if(m2->type == moDraugr && conv) {
1823         m->dead = true;
1824         continue;
1825         }
1826       if(m2->type == moButterfly && conv) {
1827         m->dead = true;
1828         continue;
1829         }
1830       if(isBull(m2->type) && conv) {
1831         m->dead = true;
1832         // enrage herd bulls, awaken sleeping bulls
1833         m2->type = moRagingBull;
1834         continue;
1835         }
1836       // Knights reflect bullets
1837       if(m2->type == moKnight) {
1838         if(m->parent && m->parent != &arrowtrap_fakeparent) {
1839           nat = spin_towards(nat, m->ori, tC0(m->parent->pat), bulletdir(), 1);
1840           m->rebasePat(nat, m->base);
1841           }
1842         m->set_parent(m2);
1843         continue;
1844         }
1845       m->dead = true;
1846       if(m->type == moFireball) makeflame(m->base, 20, false);
1847       // Orb of Winter protects from fireballs
1848       if(m->type == moFireball && ((isPlayer(m2) && markOrb(itOrbWinter)) || m2->type == moWitchWinter))
1849         continue;
1850       int ms = mirrorspirits;
1851       killMonster(m2, m->get_parenttype());
1852       if(mirrorspirits > ms) {
1853         multi::kills[cpid]--;
1854         }
1855       if(m2->dead && m2->type == moAsteroid) {
1856         gainItem(itAsteroid);
1857         spawn_asteroids(m, m2);
1858         }
1859       }
1860     }
1861   }
1862 
1863 shiftpoint closerTo;
1864 
closer(monster * m1,monster * m2)1865 bool closer(monster *m1, monster *m2) {
1866   return sqdist(m1->pat*C0,  closerTo) < sqdist(m2->pat*C0, closerTo);
1867   }
1868 
dragonbreath(cell * dragon)1869 EX bool dragonbreath(cell *dragon) {
1870   int randplayer = hrand(numplayers());
1871   monster* bullet = new monster;
1872   bullet->base = dragon;
1873   bullet->at = spin_towards(Id, bullet->ori, inverse_shift(gmatrix[dragon], tC0(pc[randplayer]->pat)), bulletdir(), 1);
1874   bullet->type = moFireball;
1875   bullet->set_parent(&dragon_fakeparent); dragon_fakeparent.type = moDragonHead;
1876   bullet->pid = randplayer;
1877   additional.push_back(bullet);
1878   return true;
1879   }
1880 
1881 #define CHARGING (-777)
1882 #define BULLSTUN (1500)
1883 
moveMonster(monster * m,int delta)1884 void moveMonster(monster *m, int delta) {
1885 
1886   bool inertia_based = m->type == moAsteroid || m->type == moRogueviz;
1887 
1888   bool stunned = m->stunoff > curtime || m->blowoff > curtime;
1889 
1890   if(stunned && cellUnstable(m->base))
1891     doesFallSound(m->base);
1892 
1893   if(isReptile(m->base->wall)) m->base->wparam = reptilemax();
1894 
1895   if(m->base->wall == waChasm && !survivesChasm(m->type) && m->type != moReptile)
1896     killMonster(m, moNone, AF_FALL);
1897 
1898   if(m->base->wall == waRose && !survivesThorns(m->type))
1899     killMonster(m, moNone);
1900 
1901   if(among(m->type, moGreaterShark, moShark, moCShark) && !isWateryOrBoat(m->base)) {
1902     if(m->type == moGreaterShark) m->type = moGreaterM;
1903     else killMonster(m, moNone, AF_CRUSH);
1904     }
1905 
1906 
1907   if(isWatery(m->base) && !survivesWater(m->type) && !m->inBoat && m->type != moReptile)
1908     killMonster(m, moNone);
1909 
1910   if(isFireOrMagma(m->base)) {
1911     if(m->type == moSalamander)
1912       m->stunoff = max(ticks+500, m->stunoff);
1913     else if(!survivesFire(m->type))
1914       killMonster(m, moNone);
1915     }
1916 
1917   if(m->base->wall == waClosedGate && !survivesWall(m->type))
1918     killMonster(m, moNone);
1919 
1920   if(m->dead) return;
1921 
1922   cell *c = m->base;
1923   shiftmatrix goal = gmatrix[c];
1924 
1925   bool direct = false; // is there a direct path to the target?
1926   int directi = 0; // which player has direct path (to set as pid in missiles)
1927 
1928   double step = SCALE * delta/1000.0;
1929   if(m->type == moWitchSpeed)
1930     step *= 2;
1931   else if(m->type == moEagle)
1932     step *= 1.6;
1933   else if(m->type == moHunterDog)
1934     step *= (1 + .5 / numplayers());
1935   else if(m->type == moLancer)
1936     step *= 1.25;
1937   else if(isDemon(m->type)) {
1938     if(m->type == moLesserM) m->type = moLesser;
1939     if(m->type == moGreaterM) m->type = moGreater;
1940     step /= 2;
1941     }
1942   else if(m->type == moMetalBeast || m->type == moMetalBeast2)
1943     step /= 2;
1944   else if(m->type == moTortoise && peace::on)
1945     step = 0;
1946   else if(m->type == moTortoise)
1947     step /= 3;
1948   else if(isBull(m->type))
1949     step *= 1.5;
1950   else if(m->type == moAltDemon || m->type == moHexDemon || m->type == moCrusher || m->type == moMonk)
1951     step *= 1.4;
1952 
1953   if(m->type == passive_switch) step = 0;
1954 
1955   if(items[itOrbBeauty] && !m->isVirtual) {
1956     bool nearplayer = false;
1957     for(int pid=0; pid<players; pid++) if(!pc[pid]->isVirtual) {
1958       double dist = sqdist(pc[pid]->pat*C0, m->pat*C0);
1959       if(dist < SCALE2) nearplayer = true;
1960       }
1961     if(nearplayer) markOrb(itOrbBeauty), step /= 2;
1962     }
1963 
1964   if(m->isVirtual) {
1965     if(inertia_based) {
1966       ld r = hypot_d(WDIM, m->inertia);
1967       shiftmatrix nat = m->pat * rspintox(m->inertia) * xpush(r * delta) * spintox(m->inertia);
1968       m->rebasePat(nat, m->base);
1969       }
1970     return;
1971     }
1972   shiftmatrix nat = m->pat;
1973 
1974   if(stunned) {
1975     if(m->blowoff > curtime) {
1976       step = SCALE * -delta / 1000.;
1977       }
1978     else if(m->type == moFatGuard || m->type == moTortoise || m->type == moRagingBull || m->type == moTerraWarrior)
1979       step = 0;
1980     else if(m->type == moReptile)
1981       step = SCALE * -delta / 1000. * (m->stunoff - curtime) / 3000.;
1982     else if(m->type == moSalamander) {
1983       if(isFireOrMagma(m->base)) step = 0;
1984       else
1985         step = SCALE * -delta / 2000.;
1986       }
1987     else step = SCALE * -delta/2000.;
1988     }
1989 
1990   else if(m->type == moRagingBull && m->stunoff == CHARGING) ;
1991 
1992   else if(inertia_based) ;
1993 
1994   else {
1995 
1996     if(m->type == moSleepBull && !m->isVirtual) {
1997       for(monster *m2: nonvirtual) if(m2!=m && m2->type != moBullet && m2->type != moArrowTrap) {
1998         double d = sqdist(m2->pat*C0, nat*C0);
1999         if(d < SCALE2*3 && m2->type == moPlayer) m->type = moRagingBull;
2000         }
2001       }
2002 
2003     if(m->type == moWitchFlash) for(int pid=0; pid<players; pid++) {
2004       if(pc[pid]->isVirtual) continue;
2005       if(m->isVirtual) continue;
2006       bool okay = sqdist(pc[pid]->pat*C0, m->pat*C0) < 2 * SCALE2;
2007       for(monster *m2: nonvirtual) {
2008         if(m2 != m && isWitch(m2->type) && sqdist(m2->pat*C0, m->pat*C0) < 2 * SCALE2)
2009           okay = false;
2010         }
2011       if(okay) {
2012         addMessage(XLAT("%The1 activates her Flash spell!", m->type));
2013         pushmonsters();
2014         activateFlashFrom(m->base, moWitchFlash, AF_MAGIC | AF_GETPLAYER | AF_MSG);
2015         popmonsters();
2016         m->type = moWitch;
2017         pc[pid]->dead = true;
2018         }
2019       }
2020     if(isBug(m->type)) {
2021       vector<monster*> bugtargets;
2022       for(monster *m2: nonvirtual)
2023         if(!isBullet(m2))
2024         if(m2->type != m->type)
2025         if(!isPlayer(m2) || !invismove)
2026         if(!m2->dead)
2027           bugtargets.push_back(m2);
2028 
2029       closerTo = m->pat * C0;
2030       sort(bugtargets.begin(), bugtargets.end(), closer);
2031 
2032       if(step) for(monster *m2: bugtargets)
2033         if(trackroute(m, m2->pat, step)) {
2034           goal = m2->pat;
2035           direct = true;
2036           break;
2037           }
2038       }
2039     else if(m->type == moWolf && !peace::on) {
2040       cell *cnext = c;
2041       for(int i=0; i<c->type; i++) {
2042         cell *c2 = c->move(i);
2043         if(c2 && gmatrix.count(c2) && (c2->land == laVolcano || (isIcyLand(c2) && HEAT(c2) > HEAT(c))) && passable(c2, c, 0))
2044           cnext = c2;
2045         }
2046       goal = gmatrix[cnext];
2047       direct = true;
2048       directi = 0;
2049       }
2050     #if CAP_FIELD
2051     else if(m->type == moHerdBull) {
2052       cell *cnext = prairie::next(c);
2053       if(cnext && gmatrix.count(cnext)) {
2054         goal = gmatrix[cnext];
2055         direct = true;
2056         directi = 0;
2057         }
2058       else m->dead = true;
2059       }
2060     #endif
2061     else if(m->type == moButterfly) {
2062       int d = neighborId(m->base, m->torigin);
2063       cell *cnext = NULL;
2064       for(int u=2; u<m->base->type; u++) {
2065         cell *c2 = createMov(m->base, (d+u) % m->base->type);
2066         if(passable_for(m->type, c2, m->base, P_ONPLAYER)) {
2067           cnext = c2;
2068           break;
2069           }
2070         }
2071 
2072       if(cnext && gmatrix.count(cnext)) {
2073         goal = gmatrix[cnext];
2074         direct = true;
2075         directi = 0;
2076         }
2077       }
2078     else if(!direct && !invismove && !peace::on) {
2079       for(int i=0; i<players; i++)
2080         if(step && trackroute(m, pc[i]->pat, step) && (!direct || sqdist(pc[i]->pat*C0, m->pat*C0) < sqdist(goal*C0,m->pat*C0))) {
2081           goal = pc[i]->pat;
2082           direct = true;
2083           directi = i;
2084           // m->trackrouteView(pc->pat, step);
2085           }
2086         }
2087 
2088     if(!direct && !peace::on) while(true) {
2089       if(step && trackroute(m, gmatrix[c], step))
2090         goal = gmatrix[c];
2091       cell *cnext = c;
2092       for(int i=0; i<c->type; i++) {
2093         cell *c2 = c->move(i);
2094         if(c2 && gmatrix.count(c2) && c2->pathdist < c->pathdist &&
2095           passable_for(m->type, c2, c, P_CHAIN | P_ONPLAYER))
2096           cnext = c2;
2097         }
2098       if(cnext == c) break;
2099       c = cnext;
2100       }
2101 
2102     if(m->type == moHedge) {
2103       hyperpoint h = inverse_shift(m->pat, tC0(goal));
2104       if(h[1] < 0)
2105         nat = nat * spin(M_PI * delta / 3000 / speedfactor());
2106       else
2107         nat = nat * spin(M_PI * -delta / 3000 / speedfactor());
2108       m->rebasePat(nat, m->base);
2109       // at most 45 degrees
2110       if(h[0] < fabsl(h[1])) return;
2111       }
2112     else if(!peace::on) {
2113       nat = spin_towards(m->pat, m->ori, tC0(goal), 0, 1);
2114       }
2115     }
2116 
2117   if(m->type == moVampire && !m->isVirtual) for(int i=0; i<players; i++)
2118   if(!pc[i]->isVirtual && sqdist(m->pat*C0, pc[i]->pat*C0) < SCALE2 * 2) {
2119     for(int i=0; i<ittypes; i++)
2120       if(itemclass(eItem(i)) == IC_ORB && items[i] && items[itOrbTime] && !orbused[i])
2121         orbused[i] = true;
2122     step = 0;
2123     }
2124 
2125   bool carried = false;
2126 
2127   if(c->land == laWhirlpool && (m->type == moShark || m->type == moCShark || m->type == moPirate))
2128     oceanCurrents(nat, m, delta), carried = true;
2129 
2130   if(m->type != moGhost && m->type != moFriendlyGhost && m->type != moAirElemental)
2131     carried |= airCurrents(nat, m, delta);
2132 
2133   if(rosedist(m->base) == 1)
2134     roseCurrents(nat, m, delta), carried = true;
2135 
2136   step /= speedfactor();
2137 
2138   if(WDIM == 3) step /= 3;
2139 
2140   int igo = 0;
2141 
2142   shiftmatrix nat0 = nat;
2143 
2144   igo_retry:
2145 
2146   if(igo == IGO && peace::on)
2147     nat0 = nat0 * spin(rand() % 16);
2148 
2149   else if(igo >= IGO) {
2150     if(m->type == moHerdBull) m->type = moRagingBull;
2151     return;
2152     }
2153 
2154   if(igo == 1 && m->type == moRagingBull && m->stunoff == CHARGING) {
2155     m->stunoff = curtime + BULLSTUN;
2156     return;
2157     }
2158 
2159   if(inertia_based) {
2160     if(igo) return;
2161     nat.T = parallel_transport(nat.T, m->ori, m->inertia * delta);
2162     }
2163   else if(WDIM == 3 && igo) {
2164     ld fspin = rand() % 1000;
2165     nat.T = parallel_transport(nat0.T, m->ori, cspin(1,2,fspin) * spin(igospan[igo]) * xtangent(step));
2166     }
2167   else {
2168     nat.T = parallel_transport(nat0.T, m->ori, spin(igospan[igo]) * xtangent(step));
2169     }
2170 
2171   if(m->type != moRagingBull && !peace::on)
2172   if(sqdist(nat*C0, goal*C0) >= sqdist(m->pat*C0, goal*C0) && !stunned && !carried && !inertia_based) {
2173     igo++; goto igo_retry; }
2174 
2175   for(int i=0; i<multi::players; i++) for(int b=0; b<2; b++) if(sword::orbcount(b)) {
2176     if(pc[i]->isVirtual) continue;
2177     shiftpoint H = swordpos(i, b, 1);
2178     double d = sqdist(H, nat*C0);
2179     if(d < SCALE2 * 0.12) { igo++; goto igo_retry; }
2180     }
2181 
2182   m->footphase += step;
2183 
2184   monster* crashintomon = NULL;
2185 
2186   if(!m->isVirtual && !inertia_based) for(monster *m2: nonvirtual) if(m2!=m && m2->type != moBullet && m2->type != moArrowTrap) {
2187     double d = sqdist(m2->pat*C0, nat*C0);
2188     if(d < SCALE2 * 0.1) crashintomon = m2;
2189     }
2190 
2191   if(inertia_based) for(int i=0; i<players; i++) if(pc[i] && hdist(tC0(pc[i]->pat), tC0(m->pat)) < collision_distance(pc[i], m))
2192     crashintomon = pc[i];
2193 
2194   if(!peace::on)
2195   for(int i=0; i<players; i++)
2196     if(crashintomon == pc[i])
2197       pc[i]->dead = true;
2198 
2199   if(peace::on) ;
2200 
2201   else if(crashintomon && isMimic(crashintomon->type)) {
2202     killMonster(crashintomon, m->type);
2203     crashintomon = NULL;
2204     }
2205 
2206   else if(crashintomon && (
2207     items[itOrbDiscord] || isBull(m->type) ||
2208        ((isBug(m->type) || isBug(crashintomon->type)) && m->type != crashintomon->type))
2209     && !isBullet(crashintomon)) {
2210     if(noncrashable(crashintomon, m)) {
2211       if(isBull(crashintomon->type)) crashintomon->type = moRagingBull;
2212       }
2213     else {
2214       killMonster(crashintomon, m->type, isBull(m->type) ? AF_BULL : 0);
2215       crashintomon = NULL;
2216       }
2217     }
2218 
2219   if(crashintomon && !inertia_based) { igo++; goto igo_retry; }
2220 
2221   cell *c2 = m->findbase(nat, 1);
2222   if(reflectflag & P_MIRRORWALL) reflect(c2, m->base, nat);
2223 
2224   if(m->type == moButterfly && !passable_for(m->type, c2, m->base, P_CHAIN | reflectflag)) {
2225     igo++; goto igo_retry;
2226     }
2227 
2228   if(isPlayerOn(c2) && !peace::on) {
2229     bool usetongue = false;
2230     if(isSlimeMover(m->type) || m->type == moWaterElemental) usetongue = true;
2231     if(isWatery(c2) && !survivesWater(m->type) && !m->inBoat) usetongue = true;
2232     if(c2->wall == waChasm && !survivesChasm(m->type)) usetongue = true;
2233     if(isFireOrMagma(c2) && !survivesFire(m->type) && !m->inBoat) usetongue = true;
2234     if(isBird(m->type) && !passable_for(moEagle, c2, c, 0)) usetongue = true;
2235     if((m->type == moMonk || m->type == moAltDemon || m->type == moHexDemon) && !passable_for(m->type, c2, c, 0))
2236       usetongue = true;
2237     if(usetongue) {
2238       if(curtime < m->nextshot) return;
2239       // m->nextshot = curtime + 25;
2240       monster* bullet = new monster;
2241       bullet->base = m->base;
2242       bullet->at = m->at;
2243       bullet->ori = m->ori;
2244       bullet->type = moTongue;
2245       bullet->set_parent(m);
2246       bullet->pid = whichPlayerOn(c2);
2247       additional.push_back(bullet);
2248       return;
2249       }
2250     }
2251 
2252   if(!ignoresPlates(m->type)) destroyWeakBranch(m->base, c2, m->type);
2253 
2254   if(c2 != m->base && (c2->wall == waClosePlate || c2->wall == waOpenPlate) && !ignoresPlates(m->type))
2255     toggleGates(c2, c2->wall, 3);
2256 
2257   if(c2 != m->base && c2->wall == waArrowTrap && c2->wparam == 0 && !ignoresPlates(m->type))
2258     activateArrowTrap(c2);
2259 
2260   if(c2 != m->base && c2->wall == waFireTrap && c2->wparam == 0 && !ignoresPlates(m->type)) {
2261     c2->wparam = 2;
2262     firetraplist.emplace(ticks + 800, c2);
2263     }
2264 
2265   if(c2 != m->base && mayExplodeMine(c2, m->type))
2266     killMonster(m, moNone);
2267 
2268   if(c2 != m->base && c2->wall == waRose && !nonAdjacent(m->base, c2) && !survivesThorns(m->type))
2269     killMonster(m, moNone);
2270 
2271   if(c2 != m->base && cellUnstable(m->base) && !ignoresPlates(m->type))
2272     doesFallSound(m->base);
2273 
2274   if(m->type == moWolf && c2->land == laVolcano) m->type = moLavaWolf;
2275   if(m->type == moLavaWolf && isIcyLand(c2)) m->type = moWolf;
2276 
2277   if(c2 != m->base && m->type == moWitchFire) makeflame(m->base, 10, false);
2278   if(c2 != m->base && m->type == moFireElemental) makeflame(m->base, 20, false);
2279   if(c2 != m->base && m->type == moWaterElemental) placeWater(c2, m->base);
2280   if(c2 != m->base && m->type == moEarthElemental) {
2281     earthMove(match(m->base, c2));
2282     }
2283 
2284   if(m->type == moReptile && c2 != m->base) {
2285     if(c2->wall == waChasm) {
2286       c2->wall = waReptile;
2287       c2->wparam = reptilemax();
2288       playSound(c, "click");
2289       m->dead = true;
2290       }
2291     else if(isChasmy(c2) || isWatery(c2)) {
2292       c2->wall = waReptileBridge;
2293       c2->item = itNone;
2294       c2->wparam = reptilemax();
2295       playSound(c, "click");
2296       m->dead = true;
2297       }
2298     }
2299 
2300   if(c2 != m->base && m->type == moNecromancer && !c2->monst) {
2301     for(int i=0; i<m->base->type; i++) {
2302       cell *c3 = m->base->move(i);
2303       if(neighborId(c3, c2) != -1 && c3->wall == waFreshGrave && gmatrix.count(c3)) {
2304         bool monstersNear = false;
2305         for(monster *m2: nonvirtual) {
2306           if(m2 != m && sqdist(m2->pat*C0, gmatrix[c3]*C0) < SCALE2 * .3)
2307             monstersNear = true;
2308           if(m2 != m && sqdist(m2->pat*C0, gmatrix[c2]*C0) < SCALE2 * .3)
2309             monstersNear = true;
2310           }
2311         if(!monstersNear) {
2312 
2313           monster* undead = new monster;
2314           undead->base = c2;
2315           undead->at = Id;
2316           undead->type = moZombie;
2317           undead->set_parent(m);
2318           undead->pid = 0;
2319           undead->findpat();
2320           additional.push_back(undead);
2321 
2322           undead = new monster;
2323           undead->base = c3;
2324           undead->at = Id;
2325           undead->type = moGhost;
2326           undead->set_parent(m);
2327           undead->findpat();
2328           undead->pid = 0;
2329           additional.push_back(undead);
2330 
2331           c3->wall = waAncientGrave;
2332           addMessage(XLAT("%The1 raises some undead!", m->type));
2333           }
2334         }
2335       }
2336     }
2337   if(m->type == moGreaterShark) {
2338     if(c2->wall == waBoat)
2339       c2->wall = waNone;
2340     if(c2->wall == waFrozenLake)
2341       c2->wall = waLake;
2342     }
2343   if(m->type == moDarkTroll && c2->wall == waCavefloor) {
2344     m->type = moTroll;
2345     }
2346   if(isLeader(m->type)) {
2347     if(c2 != m->base) {
2348       if(c2->wall == waBigStatue && canPushStatueOn(m->base, 0)) {
2349         c2->wall = m->base->wall;
2350         if(cellUnstable(m->base))
2351           m->base->wall = waChasm;
2352         else
2353           m->base->wall = waBigStatue;
2354         animateMovement(match(c2, m->base), LAYER_BOAT);
2355         }
2356       if(passable_for(m->type, c2, m->base, P_CHAIN | P_ONPLAYER | reflectflag) && !isWatery(c2) && m->inBoat) {
2357         if(isWatery(m->base))
2358           m->base->wall = waBoat, m->base->mondir = neighborId(m->base, c2);
2359         else if(boatStrandable(c2)) c2->wall = waStrandedBoat;
2360         else if(boatStrandable(m->base)) m->base->wall = waStrandedBoat;
2361         m->inBoat = false;
2362         }
2363       if(isWatery(c2) && isWatery(m->base) && m->inBoat)
2364         moveItem(m->base, c2, true);
2365       }
2366     if(c2->wall == waBoat && !m->inBoat) {
2367       m->inBoat = true; c2->wall = waSea;
2368       m->base = c2;
2369       }
2370     }
2371 
2372   if(peace::on && c2->mpdist > 7) return;
2373 
2374   if(!(m->type == moRoseBeauty && c2->land != laRose)) {
2375     if(stunned ? passable(c2, m->base, P_BLOW | reflectflag) : passable_for(m->type, c2, m->base, P_CHAIN | reflectflag)) {
2376       if(c2 != m->base && m->type == moButterfly)
2377         m->torigin = m->base;
2378       m->rebasePat(nat, c2);
2379       if(m->type == moRagingBull && step > 1e-6) m->stunoff = CHARGING;
2380       }
2381     else {
2382       if(peace::on) { igo++; goto igo_retry; }
2383       if(m->type == moRagingBull && m->stunoff == CHARGING)
2384         m->stunoff = curtime + BULLSTUN;
2385       }
2386     }
2387 
2388   if(direct) {
2389     if((m->type == moPyroCultist || m->type == moCrystalSage) && curtime >= m->nextshot) {
2390       monster* bullet = new monster;
2391       bullet->base = m->base;
2392       bullet->at = m->at;
2393       bullet->ori = m->ori;
2394       bullet->type = moFireball;
2395       bullet->set_parent(m);
2396       additional.push_back(bullet);
2397       bullet->pid = directi;
2398       if(m->type == moPyroCultist)
2399         m->type = moCultist;
2400       else
2401         m->nextshot = curtime + 100;
2402       }
2403     if(m->type == moOutlaw && curtime >= m->nextshot) {
2404       monster* bullet = new monster;
2405       bullet->base = m->base;
2406       bullet->at = m->at;
2407       bullet->ori = m->ori;
2408       bullet->type = moBullet;
2409       bullet->set_parent(m);
2410       bullet->pid = directi;
2411       additional.push_back(bullet);
2412       m->nextshot = curtime + 1500;
2413       }
2414     for(int i=0; i<players; i++) if(!pc[i]->isVirtual)
2415     if((m->type == moAirElemental) && curtime >= m->nextshot && sqdist(m->pat*C0, pc[i]->pat*C0) < SCALE2 * 2) {
2416       monster* bullet = new monster;
2417       bullet->base = m->base;
2418       bullet->at = m->at;
2419       bullet->ori = m->ori;
2420       bullet->type = moAirball;
2421       bullet->set_parent(m);
2422       bullet->pid = i;
2423       additional.push_back(bullet);
2424       m->nextshot = curtime + 1500;
2425       }
2426     for(int i=0; i<players; i++) if(!pc[i]->isVirtual)
2427       if(m->type == moTortoise && tortoise::seek() && !tortoise::diff(getBits(m->torigin)) && sqdist(m->pat*C0, pc[i]->pat*C0) < SCALE2) {
2428         items[itBabyTortoise] += 4;
2429         m->dead = true;
2430         tortoise_hero_message(m->base);
2431         }
2432     for(int i=0; i<players; i++) if(!pc[i]->isVirtual)
2433     if(m->type == moFlailer && curtime >= m->nextshot &&
2434       sqdist(m->pat*C0, pc[i]->pat*C0) < SCALE2 * 2) {
2435       m->nextshot = curtime + 3500;
2436       monster* bullet = new monster;
2437       bullet->base = m->base;
2438       bullet->at = m->at;
2439       bullet->ori = m->ori;
2440       bullet->type = moFlailBullet;
2441       bullet->set_parent(m);
2442       bullet->vel = 1/400.0;
2443       bullet->pid = i;
2444       additional.push_back(bullet);
2445       break;
2446       }
2447     for(int i=0; i<players; i++) if(!pc[i]->isVirtual)
2448     if(m->type == moCrusher && sqdist(m->pat*C0, pc[i]->pat*C0) < SCALE2 * .75) {
2449       m->stunoff = curtime + 1500;
2450       monster* bullet = new monster;
2451       bullet->base = m->base;
2452       bullet->at = m->at;
2453       bullet->ori = m->ori;
2454       bullet->type = moCrushball;
2455       bullet->set_parent(m);
2456       bullet->pid = i;
2457       additional.push_back(bullet);
2458       break;
2459       }
2460     for(int i=0; i<players; i++) if(!pc[i]->isVirtual)
2461     if(m->type == moHexer && m->base->item && (classflag(m->base->item) & IF_CURSE) && sqdist(m->pat*C0, pc[i]->pat*C0) < SCALE2 * 2) {
2462       addMessage(XLAT("%The1 curses you with %the2!", m->type, m->base->item));
2463       items[m->base->item] += orbcharges(m->base->item);
2464       m->base->item = itNone;
2465       m->stunoff = curtime + 250;
2466       break;
2467       }
2468     }
2469   }
2470 
activateMonstersAt(cell * c)2471 void activateMonstersAt(cell *c) {
2472   pair<mit, mit> p =
2473     monstersAt.equal_range(c);
2474   for(mit it = p.first; it != p.second;) {
2475     mit itplus = it;
2476     itplus++;
2477     active.push_back(it->second);
2478     monstersAt.erase(it);
2479     it = itplus;
2480     }
2481   if(c->monst && isMimic(c->monst)) c->monst = moNone;
2482   // mimics are awakened by awakenMimics
2483   if(c->monst && !isIvy(c) && !isWorm(c) && !isMutantIvy(c) && !isKraken(c->monst) && c->monst != moPrincess && c->monst != moHunterGuard && !isDie(c->monst)) {
2484     // awaken as a monster
2485     monster *enemy = new monster;
2486     enemy->at = Id;
2487     enemy->base = c;
2488     if(enemy->type == moButterfly)
2489       enemy->torigin = createMov(c, (c->mondir + 419) % c->type);
2490     enemy->torigin = c;
2491     enemy->type = c->monst;
2492     enemy->hitpoints = c->hitpoints;
2493     if(c->wall == waBoat && isLeader(c->monst))
2494       enemy->inBoat = true, c->wall = waSea;
2495     if(c->monst == moAsteroid) {
2496       enemy->inertia = random_spin() * point2(SCALE/3000., 0);
2497       }
2498     c->monst = moNone;
2499     active.push_back(enemy);
2500     }
2501   }
2502 
fixStorage()2503 EX void fixStorage() {
2504 
2505   vector<monster*> restore;
2506 
2507   for(auto it = monstersAt.begin(); it != monstersAt.end(); it++)
2508     restore.push_back(it->second);
2509 
2510   monstersAt.clear();
2511 
2512   for(monster *m: restore) m->store();
2513   }
2514 
2515 EX hookset<bool(int)> hooks_turn;
2516 
turn(int delta)2517 EX void turn(int delta) {
2518 
2519   if(racing::on && subscreens::split( [delta] () { turn(delta); })) return;
2520 
2521   int id = 0;
2522   #if CAP_MOUSEGRAB
2523   ld maimx = mouseaim_x;
2524   ld maimy = mouseaim_y;
2525   #else
2526   ld maimx, maimy;
2527   #endif
2528 
2529   if(dual::split( [&id, maimx, maimy, delta] () {
2530     turn(delta); id++;
2531     #if CAP_MOUSEGRAB
2532     if(id==1) mouseaim_x = maimx, mouseaim_y = maimy;
2533     #endif
2534     })) return;
2535 
2536   if(callhandlers(false, hooks_turn, delta)) return;
2537 
2538   lmousetarget = NULL;
2539   if(mousetarget && !mousetarget->isVirtual && sqdist(mouseh, mousetarget->pat*C0) < 0.1)
2540     lmousetarget = mousetarget;
2541 
2542   if(!shmup::on) return;
2543   if(!(cmode & sm::NORMAL)) {
2544     #if CAP_RACING
2545     if(racing::on) {
2546       if(racing::race_start_tick) racing::race_start_tick += delta;
2547       for(int& i: racing::race_finish_tick) if(i) i += delta;
2548       }
2549     #endif
2550     return;
2551     }
2552 
2553   passive_switch = (gold() & 1) ? moSwitch1 : moSwitch2;
2554 
2555   if(delta > 1000) delta = 1000;
2556 
2557   if(delta > 200) { turn(200); delta -= 200; if(!delta) return; }
2558 
2559   curtime += delta;
2560 
2561   handleInput(delta);
2562 
2563   invismove = (curtime >= visibleAt) && markOrb(itOrbInvis);
2564 
2565   // detect active monsters
2566   if(doall)
2567     for(cell *c: currentmap->allcells()) activateMonstersAt(c);
2568   else
2569     for(map<cell*, shiftmatrix>::iterator it = gmatrix.begin(); it != gmatrix.end(); it++)
2570       activateMonstersAt(it->first);
2571 
2572   /* printf("size: gmatrix = %ld, active = %ld, monstersAt = %ld, delta = %d\n",
2573     gmatrix.size(), active.size(), monstersAt.size(),
2574     delta); */
2575 
2576   bool exists[motypes];
2577 
2578   for(int i=0; i<motypes; i++) exists[i] = false;
2579 
2580   nonvirtual.clear();
2581   for(monster *m: active) {
2582     m->findpat();
2583     if(m->isVirtual) continue;
2584     else nonvirtual.push_back(m);
2585     exists[movegroup(m->type)] = true;
2586     }
2587 
2588   for(monster *m: active) {
2589 
2590     switch(m->type) {
2591       case moPlayer:
2592         movePlayer(m, delta);
2593         break;
2594 
2595       case moBullet: case moFlailBullet: case moFireball: case moTongue: case moAirball:
2596       case moArrowTrap: case moCrushball:
2597         moveBullet(m, delta);
2598         break;
2599 
2600       default: ;
2601       }
2602     }
2603 
2604   for(monster *m: active) {
2605     if(isMimic(m->type))
2606       moveMimic(m);
2607     }
2608 
2609   for(int t=1; t<motypes; t++) if(exists[t]) {
2610 
2611     pathdata pd(1);
2612 
2613     // build the path data
2614 
2615     for(cell *c: targets)
2616       onpath(c, isPlayerOn(c) ? 0 : 1);
2617 
2618     int qb = 0;
2619     for(qb=0; qb < isize(pathq); qb++) {
2620       cell *c = pathq[qb];
2621       int d = c->pathdist;
2622       if(d == PINFD-1) continue;
2623       for(int i=0; i<c->type; i++) {
2624         cell *c2 = c->move(i);
2625         // printf("i=%d cd=%d\n", i, c->move(i)->cpdist);
2626         if(c2 && c2->pathdist == PINFD && gmatrix.count(c2) &&
2627           (passable_for(eMonster(t), c, c2, P_CHAIN | P_ONPLAYER) || c->wall == waThumperOn)) {
2628           onpath(c2, d+1);
2629           }
2630         }
2631       }
2632 
2633     // printf("time %d, t=%d, q=%d\n", curtime, t, qb);
2634 
2635     // move monsters of this type
2636 
2637     for(monster *m: nonvirtual)
2638       if(movegroup(m->type) == t)
2639         moveMonster(m, delta);
2640     }
2641 
2642   if(shmup::on) {
2643 
2644     doTraps();
2645 
2646     bool tick = curtime >= nextmove;
2647     keepLightning = ticks <= lightat + 1000;
2648     cwt.at = pc[0]->base;
2649     bfs(); moverefresh(tick);
2650     countLocalTreasure();
2651     pushmonsters();
2652     if(items[itOrbFreedom])
2653       for(int i=0; i<players; i++)
2654         checkFreedom(pc[i]->base);
2655     heat::processheat(delta / 350.0);
2656     markOrb(itOrbSpeed);
2657 
2658     if((havewhat&HF_DRAGON) && curtime >= nextdragon) {
2659       groupmove(moDragonHead, 0);
2660       nextdragon = curtime + 1500;
2661       }
2662 
2663     if(tick) {
2664       nextmove += 1000;
2665       flashMessages();
2666       reduceOrbPowers();
2667       if(items[itOrbBull]) for(int p=0; p<players; p++)
2668         if(bulltime[p] < curtime - 600) orbbull::gainBullPowers();
2669 
2670       if(!((items[itOrbSpeed]/players) & 1)) {
2671         if(havewhat&HF_KRAKEN) kraken::attacks(), groupmove(moKrakenH, 0);
2672         moveworms();
2673         moveivy();
2674         movemutant();
2675         if(havewhat&HF_HEX) movehex_all();
2676         if(havewhat & HF_DICE) groupmove(moAnimatedDie, 0);
2677         wandering();
2678         livecaves();
2679         #if CAP_INV
2680         if(inv::on) inv::compute();
2681         #endif
2682         #if CAP_COMPLEX2
2683         terracotta::check();
2684         #endif
2685         heat::processfires();
2686         if(havewhat&HF_WHIRLPOOL) whirlpool::move();
2687         if(havewhat&HF_WHIRLWIND) whirlwind::move();
2688         #if CAP_COMPLEX2
2689         if(havewhat & HF_WESTWALL) westwall::move();
2690         #endif
2691         buildRosemap();
2692         if(havewhat&HF_RIVER) prairie::move();
2693         }
2694       if(recallCell.at && !markOrb(itOrbRecall)) activateRecall();
2695       save_memory();
2696       }
2697     if(elec::havecharge) elec::act();
2698     popmonsters();
2699 
2700     bool lastcanmove = canmove;
2701 
2702     canmove = true;
2703 
2704     for(int i=0; i<players; i++) {
2705       if(pc[i]->dead && items[itOrbShield]) {
2706         pc[i]->dead = false;
2707         orbused[itOrbShield] = true;
2708         }
2709 
2710       if(pc[i]->dead && items[itOrbFlash]) {
2711         pc[i]->dead = false;
2712         pushmonsters();
2713         killMonster(pc[i]->base, moNone);
2714         activateFlash();
2715         popmonsters();
2716         }
2717 
2718       if(pc[i]->dead && items[itOrbLightning]) {
2719         pc[i]->dead = false;
2720         pushmonsters();
2721         killMonster(pc[i]->base, moLightningBolt);
2722         activateLightning();
2723         popmonsters();
2724         }
2725 
2726       if(pc[i]->dead && items[itOrbShell]) {
2727         pc[i]->dead = false;
2728         useupOrb(itOrbShell, 10);
2729         items[itOrbShield] = 1;
2730         orbused[itOrbShield] = true;
2731         }
2732 
2733       if(pc[i]->dead && items[itOrbLife]) {
2734         multi::deaths[i]++;
2735         items[itOrbLife]--;
2736         items[itOrbShield] += 3;
2737         items[itOrbAether] += 3;
2738         pc[i]->dead = false;
2739         orbused[itOrbShield] = true;
2740         }
2741 
2742       if(pc[i]->dead && !lastdead) {
2743         multi::deaths[i]++;
2744         achievement_final(true);
2745         }
2746       lastdead = pc[i]->dead;
2747 
2748       if(lastcanmove && pc[i]->dead) showMissionScreen();
2749 
2750       canmove = canmove && !pc[i]->dead;
2751       }
2752     }
2753 
2754   for(monster *m: additional)
2755     active.push_back(m);
2756   additional.clear();
2757 
2758   if(delayed_safety) {
2759     activateSafety(delayed_safety_land);
2760     delayed_safety = false;
2761     }
2762 
2763   // deactivate all monsters
2764   for(monster *m: active)
2765     if(m->dead && m->type != moPlayer) {
2766       if(m == mousetarget) mousetarget = NULL;
2767       if(m == lmousetarget) lmousetarget = NULL;
2768       m->remove_reference();
2769       }
2770     else {
2771       m->store();
2772       }
2773 
2774   active.clear();
2775   }
2776 
recall()2777 EX void recall() {
2778   for(int i=0; i<players; i++) {
2779     pc[i]->base = cwt.at;
2780     if(players == 1)
2781       pc[i]->at = Id;
2782     else
2783       pc[i]->at = spin(2*M_PI*i/players) * xpush(firstland == laMotion ? .5 : .3) * Id;
2784     /* ggmatrix(cwt.at);
2785     display(gmatrix[cwt.at]);
2786     pc[i]->findpat(); */
2787     }
2788   destroyMimics();
2789   }
2790 
init()2791 EX void init() {
2792 
2793   for(int i=0; i<players; i++) pc[i] = NULL;
2794 
2795   for(int i=0; i<players; i++) {
2796     pc[i] = new monster;
2797     pc[i]->type = moPlayer;
2798     pc[i]->pid = i;
2799     if(players == 1)
2800       pc[i]->at = Id;
2801     else
2802       pc[i]->at = spin(2*M_PI*i/players) * xpush(firstland == laMotion ? .5 : .3) * Id;
2803     pc[i]->pat = shiftless(pc[i]->at);
2804     pc[i]->base = cwt.at;
2805     pc[i]->vel = 0;
2806     pc[i]->inBoat = (firstland == laCaribbean || firstland == laOcean || firstland == laLivefjord ||
2807       firstland == laWhirlpool);
2808     pc[i]->store();
2809     swordmatrix[i] = Id;
2810     }
2811 
2812   if(!safety) {
2813     items[itOrbLife] = 3;
2814     if(!racing::on)
2815       addMessage(XLAT("Welcome to the Shoot'em Up mode!"));
2816     // addMessage(XLAT("F/;/Space/Enter/KP5 = fire, WASD/IJKL/Numpad = move"));
2817     }
2818   delayed_safety = false;
2819   }
2820 
boatAt(cell * c)2821 EX bool boatAt(cell *c) {
2822   pair<mit, mit> p =
2823     monstersAt.equal_range(c);
2824   for(mit it = p.first; it != p.second; it++) {
2825     monster* m = it->second;
2826     if(m->inBoat) return true;
2827     }
2828   return false;
2829   }
2830 
2831 EX hookset<bool(const shiftmatrix&, cell*, shmup::monster*)> hooks_draw;
2832 
clearMonsters()2833 EX void clearMonsters() {
2834   for(mit it = monstersAt.begin(); it != monstersAt.end(); it++)
2835     delete(it->second);
2836   for(monster *m: active) m->remove_reference();
2837   mousetarget = NULL;
2838   lmousetarget = NULL;
2839   monstersAt.clear();
2840   active.clear();
2841   }
2842 
clearMemory()2843 EX void clearMemory() {
2844   clearMonsters();
2845   while(!traplist.empty()) traplist.pop();
2846   curtime = 0;
2847   nextmove = 0;
2848   nextdragon = 0;
2849   visibleAt = 0;
2850   for(int i=0; i<MAXPLAYER; i++) pc[i] = NULL;
2851   }
2852 
gamedata(hr::gamedata * gd)2853 void gamedata(hr::gamedata* gd) {
2854   if(shmup::on) {
2855     gd->store(pc[0]); // assuming 1 player!
2856     gd->store(nextmove);
2857     gd->store(curtime);
2858     gd->store(nextdragon);
2859     gd->store(visibleAt);
2860     gd->store(traplist);
2861     gd->store(monstersAt);
2862     gd->store(active);
2863     gd->store(mousetarget);
2864     gd->store(lmousetarget);
2865     gd->store(nonvirtual);
2866     gd->store(additional);
2867     if(WDIM == 3) gd->store(swordmatrix[0]); // assuming 1 player!
2868     gd->store(traplist);
2869     gd->store(firetraplist);
2870     }
2871   }
2872 
playerpos(int i)2873 EX cell *playerpos(int i) {
2874   if(!pc[i]) return NULL;
2875   return pc[i]->base;
2876   }
2877 
playerInBoat(int i)2878 EX bool playerInBoat(int i) {
2879   if(!pc[i]) return false;
2880   return pc[i]->inBoat;
2881   }
2882 
destroyBoats(cell * c)2883 EX void destroyBoats(cell *c) {
2884   for(monster *m: active)
2885     if(m->base == c && m->inBoat)
2886       m->inBoat = false;
2887   }
2888 
virtualRebase(shmup::monster * m)2889 EX void virtualRebase(shmup::monster *m) {
2890   virtualRebase(m->base, m->at);
2891   }
2892 
2893 EX hookset<bool(shmup::monster*, string&)> hooks_describe;
2894 
addShmupHelp(string & out)2895 EX void addShmupHelp(string& out) {
2896   if(shmup::mousetarget && sqdist(mouseh, tC0(shmup::mousetarget->pat)) < .1) {
2897     if(callhandlers(false, hooks_describe, shmup::mousetarget, out)) return;
2898     out += ", ";
2899     out += XLAT1(minf[shmup::mousetarget->type].name);
2900     help = generateHelpForMonster(shmup::mousetarget->type);
2901     }
2902   }
2903 
2904 auto hooks = addHook(hooks_clearmemory, 0, shmup::clearMemory) +
2905   addHook(hooks_gamedata, 0, shmup::gamedata) +
__anond58e49aa0402() 2906   addHook(hooks_removecells, 0, [] () {
2907     for(mit it = monstersAt.begin(); it != monstersAt.end();) {
2908       if(is_cell_removed(it->first)) {
2909         monstersAt.insert(make_pair(nullptr, it->second));
2910         auto it0 = it; it++;
2911         monstersAt.erase(it0);
2912         }
2913       else it++;
2914       }
2915     });
2916 
switch_shmup()2917 EX void switch_shmup() {
2918   stop_game();
2919   switch_game_mode(rg::shmup);
2920   resetScores();
2921   start_game();
2922   configure();
2923   }
2924 
2925 #if MAXMDIM >= 4
__anond58e49aa0502null2926 auto hooksw = addHook(hooks_swapdim, 100, [] {
2927   for(auto& p: monstersAt) swapmatrix(p.second->at);
2928   });
2929 #endif
2930 
2931 }
2932 
draw_shmup_monster()2933 bool celldrawer::draw_shmup_monster() {
2934   using namespace shmup;
2935   #if CAP_SHAPES
2936 
2937   pair<mit, mit> p =
2938     monstersAt.equal_range(c);
2939 
2940   if(p.first == p.second) return false;
2941   ld zlev = -geom3::factor_to_lev(zlevel(tC0(Vd.T)));
2942 
2943   vector<monster*> monsters;
2944 
2945   for(mit it = p.first; it != p.second; it++) {
2946     monster* m = it->second;
2947     if(c != m->base) continue; // may happen in RogueViz Collatz
2948     m->pat = ggmatrix(m->base) * m->at;
2949     shiftmatrix view = V * m->at;
2950 
2951     bool half_elliptic = elliptic && GDIM == 3 && WDIM == 2;
2952     bool mirrored = det(view.T) > 0;
2953     if(half_elliptic && mirrored) continue;
2954 
2955     if(!mouseout()) {
2956       if(m->no_targetting) ; else
2957       if(mapeditor::drawplayer || m->type != moPlayer)
2958       if(!mousetarget || sqdist(mouseh, mousetarget->pat*C0) > sqdist(mouseh, m->pat*C0))
2959         mousetarget = m;
2960       }
2961 
2962     if(m->inBoat) {
2963       view = m->pat;
2964       if(WDIM == 2) Vboat = view;
2965       if(WDIM == 3) Vboat = view * spin(-M_PI/2);
2966 
2967       bool magic = m->type == moPlayer && items[itOrbWater];
2968       color_t outcolor = magic ? watercolor(0) : 0xC06000FF;
2969       color_t incolor = magic ? 0x0060C0FF : 0x804000FF;
2970 
2971       if(WDIM == 2) {
2972         queuepoly(Vboat, cgi.shBoatOuter, outcolor);
2973         queuepoly(Vboat, cgi.shBoatInner, incolor);
2974         }
2975       if(WDIM == 3) {
2976         queuepoly(mscale(Vboat, cgi.scalefactor/2), cgi.shBoatOuter, outcolor);
2977         queuepoly(mscale(Vboat, cgi.scalefactor/2-0.01), cgi.shBoatInner, incolor);
2978         }
2979       }
2980 
2981     if(doHighlight())
2982       poly_outline =
2983         isBullet(m) ? 0x00FFFFFF :
2984         (isFriendly(m->type) || m->type == moPlayer) ? 0x00FF00FF : 0xFF0000FF;
2985 
2986     int q = isize(ptds);
2987     if(q != isize(ptds) && !m->inBoat) pushdown(c, q, view, zlev, true, false);
2988 
2989     if(callhandlers(false, hooks_draw, V, c, m)) continue;
2990 
2991     switch(m->type) {
2992       case moPlayer: {
2993         playerfound = true;
2994 
2995         dynamicval<int> d(cpid, m->pid);
2996 
2997         bool h = hide_player();
2998         bool ths = subscreens::is_current_player(m->pid);
2999 
3000         if(!ths || !h) {
3001           drawPlayerEffects(view, c, true);
3002           if(WDIM == 3) {
3003             if(prod) {
3004               hyperpoint h = m->ori * C0; // ztangent(1)
3005               view = view * spin(-atan2(h[1], h[0]));
3006               }
3007             else {
3008               view = view * spin(-M_PI/2) * cspin(0, 2, -M_PI/2);
3009               }
3010             }
3011           if(m->inBoat) m->footphase = 0;
3012           if(mapeditor::drawplayer) drawMonsterType(moPlayer, c, view, 0xFFFFFFC0, m->footphase, 0xFFFFFFC0);
3013           }
3014 
3015         if(ths && h) first_cell_to_draw = false;
3016 
3017         if(ths && h && WDIM == 3) {
3018           if(items[itOrbSword])
3019             queuestr(swordpos(m->pid, false, 1), vid.fsize * 2, "+", iinf[itOrbSword].color);
3020           if(items[itOrbSword2])
3021             queuestr(swordpos(m->pid, true, 1), vid.fsize * 2, "+", iinf[itOrbSword2].color);
3022           }
3023 
3024         if(ths && keyresult[cpid]) {
3025           shiftpoint h = keytarget(cpid);
3026           if(WDIM == 2)
3027             queuestr(h, vid.fsize, "+", iinf[keyresult[cpid]].color);
3028           else {
3029             dynamicval<color_t> p(poly_outline, darkena(iinf[keyresult[cpid]].color, 0, 255));
3030             queuepoly(rgpushxto0(h) * cspin(0, 1, ticks / 140.), cgi.shGem[1], 0);
3031             }
3032           }
3033 
3034         break;
3035         }
3036       case moBullet: {
3037         color_t col;
3038         cpid = m->pid;
3039         if(m->get_parenttype() == moPlayer)
3040           col = getcs().swordcolor;
3041         else if(m->get_parenttype() == moMimic)
3042           col = (mirrorcolor(det(view.T) < 0) << 8) | 0xFF;
3043         else
3044           col = (minf[m->get_parenttype()].color << 8) | 0xFF;
3045         if(getcs().charid >= 4) {
3046           queuepoly(mmscale(view, 1.15), cgi.shPHead, col);
3047           ShadowV(view, cgi.shPHead);
3048           }
3049         else if(peace::on) {
3050           queuepolyat(mmscale(view, 1.15), cgi.shDisk, col, PPR::MISSILE);
3051           ShadowV(view, cgi.shPHead);
3052           }
3053         else {
3054           shiftmatrix t = view * spin(curtime / 50.0);
3055           queuepoly(WDIM == 3 ? t : GDIM == 3 ? mscale(t, cgi.BODY) : mmscale(t, 1.15), cgi.shKnife, col);
3056           ShadowV(t, cgi.shKnife);
3057           }
3058         break;
3059         }
3060       case moArrowTrap: {
3061         queuepoly(mmscale(view, 1.15), cgi.shTrapArrow, 0xFFFFFFFF);
3062         ShadowV(view, cgi.shTrapArrow);
3063         break;
3064         }
3065       case moTongue: {
3066         queuepoly(mmscale(view, 1.15), cgi.shTongue, (minf[m->get_parenttype()].color << 8) | 0xFF);
3067         ShadowV(view, cgi.shTongue);
3068         break;
3069         }
3070       case moFireball:  case moAirball: { // case moLightningBolt:
3071         queuepoly(mmscale(view, 1.15), cgi.shPHead, (minf[m->type].color << 8) | 0xFF);
3072         ShadowV(view, cgi.shPHead);
3073         break;
3074         }
3075       case moFlailBullet: case moCrushball: {
3076         shiftmatrix t = view * spin(curtime / 50.0);
3077         queuepoly(mmscale(t, 1.15), cgi.shFlailMissile, (minf[m->type].color << 8) | 0xFF);
3078         ShadowV(view, cgi.shFlailMissile);
3079         break;
3080         }
3081       case moAsteroid: {
3082         if(GDIM == 3) addradar(view, '*', 0xFFFFFF, 0xC0C0C0FF);
3083         shiftmatrix t = view;
3084         if(WDIM == 3) t = face_the_player(t);
3085         t = t * spin(curtime / 500.0);
3086         ShadowV(t, cgi.shAsteroid[m->hitpoints & 7]);
3087         if(WDIM == 2) t = mmscale(t, 1.15);
3088         color_t col = WDIM == 3 ? 0xFFFFFF : minf[m->type].color;
3089         col <<= 8;
3090         queuepoly(t, cgi.shAsteroid[m->hitpoints & 7], col | 0xFF);
3091         break;
3092         }
3093 
3094       default:
3095         if(WDIM == 3) {
3096           if(prod) {
3097             hyperpoint h = m->ori * xtangent(1);
3098             view = view * spin(-atan2(h[1], h[0]));
3099             }
3100           }
3101         if(m->inBoat) m->footphase = 0;
3102         color_t col = minf[m->type].color;
3103         if(m->type == moMimic)
3104           col = mirrorcolor(det(view.T) < 0);
3105         if(m->type == moSlime) {
3106           col = winf[c->wall].color;
3107           col |= (col >> 1);
3108           }
3109         cpid = protect_pid(m->pid);
3110         if(m->stunoff > curtime)
3111           c->stuntime = 1 + (m->stunoff - curtime-1)/300;
3112         if(hasHitpoints(m->type))
3113           c->hitpoints = m->hitpoints;
3114         if(m->type == moTortoise) tortoise::emap[c] = getBits(m->torigin);
3115         /* if(m->type == moMimic && GDIM == 3)
3116           drawMonsterType(m->type, c, view * spin(-M_PI/2), col, m->footphase); */
3117         /* else if(GDIM == 3)
3118           drawMonsterType(m->type, c, view * cspin(0, 2, M_PI/2), col, m->footphase); */
3119         /* else */
3120           drawMonsterType(m->type, c, view, col, m->footphase, col);
3121         if(m->type == moTortoise) tortoise::emap.erase(c);
3122         break;
3123       }
3124     }
3125 
3126   #endif
3127   return false;
3128   }
3129 
3130 }
3131