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