1 // Hyperbolic Rogue -- orbs
2 // Copyright (C) 2011-2019 Zeno Rogue, see 'hyper.cpp' for details
3 
4 /** \file orbs.cpp
5  *  \brief Implementation of various Orb effects, and their properties such as default and maximum charges
6  */
7 
8 #include "hyper.h"
9 namespace hr {
10 
11 EX bool orbused[ittypes], lastorbused[ittypes];
12 
markOrb(eItem it)13 EX bool markOrb(eItem it) {
14   if(!items[it]) return false;
15   orbused[it] = true;
16   return true;
17   }
18 
markEmpathy(eItem it)19 EX bool markEmpathy(eItem it) {
20   if(!items[itOrbEmpathy]) return false;
21   if(!markOrb(it)) return false;
22   markOrb(itOrbEmpathy);
23   return true;
24   }
25 
markEmpathy2(eItem it)26 EX bool markEmpathy2(eItem it) {
27   if(items[itOrbEmpathy] < 2) return false;
28   if(!markOrb2(it)) return false;
29   markOrb2(itOrbEmpathy);
30   return true;
31   }
32 
markOrb2(eItem it)33 EX bool markOrb2(eItem it) {
34   return markOrb(it);
35   /* if(!items[it]) return false;
36   orbused[it] = true;
37   return items[it] > 1; */
38   }
39 
fixpower(int qty)40 EX int fixpower(int qty) {
41   if(markOrb(itOrbEnergy)) qty = (qty+1)/2;
42   return qty;
43   }
44 
useupOrb(eItem it,int qty)45 EX void useupOrb(eItem it, int qty) {
46   items[it] -= fixpower(qty);
47   if(items[it] < 0) items[it] = 0;
48   }
49 
50 EX void drainOrb(eItem it, int target IS(0)) {
51   if(items[it] > target) useupOrb(it, items[it] - target);
52   }
53 
empathyMove(const movei & mi)54 EX void empathyMove(const movei& mi) {
55   if(!items[itOrbEmpathy]) return;
56 
57   if(items[itOrbFire]) {
58     invismove = false;
59     if(makeflame(mi.s, 10, false)) markEmpathy(itOrbFire);
60     }
61 
62   if(items[itCurseWater]) {
63     invismove = false;
64     if(makeshallow(mi.s, 10, false)) markEmpathy(itCurseWater);
65     }
66 
67   if(items[itOrbDigging]) {
68    if(mi.proper() && earthMove(mi))
69      markEmpathy(itOrbDigging), invismove = false;
70    }
71 
72   if(items[itOrbWinter] && isIcyLand(mi.s) && mi.s->wall == waNone) {
73     invismove = false;
74     mi.s->wall = waIcewall;
75     markEmpathy(itOrbWinter);
76     }
77   }
78 
intensify(int val)79 EX int intensify(int val) {
80   return inv::on ? 2 * val : val * 6 / 5;
81   }
82 
reduceOrbPower(eItem it,int cap)83 EX bool reduceOrbPower(eItem it, int cap) {
84   if(items[it] && markOrb(itCurseDraining)) {
85     items[it] -= (markOrb(itOrbEnergy) ? 1 : 2) * multi::activePlayers();
86     if(items[it] < 0) items[it] = 0;
87     if(items[it] == 0 && it == itOrbLove)
88       princess::bringBack();
89     }
90   if(items[it] && (lastorbused[it] || (it == itOrbShield && items[it]>3) || !markOrb(itOrbTime))) {
91     items[it] -= multi::activePlayers();
92     if(isHaunted(cwt.at->land))
93       fail_survivalist();
94     if(items[it] < 0) items[it] = 0;
95     if(items[it] > cap && markOrb(itOrbIntensity)) cap = intensify(cap);
96     if(items[it] > cap && timerghost) items[it] = cap;
97     if(items[it] == 0 && it == itOrbLove)
98       princess::bringBack();
99     return true;
100     }
101   if(items[it] > cap && timerghost) items[it] = cap;
102   return false;
103   }
104 
reduceOrbPowerAlways(eItem it)105 EX void reduceOrbPowerAlways(eItem it) {
106   if(items[it]) {
107     items[it] -= multi::activePlayers();
108     if(items[it] < 0) items[it] = 0;
109     }
110   }
111 
reverse_curse(eItem curse,eItem orb,bool cancel)112 EX void reverse_curse(eItem curse, eItem orb, bool cancel) {
113   if(items[curse] && markOrb(itOrbPurity)) {
114     items[orb] += items[curse];
115     if(curse == itCurseWater) items[itOrbWinter] += items[curse];
116     items[curse] = 0;
117     }
118   if(cancel && items[curse] && items[orb]) {
119     int m = min(items[curse], items[orb]);
120     items[curse] -= m;
121     items[orb] -= m;
122     }
123   }
124 
reduceOrbPowers()125 EX void reduceOrbPowers() {
126 
127   reverse_curse(itCurseWeakness, itOrbSlaying, true);
128   reverse_curse(itCurseFatigue, itOrbSpeed, false); // OK
129   reverse_curse(itCurseRepulsion, itOrbMagnetism, true); // OK
130   reverse_curse(itCurseWater, itOrbFire, true); // OK
131   reverse_curse(itCurseDraining, itOrbTime, false); // OK
132   reverse_curse(itCurseGluttony, itOrbChoice, true); // OK
133 
134   if(haveMount()) markOrb(itOrbDomination);
135   for(int i=0; i<ittypes; i++)
136     lastorbused[i] = orbused[i], orbused[i] = false;
137   if(items[itOrbShield]) orbused[itOrbShield] = lastorbused[itOrbShield];
138   reduceOrbPower(itOrbTime, cwt.at->land == laCaribbean ? 777 : 150);
139   if(invismove && !invisfish) markOrb(itOrbInvis);
140   reduceOrbPower(itOrbLightning, 777);
141   reduceOrbPower(itOrbSpeed, 67);
142   reduceOrbPower(itOrbShield, 77);
143   reduceOrbPower(itOrbShell, 150);
144   reduceOrbPower(itOrbFlash, 777);
145   reduceOrbPower(itOrbWinter, 77);
146   reduceOrbPower(itOrbFire, 77);
147   reduceOrbPower(itOrbIllusion, 111);
148   reduceOrbPower(itOrbDragon, 111);
149   reduceOrbPower(itOrbPsi, 111);
150   reduceOrbPower(itOrbInvis, 77);
151   reduceOrbPower(itOrbAether, 77);
152   reduceOrbPower(itOrbWoods, 100);
153   reduceOrbPower(itOrbDigging, 100);
154   reduceOrbPower(itOrbTeleport, 200);
155   reduceOrbPower(itOrbSpace, 150);
156   reduceOrbPower(itOrbMagnetism, 150);
157   reduceOrbPowerAlways(itOrbSafety);
158   reduceOrbPower(itOrbThorns, 150);
159   reduceOrbPower(itOrbWater, 150);
160   reduceOrbPower(itOrbAir, 150);
161   reduceOrbPower(itOrbFrog, 77);
162   reduceOrbPower(itOrbDash, 77);
163   reduceOrbPower(itOrbPhasing, 77);
164   reduceOrbPower(itOrbDiscord, 67);
165   reduceOrbPower(itOrbSummon, 333);
166   reduceOrbPower(itOrbMatter, 333);
167   reduceOrbPower(itOrbFish, 57 + 20 * multi::activePlayers());
168   if(!items[itSavedPrincess]) items[itOrbLove] = 0;
169   reduceOrbPower(itOrbLove, 777);
170   reduceOrbPower(itOrbStunning, 100);
171   reduceOrbPower(itOrbLuck, 333);
172   reduceOrbPower(itOrbUndeath, 77);
173   reduceOrbPower(itOrbFreedom, 77);
174   reduceOrbPower(itOrbEmpathy, 77);
175   markOrb(itOrb37); reduceOrbPower(itOrb37, 333);
176   reduceOrbPower(itOrbBeauty, 77);
177   reduceOrbPower(itOrbEnergy, 77);
178   reduceOrbPower(itOrbDomination, 120);
179   reduceOrbPower(itOrbSword, 100 + 20 * multi::activePlayers());
180   reduceOrbPower(itOrbSword2, 100 + 20 * multi::activePlayers());
181   reduceOrbPower(itOrbStone, 120);
182   reduceOrbPower(itOrbNature, 120);
183   reduceOrbPower(itOrbRecall, 77);
184   reduceOrbPower(itOrbBull, 120);
185   reduceOrbPower(itOrbHorns, 77);
186   reduceOrbPower(itOrbLava, 80);
187   reduceOrbPower(itOrbMorph, 80);
188   reduceOrbPower(itOrbSlaying, 120);
189   reduceOrbPower(itOrbGravity, 120);
190   reduceOrbPower(itOrbChoice, 120);
191   reduceOrbPower(itOrbIntensity, 120);
192   reduceOrbPower(itOrbImpact, 120);
193   reduceOrbPower(itOrbChaos, 120);
194   reduceOrbPower(itOrbPlague, 120);
195 
196   reduceOrbPower(itOrbSide1, 120);
197   reduceOrbPower(itOrbSide2, 120);
198   reduceOrbPower(itOrbSide3, 120);
199   if(cwt.at->land != laWildWest)
200     reduceOrbPower(itRevolver, 6);
201 
202   reduceOrbPower(itOrbPurity, 30);
203   reduceOrbPower(itCurseWeakness, 199);
204   reduceOrbPower(itCurseDraining, 199);
205   reduceOrbPower(itCurseWater, 199);
206   reduceOrbPower(itCurseFatigue, 199);
207   reduceOrbPower(itCurseRepulsion, 199);
208   reduceOrbPower(itCurseGluttony, 199);
209 
210   #if CAP_COMPLEX2
211   mine::auto_teleport_charges();
212   #endif
213 
214   whirlwind::calcdirs(cwt.at);
215   items[itStrongWind] = !items[itOrbAether] && whirlwind::qdirs == 1;
216   items[itWarning] = 0;
217   }
218 
flashAlchemist(cell * c)219 EX void flashAlchemist(cell *c) {
220   if(isAlch(c)) {
221     if(isAlch(cwt.at))
222       c->wall = cwt.at->wall;
223     else
224       c->wall = eWall(c->wall ^ waFloorB ^ waFloorA);
225     }
226   }
227 
flashCell(cell * c,eMonster killer,flagtype flags)228 EX void flashCell(cell *c, eMonster killer, flagtype flags) {
229   changes.ccell(c);
230   eWall ow = c->wall;
231   flashAlchemist(c);
232   if((flags & AF_MSG) && c->monst && !isWorm(c) && c->monst != moShadow)
233     addMessage(XLAT("%The1 is destroyed by the Flash.", c->monst));
234   if(c->monst || isPlayerOn(c))
235     if(canAttack(nullptr, killer, c, c->monst, flags))
236       attackMonster(c, flags, killer);
237   if(isIcyLand(c))
238     HEAT(c) += 2;
239   if(c->land == laDryForest)
240     c->landparam += 2;
241   if(c->wall == waCavewall)  c->wall = waCavefloor;
242   if(c->wall == waDeadTroll) c->wall = waCavefloor;
243   if(c->wall == waDeadTroll2) c->wall = waNone;
244   if(c->wall == waPetrified) c->wall = waNone;
245   if(c->wall == waDeadfloor2)  c->wall = waDeadfloor;
246   if(c->wall == waGargoyleFloor)  c->wall = waChasm;
247   if(c->wall == waGargoyleBridge)  placeWater(c, c);
248   if(c->wall == waGargoyle)  c->wall = waNone;
249   if(c->wall == waTerraWarrior)  c->wall = waNone, kills[moTerraWarrior]++;
250   if(c->wall == waPlatform)  c->wall = waNone;
251   if(c->wall == waStone)     c->wall = waNone, destroyTrapsAround(c);
252   if(c->wall == waRubble)    c->wall = waNone;
253   if(c->wall == waDeadwall)  c->wall = waDeadfloor2;
254   if(c->wall == waGiantRug)  c->wall = waNone;
255   if(c->wall == waMirror)    c->wall = waNone;
256   if(c->wall == waCloud)     c->wall = waNone;
257   if(c->wall == waExplosiveBarrel) explodeBarrel(c);
258   if(c->wall == waRuinWall)  c->wall = waNone;
259   if(c->wall == waDune)      c->wall = waNone;
260   if(c->wall == waSaloon)    c->wall = waNone;
261   if(c->wall == waSandstone) c->wall = waNone;
262   if(c->wall == waAncientGrave) c->wall = waNone;
263   if(c->wall == waFreshGrave) c->wall = waNone;
264   if(c->wall == waColumn)    c->wall = waNone;
265   if(c->wall == waGlass)     c->wall = waNone;
266   if(c->wall == waBigTree || c->wall == waSmallTree)    c->wall = waNone;
267   if(c->wall == waBigStatue) c->wall = waNone;
268   if(c->wall == waCTree)     c->wall = waCIsland2;
269   if(c->wall == waPalace)    c->wall = waRubble;
270   if(c->wall == waRose)      c->wall = waNone;
271   if(c->wall == waOpenGate || c->wall == waClosedGate) {
272     eWall w = c->wall;
273     c->wall = waNone;
274     for(int i=0; i<c->type; i++) if(c->move(i) && c->move(i)->wall == w)
275       flashCell(c->move(i), killer, flags);
276     }
277   if(c->wall == waRed1)      c->wall = waNone;
278   else if(c->wall == waRed2)      c->wall = waRed1;
279   else if(c->wall == waRed3)      c->wall = waRed2;
280 
281   if(c->wall == waBarrowWall) c->wall = waBarrowDig;
282   else if(c->wall == waBarrowDig) c->wall = waNone;
283 
284   if(c->wall != ow && ow) drawParticles(c, winf[ow].color, 16);
285 
286   if(hasTimeout(c) && c->wparam < 77) c->wparam = 77;
287   if(isActivable(c))
288     activateActiv(c, false);
289   }
290 
activateFlashFrom(cell * cf,eMonster who,flagtype flags)291 EX void activateFlashFrom(cell *cf, eMonster who, flagtype flags) {
292   drawFlash(cf);
293   playSound(cf, "storm");
294   for(int i=0; i<isize(dcal); i++) {
295     cell *c = dcal[i];
296     if(c == cf) continue;
297     for(int t=0; t<c->type; t++)
298     for(int u=0; u<cf->type; u++)
299       if(c->move(t) == cf->move(u) && c->move(t) != NULL) {
300         flashCell(c, who, flags);
301         goto nexti;
302         }
303     nexti: ;
304     }
305   }
306 
distanceBound(cell * c1,cell * c2,int d)307 EX bool distanceBound(cell *c1, cell *c2, int d) {
308   if(!c1 || !c2) return false;
309   if(d == 0) return c1 == c2;
310   for(int i=0; i<c2->type; i++)
311     if(distanceBound(c1, c2->move(i), d-1)) return true;
312   return false;
313   }
314 
checkFreedom(cell * cf)315 EX void checkFreedom(cell *cf) {
316   manual_celllister cl;
317   cl.add(cf);
318   for(int i=0; i<isize(cl.lst); i++) {
319     cell *c = cl.lst[i];
320     if(c->cpdist >= 5) return;
321     for(int i=0; i<c->type; i++) {
322       cell *c2 = c->move(i);
323       // todo leader
324       if(cl.listed(c2)) continue;
325       if(!passable(c2, c, P_ISPLAYER | P_MIRROR | P_LEADER)) continue;
326       if(againstRose(c, c2) && !scentResistant()) continue;
327       if(c2->wall == waArrowTrap && c2->wparam == 2) continue;
328       bool monsterhere = false;
329       for(int j=0; j<c2->type; j++) {
330         cell *c3 = c2->move(j);
331         if(c3 && c3->monst && !isFriendly(c3))
332           monsterhere = true;
333         }
334       if(!monsterhere) cl.add(c2);
335       }
336     }
337   addMessage(XLAT("Your %1 activates!", itOrbFreedom));
338   drainOrb(itOrbFreedom);
339   for(cell *pc: player_positions())
340     drawBigFlash(pc);
341   for(int i=0; i<isize(dcal); i++) {
342     cell *c = dcal[i];
343     if(c == cf && !shmup::on) continue;
344     if(c->cpdist > 5) break;
345     flashCell(c, moPlayer, AF_MAGIC);
346     }
347   }
348 
activateFlash()349 EX void activateFlash() {
350   int tk = tkills();
351 
352   for(cell *pc: player_positions())
353     drawFlash(pc);
354 
355   addMessage(XLAT("You activate the Flash spell!"));
356   playSound(cwt.at, "storm");
357   drainOrb(itOrbFlash);
358   for(int i=0; i<isize(dcal); i++) {
359     cell *c = dcal[i];
360     if(c->cpdist > 2) break;
361     flashCell(c, moPlayer, AF_MAGIC);
362     }
363   achievement_count("FLASH", tkills(), tk);
364   }
365 
reflectingBarrierAt(cell * c)366 EX bool reflectingBarrierAt(cell *c) {
367   return
368     c->wall == waBarrier || c->wall == waCamelot ||
369     c->wall == waPalace || c->wall == waPlatform ||
370     c->wall == waTempWall || c->wall == waWarpGate || c->wall == waBarrowDig || c->wall == waBarrowWall;
371   }
372 
reflectingBarrierAt(cellwalker & c,int d)373 EX bool reflectingBarrierAt(cellwalker& c, int d) {
374   if(d >= 3) return true;
375   if(d <= -3) return true;
376   d = gmod(c.spin + d, c.at->type);
377   if(!c.at->move(d)) return true;
378 
379   return reflectingBarrierAt(c.at->move(d));
380   // WAS:
381   // if(c.at->move(d)->wall == waBarrier) return true;
382   // THEN:
383   // if(c.at->move(d)->land == laBarrier || c.at->move(d)->land == laOceanWall ||
384   //   c.at->move(d)->land == laHauntedWall ||
385   //   c.at->move(d)->land == laElementalWall) ;
386   // return false;
387   }
388 
killAdjacentSharks(cell * c)389 EX void killAdjacentSharks(cell *c) {
390   for(int i=0; i<c->type; i++) {
391     cell *c2 = c->move(i);
392     if(!c2) continue;
393     if(isShark(c2->monst)) {
394       changes.ccell(c2);
395       c2->ligon = true;
396       killMonster(c2, moLightningBolt);
397       killAdjacentSharks(c2);
398       }
399     if(isKraken(c2->monst) && isWatery(c2)) {
400       changes.ccell(c2);
401       cell *c3 = kraken::head(c2);
402       changes.ccell(c3);
403       c3->ligon = true;
404       forCellEx(c4, c3) changes.ccell(c4), killMonster(c4, moLightningBolt); // kill-all
405       forCellEx(c4, c3) if(isWatery(c4)) {
406         c4->ligon = true;
407         killAdjacentSharks(c4);
408         }
409       }
410     }
411   }
412 
castLightningBolt(cellwalker lig)413 EX void castLightningBolt(cellwalker lig) {
414   int bnc = 0;
415   int counter = 1000;
416   while(true) {
417     counter--; if(counter < 0) break;
418     // printf("at: %p i=%d d=%d\n", lig.c, i, lig.spin);
419 
420     killAdjacentSharks(lig.at);
421 
422     if(lig.peek() == NULL) break;
423 
424     lig += wstep;
425     if(inmirror(lig)) lig = mirror::reflect(lig);
426 
427     cell *c = lig.at;
428     changes.ccell(c);
429 
430     eWall ow = c->wall;
431 
432     flashAlchemist(c);
433     if(c->monst == moMetalBeast2 && !c->item) c->item = itFulgurite;
434     if(c->monst)
435       if(canAttack(nullptr, moPlayer, c, c->monst, AF_MAGIC))
436         attackMonster(c, AF_MAGIC, moLightningBolt);
437     if(isIcyLand(c)) HEAT(c) += 2;
438     if(c->land == laDryForest) c->landparam += 2;
439     bool first = !c->ligon;
440     c->ligon = 1;
441 
442     bool brk = false, spin = false;
443 
444     if(c->wall == waGargoyle)  brk = true;
445     if(c->wall == waExplosiveBarrel) explodeBarrel(c), brk = true;
446     if(c->wall == waCavewall)  c->wall = waCavefloor, brk = true;
447     if(c->wall == waDeadTroll) c->wall = waCavefloor, brk = true;
448     if(c->wall == waDeadTroll2)c->wall = waNone, brk = true;
449     if(c->wall == waPetrified) c->wall = waNone, brk = true;
450     if(c->wall == waDeadfloor2)c->wall = waDeadfloor;
451     if(c->wall == waRubble)    c->wall = waNone;
452     if(c->wall == waDeadwall)  c->wall = waDeadfloor2, brk = true;
453     if(c->wall == waGlass)     c->wall = waNone, spin = true;
454     if(c->wall == waDune)      c->wall = waNone, brk = true;
455     if(c->wall == waRuinWall)  c->wall = waNone;
456     if(c->wall == waIcewall)   c->wall = waNone, brk = true;
457     if(c->wall == waAncientGrave) c->wall = waNone, spin = true;
458     if(c->wall == waFreshGrave) c->wall = waNone, spin = true;
459 
460     if(c->wall == waFreshGrave) c->wall = waNone, spin = true;
461 
462     if(c->wall == waBigStatue) c->wall = waNone, spin = true;
463     if(c->wall == waColumn)    c->wall = waNone, spin = true;
464     if(c->wall == waStone)     c->wall = waNone, brk = true, destroyTrapsAround(c);
465     if(c->wall == waArrowTrap) activateArrowTrap(c);
466     if(c->wall == waTerraWarrior)  c->wall = waNone, kills[moTerraWarrior]++;
467 
468     if(c->wall == waCanopy || c->wall == waTrunk || c->wall == waBigBush || c->wall == waSmallBush) {
469       makeflame(c, 12, false); brk = true;
470       }
471 
472     if(c->wall == waGrounded)  brk = true;
473     if(c->wall == waFan)       spin = true;
474     if(c->wall == waMetal)     c->wall = waCharged, brk = true;
475     if(c->wall == waSandstone) {
476       c->wall = waNone, brk = true;
477       if(c->land == laStorms) c->item = itFulgurite;
478       }
479 
480     if(c->wall == waCharged && first) {
481       for(int i=0; i<c->type; i++)
482         // do not do strange things in horocyclic spires
483         if(c->move(i) && c->move(i)->wall != waCharged) {
484           cellwalker lig2(c, i);
485           castLightningBolt(lig2);
486           }
487       brk = true;
488       }
489 
490     if(c->wall == waBoat && c != cwt.at)    become_water(c), spin = true;
491     if(c->wall == waStrandedBoat && c !=cwt.at)    c->wall = waNone, spin = true;
492 
493     if((c->wall == waNone || c->wall == waSea) && c->land == laLivefjord)
494       c->wall = eWall(c->wall ^ waSea ^ waNone);
495 
496     if(c->wall == waRed1)      c->wall = waNone;
497     if(c->wall == waRed2)      c->wall = waRed1;
498     if(c->wall == waRed3)      c->wall = waRed2, brk = true;
499 
500     if(isActivable(c))         activateActiv(c, false);
501     if(c->wall == waBigTree || c->wall == waSmallTree || c->wall == waVinePlant ||
502       c->wall == waSaloon)    {
503       makeflame(c, 4, false);
504       brk = true;
505       }
506     if(c->wall == waDock)  makeflame(c, 5, false);
507     if(c->wall == waCTree) makeflame(c, 12, false);
508     if(c->wall == waRose)  makeflame(c, 60, false);
509     if(cellHalfvine(c) && c->wall == lig.peek()->wall) {
510       destroyHalfvine(c, waPartialFire, 4);
511       brk = true;
512       }
513 
514     if(c->wall != ow && ow)
515       drawParticles(c, winf[ow].color, 16);
516 
517     if(c == cwt.at)             {bnc++; if(bnc > 10) break; }
518     if(spin) lig += hrand(lig.at->type);
519 
520     if(brk) break;
521 
522     if(reflectingBarrierAt(c)) {
523       int left = -1;
524       int right = 1;
525       while(!reflectingBarrierAt(lig, left)) left--;
526       while(!reflectingBarrierAt(lig, right)) right++;
527       lig += right + left;
528       if(c->wall == waBarrowWall) c->wall = waBarrowDig;
529       else if(c->wall == waBarrowDig) c->wall = waNone;
530       bnc++; if(bnc > 10) break;
531       }
532     else
533       lig += rev;
534 
535     if(lig.at->wall == waCloud) {
536       lig.at->wall = waNone;
537       mirror::createMirages(lig, mirror::LIGHTNING);
538       }
539     if(lig.at->wall == waMirror) {
540       lig.at->wall = waNone;
541       mirror::createMirrors(lig, mirror::LIGHTNING);
542       }
543     }
544   }
545 
castLightningBoltFrom(cell * c)546 EX void castLightningBoltFrom(cell *c) {
547   for(int i=0; i<c->type; i++) castLightningBolt(cellwalker(c, i));
548   }
549 
activateLightning()550 EX void activateLightning() {
551   int tk = tkills();
552   drawLightning();
553   addMessage(XLAT("You activate the Lightning spell!"));
554 
555   for(int i=0; i<isize(dcal); i++) if(dcal[i]) dcal[i]->ligon = 0;
556 
557   drainOrb(itOrbLightning);
558 
559   for(cell *pc: player_positions())
560     castLightningBoltFrom(pc);
561 
562   elec::afterOrb = true;
563   elec::act();
564   elec::afterOrb = false;
565 
566   achievement_count("LIGHTNING", tkills(), tk);
567   playSound(cwt.at, "storm");
568   }
569 
570 // roCheck: return orb type if successful, 0 otherwise
571 // roMouse/roKeyboard:
572 //    return orb type if successful, eItem(-1) if do nothing, 0 otherwise
573 
haveRangedTarget()574 EX bool haveRangedTarget() {
575   if(!haveRangedOrb())
576     return false;
577   for(int i=0; i<isize(dcal); i++) {
578     cell *c = dcal[i];
579     if(targetRangedOrb(c, roCheck)) {
580       return true;
581       }
582     }
583   return false;
584   }
585 
checkmoveO()586 void checkmoveO() {
587   if(multi::players > 1 && multi::activePlayers() == 1)
588     multi::checklastmove();
589   if(multi::players == 1) checkmove();
590   }
591 
teleportAction()592 int teleportAction() {
593   // normal teleport
594   if(shmup::on || numplayers() == 1) return 1;
595   // multi-player, but all in -- do nothing
596   else if(numplayers() == multi::activePlayers()) return 0;
597   // otherwise teleport to the game
598   else return 2;
599   }
600 
teleportTo(cell * dest)601 EX void teleportTo(cell *dest) {
602   playSound(dest, "other-teleport");
603   if(dest->monst) {
604     cwt.at->monst = dest->monst;
605     cwt.at->stuntime = dest->stuntime;
606     dest->monst = moNone;
607     }
608 
609   if(teleportAction() == 2) {
610     bool b = multiRevival(dest, NULL);
611     if(b) {
612       killFriendlyIvy();
613       drainOrb(itOrbTeleport);
614       movecost(cwt.at, dest, 3);
615       playerMoveEffects(movei(cwt.at, dest, TELEPORT));
616       afterplayermoved();
617       bfs();
618       }
619     return;
620     }
621 
622   addMessage(XLAT("You teleport to a new location!"));
623 
624   killFriendlyIvy();
625   cell *from = cwt.at;
626   movecost(from, dest, 1);
627   playerMoveEffects(movei(cwt.at, dest, TELEPORT));
628   fix_whichcopy(dest);
629   cwt.at = dest; cwt.spin = hrand(dest->type); flipplayer = !!(hrand(2));
630   drainOrb(itOrbTeleport);
631 
632   mirror::destroyAll();
633 
634   afterplayermoved();
635   bfs();
636 
637   sword::reset();
638   items[itOrbSword2] = 0;
639   if(shmup::on)
640     shmup::teleported();
641   else
642     checkmoveO();
643 
644   movecost(from, dest, 2);
645   #if CAP_COMPLEX2
646   mine::auto_teleport_charges();
647   #endif
648   }
649 
650 EX bool jumpTo(orbAction a, cell *dest, eItem byWhat, int bonuskill IS(0), eMonster dashmon IS(moNone)) {
651   if(byWhat != itStrongWind) playSound(dest, "orb-frog");
652   cell *from = cwt.at;
653   changes.value_keep(cwt);
654   changes.ccell(dest);
655   changes.ccell(cwt.at);
656 
657   if(byWhat == itOrbFrog) {
658     useupOrb(itOrbFrog, 5);
659     addMessage(XLAT("You jump!"));
660     }
661 
662   if(byWhat == itOrbDash) {
663     useupOrb(itOrbDash, 5);
664     if(dashmon) addMessage(XLAT("You vault over %the1!", dashmon));
665     }
666 
667   if(byWhat == itOrbPhasing) {
668     useupOrb(itOrbPhasing, 5);
669     addMessage(XLAT("You jump!"));
670     }
671 
672   movecost(from, dest, 1);
673 
674   killFriendlyIvy();
675 
676   cell *c1 = cwt.at;
677   animateMovement(match(cwt.at, dest), LAYER_SMALL);
678   cwt.at = dest;
679   forCellIdEx(c2, i, dest) if(c2->cpdist < dest->cpdist) {
680     cwt.spin = i;
681     flipplayer = true;
682     }
683 
684   sword::reset();
685   auto mi = movei(c1, dest, JUMP);
686   stabbingAttack(mi, moPlayer, bonuskill);
687   playerMoveEffects(mi);
688 
689   if(itemclass(byWhat) == IC_ORB)
690     apply_impact(dest);
691 
692   // do not apply movecost later, when from no longer exists
693   if(cwt.at->item == itOrbSafety) {
694     movecost(from, dest, 2);
695     from = NULL;
696     }
697   if(cwt.at->item != itOrbYendor && cwt.at->item != itHolyGrail) {
698     auto c = collectItem(cwt.at, true);
699     if(c) {
700       return true;
701       }
702     }
703 
704   mirror::destroyAll();
705 
706   if(monstersnearO(a, dest)) {
707     changes.rollback();
708     return false;
709     }
710 
711   if(isCheck(a)) {
712     changes.rollback();
713     return true;
714     }
715 
716   changes.commit();
717 
718   fix_whichcopy(dest);
719   countLocalTreasure();
720 
721   for(int i=9; i>=0; i--)
722     setdist(cwt.at, i, NULL);
723 
724   if(from) movecost(from, dest, 2);
725 
726   createNoise(1);
727 
728   if(shmup::on)
729     shmup::teleported();
730   else
731     monstersTurn();
732 
733   return true;
734   }
735 
growIvyTo(const movei & mi)736 void growIvyTo(const movei& mi) {
737   auto& dest = mi.t;
738   if(dest->monst)
739     attackMonster(dest, AF_NORMAL | AF_MSG, moFriendlyIvy);
740   else {
741     dest->monst = moFriendlyIvy;
742     dest->mondir = mi.rev_dir_or(NODIR);
743     animateMovement(mi, LAYER_BIG);
744     moveEffect(mi, moFriendlyIvy);
745     empathyMove(mi);
746     }
747   createNoise(1);
748   monstersTurn();
749   }
750 
spacedrain(cell * c)751 pair<int, bool> spacedrain(cell *c) {
752   int d = c->cpdist;
753   bool usemagnet = items[itOrbMagnetism] && d > 0;
754   if(usemagnet) d--;
755   return {d * d, usemagnet};
756   }
757 
telekinesis(cell * dest)758 void telekinesis(cell *dest) {
759 
760   auto cost = spacedrain(dest);
761 
762   if(dest->land == laAlchemist && isAlchAny(dest) && isAlchAny(cwt.at))
763     dest->wall = cwt.at->wall;
764 
765   if(dest->land == laPower && cwt.at->land != laPower && dest->item != itOrbFire && dest->item != itOrbLife) {
766     if(itemclass(dest->item) != IC_ORB)
767       items[dest->item] ++;
768     else
769       items[dest->item] += 2;
770     addMessage(XLAT("The Orb loses its power as it leaves the Land of Power!"));
771     dest->item = itNone;
772     }
773 
774   if(dest->wall == waGlass) {
775     drainOrb(itOrbSpace);
776     addMessage(XLAT("Your power is drained by %the1!", dest->wall));
777     }
778 
779   moveItem(dest, cwt.at, true);
780   collectItem(cwt.at, true);
781   useupOrb(itOrbSpace, cost.first);
782   if(cost.second)
783     markOrb(itOrbMagnetism);
784 
785   createNoise(3);
786   checkSwitch();
787   bfs();
788   if(!shmup::on) checkmoveO();
789   }
790 
summonedAt(cell * dest)791 EX eMonster summonedAt(cell *dest) {
792   if(dest->monst) return moNone;
793   if(dest->wall == waVineHalfA || dest->wall == waVineHalfB || dest->wall == waVinePlant)
794     return moVineSpirit;
795   if(dest->wall == waCTree)
796     return moParrot;
797   if(dest->wall == waLake)
798     return moGreaterShark;
799   if(dest->wall == waAncientGrave || dest->wall == waFreshGrave)
800     return moGhost;
801   if(dest->wall == waClosePlate || dest->wall == waOpenPlate)
802     return dest->land == laPalace ? moPalace : moBat;
803   if(dest->wall == waFloorA || dest->wall == waFloorB)
804     return moSlime;
805   if(dest->wall == waCavefloor)
806     return moTroll;
807   if(dest->wall == waDeadfloor)
808     return moEarthElemental;
809   if(dest->wall == waDeadfloor2)
810     return moMiner;
811   if(dest->wall == waMineOpen || dest->wall == waMineMine || dest->wall == waMineUnknown)
812     return moBomberbird;
813   if(dest->wall == waRichDie)
814     return moAnimatedDie;
815   if(dest->wall == waHappyDie)
816     return moAngryDie;
817   if(dest->wall == waTrapdoor)
818     return dest->land == laPalace ? moFatGuard : moOrangeDog;
819   if(dest->land == laFrog && dest->wall == waNone) {
820     forCellEx(c1, dest) if(c1->wall == waShrub) return moVaulter;
821     forCellEx(c1, dest) if(c1->wall == waDeepWater) return moFrog;
822     forCellEx(c1, dest) if(c1->wall == waStone) return moPhaser;
823     }
824   if(dest->wall == waSea)
825     return
826       isElemental(dest->land) ? moWaterElemental :
827       dest->land == laLivefjord ? moViking :
828       dest->land == laKraken ? moViking :
829       dest->land == laWarpCoast ? moRatling :
830       dest->land == laDocks ? moWaterElemental :
831       moPirate;
832   if(isReptile(dest->wall))
833     return moReptile;
834   if(dest->wall == waChasm)
835     return moAirElemental;
836   if(isFire(dest) || dest->wall == waMagma)
837     return moFireElemental;
838   if(dest->wall == waCavewall || dest->wall == waDeadwall)
839     return moSeep;
840   if(dest->wall == waRed1 || dest->wall == waRed2 || dest->wall == waRed3)
841     return moRedTroll;
842   if(dest->wall == waFrozenLake)
843     return moFireElemental;
844   if(dest->wall == waCIsland || dest->wall == waCIsland2)
845     return moWaterElemental;
846   if(dest->wall == waRubble || dest->wall == waGargoyleFloor || dest->wall == waGargoyleBridge || dest->wall == waLadder)
847     return moGargoyle;
848   if(dest->wall == waStrandedBoat)
849     return moWaterElemental;
850   if(dest->wall == waBoat)
851     return moAirElemental;
852   if(dest->wall == waStone || dest->wall == waRuinWall)
853     return moEarthElemental;
854   if(dest->wall == waGiantRug)
855     return moVizier;
856   if(dest->wall == waNone) {
857     if(dest->land == laHunting) return moAirElemental;
858     if(dest->land == laBull) return moRagingBull;
859     if(dest->land == laPrairie) return moAirElemental;
860     if(dest->land == laZebra) return moAirElemental;
861     if(dest->land == laMirror) return moAirElemental;
862     if(dest->land == laMountain) return moAirElemental; // unfortunately Ivies are too large
863     if(dest->land == laDungeon) return moBat;
864     if(dest->land == laIce) return moFireElemental;
865     if(dest->land == laDesert) return moEarthElemental;
866     if(dest->land == laJungle) return moWaterElemental;
867     if(dest->land == laGraveyard) return moZombie;
868     if(dest->land == laRlyeh || dest->land == laTemple) return moPyroCultist;
869     if(dest->land == laHell) return moWaterElemental;
870     if(dest->land == laPower) return moWitchFire;
871     if(dest->land == laWineyard) return moVineBeast;
872     if(dest->land == laEmerald) return moMiner;
873     if(dest->land == laHive) return dest->type == 7 ? moBug1 : moBug0;
874     if(dest->land == laRedRock) return moRedTroll;
875     if(dest->land == laOcean) return moEarthElemental;
876     if(dest->land == laDryForest) return moFireFairy;
877     if(dest->land == laLivefjord) return moFjordTroll;
878     if(dest->land == laStorms) return moStormTroll;
879     if(dest->land == laOvergrown) return moForestTroll;
880     if(dest->land == laIvoryTower) return moAirElemental;
881     if(dest->land == laEndorian) return moAirElemental;
882     if(dest->land == laEAir) return moAirElemental;
883     if(dest->land == laEWater) return moWaterElemental;
884     if(dest->land == laEEarth) return moEarthElemental;
885     if(dest->land == laEFire) return moFireElemental;
886     if(dest->land == laMotion) return moRunDog;
887     if(dest->land == laWildWest) return moOutlaw;
888     if(dest->land == laClearing) return moForestTroll;
889     if(dest->land == laWhirlwind) return moAirElemental;
890     if(dest->land == laWarpCoast) return moRatling;
891     if(dest->land == laRose) return moRoseLady;
892     if(dest->land == laDragon) return moFireElemental;
893     if(dest->land == laTortoise) return moTortoise;
894     if(dest->land == laBurial) return moEarthElemental;
895     if(dest->land == laVolcano) return moFireElemental;
896     if(dest->land == laBlizzard) return moAirElemental;
897     if(dest->land == laTerracotta) return moEarthElemental;
898     if(dest->land == laRuins) return moEarthElemental;
899     if(dest->land == laSwitch) return passive_switch;
900     if(dest->land == laSnakeNest) return moEarthElemental;
901     if(dest->land == laBrownian) return moAcidBird;
902     if(dest->land == laVariant) return moFireElemental;
903     if(dest->land == laWestWall) return moAirElemental;
904     if(isHaunted(dest->land)) return moGhost;
905     }
906   return moNone;
907   }
908 
summonAt(cell * dest)909 void summonAt(cell *dest) {
910   playSound(dest, "orb-ranged");
911   dest->monst = summonedAt(dest);
912   dest->stuntime = 3;
913   if(dest->monst == moPirate || dest->monst == moViking || (dest->monst == moRatling && dest->wall == waSea))
914     dest->wall = waBoat, dest->item = itNone;
915   if(dest->monst == moAnimatedDie)
916     dest->wall = waNone;
917   if(dest->monst == moAngryDie)
918     dest->wall = waNone;
919   if(dest->monst == moViking && dest->land == laKraken)
920     dest->item = itOrbFish;
921   if(dest->wall == waStrandedBoat)
922     dest->wall = waBoat;
923   else if(dest->monst == moWaterElemental)
924     placeWater(dest, dest);
925   if(dest->wall == waStone)
926     dest->wall = waNone;
927   if(dest->monst == moFireElemental && isFire(dest))
928     dest->wall = waNone;
929   if(dest->monst == moTortoise)
930     tortoise::emap[dest] = getBits(dest), dest->hitpoints = 3;
931   addMessage(XLAT("You summon %the1!", dest->monst));
932   moveEffect(movei(dest, FALL), dest->monst);
933   if(dest->wall == waClosePlate || dest->wall == waOpenPlate)
934     toggleGates(dest, dest->wall);
935 
936   if(hasHitpoints(dest->monst))
937     dest->hitpoints = palaceHP();
938 
939   useupOrb(itOrbSummon, 20);
940   createNoise(2);
941   bfs();
942   checkmoveO();
943   }
944 
tempWallPossibleAt(cell * dest)945 bool tempWallPossibleAt(cell *dest) {
946   if(dest->land == laWestWall) return false;
947   if(dest->monst || (dest->item && !itemHidden(dest))) return false;
948   return dest->wall == waChasm || isWatery(dest) || dest->wall == waNone ||
949     dest->wall == waTempBridge;
950   }
951 
tempWallAt(cell * dest)952 void tempWallAt(cell *dest) {
953   if(dest->wall == waChasm)
954     dest->wall = waTempFloor;
955   else if(dest->wall == waNone)
956     dest->wall = waTempWall;
957   else if(dest->wall == waTempBridge) {
958     dest->wall = waTempBridgeBlocked;
959     return;
960     }
961   else if(isWatery(dest))
962     dest->wall = waTempBridge;
963   int len = (items[itOrbMatter]+1) / 2;
964   dest->wparam = len;
965   useupOrb(itOrbMatter, len);
966   dest->item = itNone; // underwater items are destroyed by this
967   createNoise(2);
968   bfs();
969   checkmoveO();
970   }
971 
psi_attack(cell * dest)972 void psi_attack(cell *dest) {
973   playSound(dest, "other-mind");
974   if(isNonliving(dest->monst))
975     addMessage(XLAT("You destroy %the1 with a mental blast!", dest->monst));
976   else if(isDragon(dest->monst) || isKraken(dest->monst))
977     addMessage(XLAT("You damage %the1 with a mental blast!", dest->monst));
978   else
979     addMessage(XLAT("You kill %the1 with a mental blast!", dest->monst));
980   // note: psi attack works with Petrify!
981   attackMonster(dest, AF_PSI, moPlayer);
982   useupOrb(itOrbPsi, 30);
983   createNoise(2);
984   bfs();
985   checkmoveO();
986   }
987 
gun_attack(orbAction a,cell * dest)988 bool gun_attack(orbAction a, cell *dest) {
989   playSound(dest, "orb-ranged");
990   addMessage(XLAT("You shoot %the1!", dest->monst));
991   changes.ccell(dest);
992   attackMonster(dest, AF_GUN, moNone);
993   apply_impact(dest);
994 
995   if(monstersnearO(a, cwt.at)) {
996     changes.rollback();
997     return false;
998     }
999   if(isCheck(a)) {
1000     changes.rollback();
1001     return true;
1002     }
1003   changes.commit();
1004   items[itRevolver] --;
1005   bfs();
1006   checkmoveO();
1007   createNoise(5);
1008   monstersTurn();
1009   return true;
1010   }
1011 
checkStunKill(cell * dest)1012 EX void checkStunKill(cell *dest) {
1013   if(isBird(dest->monst)) {
1014     moveEffect(movei(dest, FALL), moDeadBird);
1015     doesFall(dest);
1016     if(isWatery(dest) || dest->wall == waChasm || isFire(dest)) {
1017       addMessage(XLAT("%The1 falls!", dest->monst));
1018       fallMonster(dest);
1019       return;
1020       }
1021     }
1022   /* if(!isPermanentFlying(dest->monst) && cellEdgeUnstable(dest)) {
1023     addMessage(XLAT("%The1 falls!", dest->monst));
1024     fallMonster(dest);
1025     } */
1026   }
1027 
stun_attack(cell * dest)1028 void stun_attack(cell *dest) {
1029   playSound(dest, "orb-ranged");
1030   addMessage(XLAT("You stun %the1!", dest->monst));
1031   dest->stuntime += 5;
1032   checkStunKill(dest);
1033   useupOrb(itOrbStunning, 10);
1034   createNoise(3);
1035   bfs();
1036   checkmoveO();
1037   }
1038 
poly_attack(cell * dest)1039 void poly_attack(cell *dest) {
1040   playSound(dest, "orb-ranged");
1041   eMonster orig = dest->monst;
1042   auto polymonsters = {
1043     moYeti, moRunDog, moRanger,
1044     moMonkey, moCultist,
1045     moFallingDog, moVariantWarrior, moFamiliar, moOrangeDog,
1046     moRedFox, moFalsePrincess, moResearcher,
1047     moNarciss,
1048     };
1049   int ssf = 0;
1050   eMonster target = *(polymonsters.begin() + hrand(isize(polymonsters)));
1051   for(eMonster m: polymonsters)
1052     if(kills[m] && m != dest->monst) {
1053       ssf += kills[m];
1054       if(hrand(ssf) < kills[m])
1055         target = m;
1056       }
1057   addMessage(XLAT("You polymorph %the1 into %the2!", dest->monst, target));
1058   dest->monst = target;
1059   if(!dest->stuntime) dest->stuntime = 1;
1060 
1061   if(orig == moPair) {
1062     cell *dest2 = dest->move(dest->mondir);
1063     if(dest2->monst == moPair) {
1064       dest2->monst = dest->monst;
1065       if(!dest2->stuntime) dest2->stuntime = 1;
1066       }
1067     }
1068 
1069   checkStunKill(dest);
1070   useupOrb(itOrbMorph, 3);
1071   createNoise(3);
1072   bfs();
1073   checkmoveO();
1074   }
1075 
placeIllusion(cell * c)1076 void placeIllusion(cell *c) {
1077   c->monst = moIllusion;
1078   c->stuntime = 0;
1079   useupOrb(itOrbIllusion, 5);
1080   addMessage(XLAT("You create an Illusion!"));
1081   bfs();
1082   checkmoveO();
1083   }
1084 
blowoff(const movei & mi)1085 void blowoff(const movei& mi) {
1086   auto& cf = mi.s;
1087   auto& ct = mi.t;
1088   bool die = cf->wall == waRichDie;
1089   playSound(ct, "orb-ranged");
1090   if(cf->monst)
1091   addMessage(XLAT("You blow %the1 away!", cf->monst));
1092   if(cf->wall == waThumperOff) activateActiv(cf, false);
1093   if(isPushable(cf->wall) || cf->wall == waBigStatue)
1094     pushThumper(mi);
1095   else if(isBoat(cf) && !cf->monst) {
1096     bool was_stranded = cf->wall == waStrandedBoat;
1097     bool willbe_stranded = ct->wall == waNone;
1098     if(was_stranded) cf->wall = waBoat;
1099     if(willbe_stranded) become_water(ct);
1100     moveBoat(mi);
1101     if(was_stranded) cf->wall = waNone;
1102     if(willbe_stranded) ct->wall = waStrandedBoat;
1103     }
1104   else
1105     pushMonster(mi);
1106   if(cf->item == itBabyTortoise) {
1107     if(ct->item) ct->item = itNone;
1108     moveItem(cf, ct, true);
1109     }
1110   #if CAP_COMPLEX2
1111   if(ct->monst == moAnimatedDie && dice::data[ct].happy() > 0) {
1112     ct->monst = moNone;
1113     ct->wall = waHappyDie;
1114     if(ct->land == laDice && cf->land == laDice) {
1115       cf->item = itDice;
1116       addMessage(XLAT("The die is now happy, and you are rewarded!"));
1117       }
1118     }
1119   if(die && ct->wall == waHappyDie) {
1120     /* pushMonster already awarded us -- place the reward on cf instead */
1121     cf->item = itDice;
1122     items[itDice]--;
1123     }
1124   if(ct->wall == waHappyDie && dice::data[ct].happy() <= 0) {
1125     ct->monst = moAngryDie;
1126     ct->wall = waNone;
1127     ct->stuntime = 5;
1128     addMessage(XLAT("You have made a Happy Die angry!"));
1129     }
1130   #endif
1131   items[itOrbAir]--;
1132   createNoise(2);
1133   bfs();
1134   checkmoveO();
1135   }
1136 
useOrbOfDragon(cell * c)1137 void useOrbOfDragon(cell *c) {
1138   makeflame(c, 20, false);
1139   playSound(c, "fire");
1140   addMessage(XLAT("You throw fire!"));
1141   useupOrb(itOrbDragon, 5);
1142   createNoise(3);
1143   bfs();
1144   checkmoveO();
1145   }
1146 
monstersnearO(orbAction a,cell * c)1147 EX bool monstersnearO(orbAction a, cell *c) {
1148   // printf("[a = %d] ", a);
1149   if(shmup::on) return false;
1150   if(a == roCheck && multi::players > 1)
1151     return true;
1152   else if(a == roMultiCheck) return false;
1153   else return monstersnear(c, moPlayer);
1154   }
1155 
isCheck(orbAction a)1156 EX bool isCheck(orbAction a) { return a == roCheck || a == roMultiCheck; }
isWeakCheck(orbAction a)1157 EX bool isWeakCheck(orbAction a) { return a == roCheck || a == roMultiCheck || a == roMouse; }
1158 
blowoff_destination(cell * c,int & di)1159 EX movei blowoff_destination(cell *c, int& di) {
1160   int d = 0;
1161   for(; d<c->type; d++) if(c->move(d) && c->move(d)->cpdist < c->cpdist) break;
1162   if(d<c->type) for(int e=d; e<d+c->type; e++) {
1163     int di = e % c->type;
1164     cell *c2 = c->move(di);
1165     #if CAP_COMPLEX2
1166     if(dice::on(c) && !dice::can_roll(movei(c, di)))
1167       continue;
1168     #endif
1169     if(c2 && c2->cpdist > c->cpdist && passable(c2, c, P_BLOW)) return movei(c, c2, di);
1170     }
1171   return movei(c, c, NO_SPACE);
1172   }
1173 
check_jump(cell * cf,cell * ct,flagtype flags,cell * & jumpthru)1174 EX int check_jump(cell *cf, cell *ct, flagtype flags, cell*& jumpthru) {
1175   int partial = 1;
1176   forCellCM(c2, cf) {
1177     if(isNeighbor(c2, ct)) {
1178       jumpthru = c2;
1179       if(passable(c2, cf, flags | P_JUMP1)) {
1180         partial = 2;
1181         if(passable(ct, c2, flags | P_JUMP2)) {
1182           return 3;
1183           }
1184         }
1185       }
1186     }
1187   return partial;
1188   }
1189 
check_phase(cell * cf,cell * ct,flagtype flags,cell * & jumpthru)1190 EX int check_phase(cell *cf, cell *ct, flagtype flags, cell*& jumpthru) {
1191   int partial = 1;
1192   forCellCM(c2, cf) {
1193     if(isNeighbor(c2, ct) && !nonAdjacent(cf, c2) && !nonAdjacent(c2, ct)) {
1194       jumpthru = c2;
1195       if(passable(ct, cf, flags | P_PHASE)) {
1196         partial = 2;
1197         if(c2->monst || (isWall(c2) && c2->wall != waShrub)) {
1198           return 3;
1199           }
1200         }
1201       }
1202     }
1203   return partial;
1204   }
1205 
common_neighbor(cell * cf,cell * ct)1206 EX cell *common_neighbor(cell *cf, cell *ct) {
1207   forCellCM(cc, cf) {
1208     if(isNeighbor(cc, ct)) return cc;
1209     }
1210   return nullptr;
1211   }
1212 
apply_impact(cell * c)1213 EX void apply_impact(cell *c) {
1214   if(markOrb(itOrbImpact))
1215     forCellEx(c1, c) {
1216       if(!c1->monst) continue;
1217       if(c1->monst == moMimic) continue;
1218       if(isMultitile(c1->monst)) continue;
1219       addMessage(XLAT("You stun %the1!", c1->monst));
1220       changes.ccell(c1);
1221       c1->stuntime = min(c1->stuntime + 5, 7);
1222       checkStunKill(c1);
1223       }
1224   }
1225 
check_vault(cell * cf,cell * ct,flagtype flags,cell * & jumpthru)1226 EX int check_vault(cell *cf, cell *ct, flagtype flags, cell*& jumpthru) {
1227   cell *c2 = NULL, *c3 = NULL;
1228   forCellCM(cc, cf) {
1229     if(isNeighbor(cc, ct)) c3 = c2, c2 = cc;
1230     }
1231   jumpthru = c2;
1232   if(!c2) return 0;
1233   bool cutwall = among(c2->wall, waShrub, waExplosiveBarrel, waSmallTree, waBigTree);
1234   if(!c2->monst && !cutwall) return 1;
1235   bool for_monster = !(flags & P_ISPLAYER);
1236   if(for_monster && c2->monst && frog_power(c2->monst) && !items[itOrbDiscord]) return 1;
1237   if(c3) return 2;
1238   if(!cutwall && !passable(c2, cwt.at, flags | P_JUMP1 | P_MONSTER)) return 3;
1239   if(!passable(ct, c2, flags | P_JUMP2)) return 4;
1240   if(!cutwall && !canAttack(cwt.at, moPlayer, c2, c2->monst, 0)) return 5;
1241   return 6;
1242   }
1243 
targetRangedOrb(cell * c,orbAction a)1244 EX eItem targetRangedOrb(cell *c, orbAction a) {
1245 
1246   if(!haveRangedOrb()) {
1247     return itNone;
1248     }
1249 
1250   if(rosedist(cwt.at) == 1) {
1251     int r = rosemap[cwt.at];
1252     int r2 = rosemap[c];
1253     if(r2 <= r && !markOrb(itOrbBeauty)) {
1254       if(a == roKeyboard || a == roMouseForce )
1255         addMessage(XLAT("Those roses smell too nicely. You can only target cells closer to them!"));
1256       return itNone;
1257       }
1258     }
1259 
1260   // (-2) shmup variants
1261   eItem shmupEffect = shmup::targetRangedOrb(a);
1262 
1263   if(shmupEffect) return shmupEffect;
1264 
1265   // (-1) distance
1266 
1267   if(c == cwt.at || isNeighbor(cwt.at, c)) {
1268     if(!isWeakCheck(a))
1269       addMessage(XLAT("You cannot target that close!"));
1270     return itNone;
1271     }
1272   if(c->cpdist > 7) {
1273     if(!isWeakCheck(a))
1274       addMessage(XLAT("You cannot target that far away!"));
1275     return itNone;
1276     }
1277 
1278   // (0-) strong wind
1279   if(items[itStrongWind] && c->cpdist == 2 && cwt.at == whirlwind::jumpFromWhereTo(c, true)) {
1280     changes.init(isCheck(a));
1281     if(jumpTo(a, c, itStrongWind)) return itStrongWind;
1282     }
1283 
1284   // (0x) control
1285   if(haveMount() && items[itOrbDomination] && dragon::whichturn != turncount) {
1286     if(!isCheck(a)) {
1287       dragon::target = c;
1288       dragon::whichturn = turncount;
1289       addMessage(XLAT("Commanded %the1!", haveMount()));
1290       checkmoveO();
1291       }
1292     return itOrbDomination;
1293     }
1294 
1295   // (0) telekinesis
1296   if(c->item && !itemHiddenFromSight(c) && !cwt.at->item && items[itOrbSpace] >= fixpower(spacedrain(c).first) && !cantGetGrimoire(c, !isCheck(a))
1297     && c->item != itBarrow) {
1298     if(!isCheck(a)) {
1299       bool saf = c->item == itOrbSafety;
1300       telekinesis(c);
1301       if(!saf) apply_impact(c);
1302       }
1303     return itOrbSpace;
1304     }
1305 
1306   // (0') air blow
1307   bool nowhereToBlow = false;
1308   if(items[itOrbAir] && (isBlowableMonster(c->monst) || isPushable(c->wall) || c->wall == waBigStatue || isBoat(c))) {
1309     int di = NODIR;
1310     movei mi = blowoff_destination(c, di);
1311     auto& c2 = mi.t;
1312     if(!mi.op()) nowhereToBlow = true;
1313     else if(isBoat(c) && !isWatery(c2) && c2->wall != waNone) nowhereToBlow = true;
1314     else if(c->wall == waBigStatue && !canPushStatueOn(c2, P_BLOW)) nowhereToBlow = true;
1315     else {
1316       if(!isCheck(a)) blowoff(mi), apply_impact(c);
1317       return itOrbAir;
1318       }
1319     }
1320 
1321   // nature
1322   if(items[itOrbNature] && numplayers() == 1 && c->monst != moFriendlyIvy) {
1323     vector<int> dirs;
1324     forCellIdCM(cf, d, c)
1325       if(cf->monst == moFriendlyIvy) {
1326 
1327         if(c->monst) {
1328           if(!canAttack(cf, moFriendlyIvy, c, c->monst, 0)) continue;
1329           if(monstersnear(cwt.at, moPlayer)) continue;
1330           }
1331         else {
1332           if(!passable(c, cf, P_ISPLAYER | P_MONSTER)) continue;
1333           if(strictlyAgainstGravity(c, cf, false, MF_IVY)) continue;
1334           if(monstersnear(cwt.at, moPlayer)) continue;
1335           }
1336         dirs.push_back(d);
1337         }
1338 
1339     int di = hrand_elt(dirs, -1);
1340     if(di != -1) {
1341       if(!isCheck(a)) growIvyTo(movei(c, di).rev()), apply_impact(c);
1342       return itOrbNature;
1343       }
1344     }
1345 
1346   // (0'') jump
1347   int jumpstate = 0;
1348   cell *jumpthru = NULL;
1349 
1350   // orb of vaulting
1351   if(!shmup::on && items[itOrbDash] && c->cpdist == 2) {
1352     int i = items[itOrbAether];
1353     if(i) items[itOrbAether] = i-1;
1354     jumpstate = 10 + check_vault(cwt.at, c, P_ISPLAYER, jumpthru);
1355     items[itOrbAether] = i;
1356 
1357     if(jumpstate == 16) {
1358       changes.init(isCheck(a));
1359       int k = tkills();
1360       eMonster m = jumpthru->monst;
1361       if(jumpthru->wall == waShrub) {
1362         addMessage(XLAT("You chop down the shrub."));
1363         jumpthru->wall = waNone;
1364         }
1365       if(jumpthru->wall == waSmallTree) {
1366         addMessage(XLAT("You chop down the tree."));
1367         jumpthru->wall = waNone;
1368         }
1369       if(jumpthru->wall == waBigTree) {
1370         addMessage(XLAT("You start chopping down the tree."));
1371         jumpthru->wall = waSmallTree;
1372         }
1373       if(jumpthru->wall == waExplosiveBarrel) {
1374         explodeBarrel(jumpthru);
1375         }
1376       if(m)
1377         attackMonster(jumpthru, AF_NORMAL | AF_MSG, moPlayer);
1378       k = tkills() - k;
1379       if(jumpTo(a, c, itOrbDash, k, m)) jumpstate = 17;
1380       if(jumpstate == 17) return itOrbDash;
1381       }
1382     }
1383 
1384   if(items[itOrbFrog] && c->cpdist == 2) {
1385     int i = items[itOrbAether];
1386     if(i) items[itOrbAether] = i-1;
1387     jumpstate = check_jump(cwt.at, c, P_ISPLAYER, jumpthru);
1388     items[itOrbAether] = i;
1389     if(jumpstate == 3) {
1390       changes.init(isCheck(a));
1391       if(jumpTo(a, c, itOrbFrog)) jumpstate = 4;
1392       if(jumpstate == 4) return itOrbFrog;
1393       }
1394     }
1395 
1396   if(items[itOrbPhasing] && c->cpdist == 2) {
1397     if(shmup::on) shmup::pushmonsters();
1398     int i = items[itOrbAether];
1399     if(i) items[itOrbAether] = i-1;
1400     jumpstate = 20 + check_phase(cwt.at, c, P_ISPLAYER, jumpthru);
1401     items[itOrbAether] = i;
1402     if(jumpstate == 23) {
1403       changes.init(isCheck(a));
1404       if(jumpTo(a, c, itOrbPhasing)) jumpstate = 24;
1405       }
1406     if(shmup::on) shmup::popmonsters();
1407     if(jumpstate == 24) return itOrbPhasing;
1408     }
1409 
1410   // (1) switch with an illusion
1411   if(items[itOrbTeleport] && c->monst == moIllusion && !cwt.at->monst && teleportAction() == 1) {
1412     if(!isCheck(a)) teleportTo(c), apply_impact(c);
1413     return itOrbTeleport;
1414     }
1415 
1416   // (2) place illusion
1417   if(!shmup::on && items[itOrbIllusion] && c->monst == moNone && c->item == itNone && passable(c, NULL, P_MIRROR)) {
1418     if(!isCheck(a)) placeIllusion(c), apply_impact(c);
1419     return itOrbIllusion;
1420     }
1421 
1422   // (3) teleport
1423   if(items[itOrbTeleport] && c->monst == moNone && (c->item == itNone || itemHidden(c)) &&
1424     passable(c, NULL, P_ISPLAYER | P_TELE) && teleportAction() && shmup::verifyTeleport()) {
1425     if(!isCheck(a)) teleportTo(c), apply_impact(c);
1426     return itOrbTeleport;
1427     }
1428 
1429   // (4) remove an illusion
1430   if(!shmup::on && items[itOrbIllusion] && c->monst == moIllusion) {
1431     if(!isCheck(a)) {
1432       addMessage(XLAT("You take the Illusion away."));
1433       items[itOrbIllusion] += 3; // 100% effective with the Orb of Energy!
1434       apply_impact(c);
1435       c->monst = moNone;
1436       }
1437     return itOrbIllusion;
1438     }
1439 
1440   // (4a) colt
1441   if(!shmup::on && items[itRevolver] && c->monst && canAttack(cwt.at, moPlayer, c, c->monst, AF_GUN)) {
1442     bool inrange = false;
1443     for(cell *c1: gun_targets(cwt.at)) if(c1 == c) inrange = true;
1444     if(inrange) {
1445       changes.init(isCheck(a));
1446       if(gun_attack(a, c)) return itRevolver;
1447       }
1448     }
1449 
1450   // (5) psi blast (non-shmup variant)
1451   if(!shmup::on && items[itOrbPsi] && c->monst && (isDragon(c->monst) || !isWorm(c)) && c->monst != moShadow && c->monst != moKrakenH) {
1452     if(!isCheck(a)) psi_attack(c), apply_impact(c);
1453     return itOrbPsi;
1454     }
1455 
1456   // (5a) summoning
1457   if(items[itOrbSummon] && summonedAt(c)) {
1458     if(!isCheck(a)) summonAt(c), apply_impact(c);
1459     return itOrbSummon;
1460     }
1461 
1462   // (5b) matter
1463   if(items[itOrbMatter] && tempWallPossibleAt(c)) {
1464     if(!isCheck(a)) tempWallAt(c), apply_impact(c);
1465     return itOrbMatter;
1466     }
1467 
1468   // (5c) stun
1469   if(items[itOrbStunning] && c->monst && !isMultitile(c->monst) && c->monst != moMimic && c->stuntime < 3 && !shmup::on) {
1470     if(!isCheck(a)) stun_attack(c), apply_impact(c);
1471     return itOrbStunning;
1472     }
1473 
1474   // (5d) poly
1475   if(items[itOrbMorph] && c->monst && !isMultitile(c->monst) && !shmup::on) {
1476     if(!isCheck(a)) poly_attack(c), apply_impact(c);
1477     return itOrbMorph;
1478     }
1479 
1480   // (6) place fire (non-shmup variant)
1481   if(!shmup::on && items[itOrbDragon] && makeflame(c, 20, true)) {
1482     if(!isCheck(a)) useOrbOfDragon(c), apply_impact(c);
1483     return itOrbDragon;
1484     }
1485 
1486   if(isWeakCheck(a)) return itNone;
1487 
1488   if(nowhereToBlow && isBlowableMonster(c->monst)) {
1489     addMessage(XLAT("Nowhere to blow %the1!", c->monst));
1490     }
1491   else if(nowhereToBlow) {
1492     addMessage(XLAT("Nowhere to blow %the1!", c->wall));
1493     }
1494   else if(jumpstate == 1 && jumpthru && jumpthru->monst) {
1495     addMessage(XLAT("Cannot jump through %the1!", jumpthru->monst));
1496     }
1497   else if(jumpstate == 1 && jumpthru) {
1498     addMessage(XLAT("Cannot jump through %the1!", jumpthru->wall));
1499     }
1500   else if(jumpstate == 2 && c->monst) {
1501     addMessage(XLAT("Cannot jump on %the1!", c->monst));
1502     }
1503   else if(jumpstate == 2 && c->wall) {
1504     addMessage(XLAT("Cannot jump on %the1!", c->wall));
1505     }
1506   else if(jumpstate == 3)
1507     wouldkill("%The1 would get you there!");
1508   else if(items[itOrbAir] && c->monst) {
1509     addMessage(XLAT("%The1 is immune to wind!", c->monst));
1510     }
1511   else if(items[itOrbPsi] && c->monst) {
1512     addMessage(XLAT("%The1 is immune to mental blasts!", c->monst));
1513     }
1514   else if(items[itOrbTeleport] && c->monst) {
1515     addMessage(XLAT("Cannot teleport on a monster!"));
1516     }
1517   else if(items[itOrbSpace] && c->item == itBarrow)
1518     addMessage(XLAT("%The1 is protected from this kind of magic!", c->item));
1519   else if(items[itOrbSpace] && saved_tortoise_on(c))
1520     addMessage(XLAT("No, that would be heartless!"));
1521   else if(c->item && items[itOrbSpace] && !itemHiddenFromSight(c)) {
1522     if(cwt.at->item)
1523       addMessage(XLAT("Cannot use %the1 here!", itOrbSpace));
1524     addMessage(XLAT("Not enough power for telekinesis!"));
1525     }
1526   else if(items[itOrbIllusion] && c->item)
1527     addMessage(XLAT("Cannot cast illusion on an item!"));
1528   else if(items[itOrbIllusion] && c->monst)
1529     addMessage(XLAT("Cannot cast illusion on a monster!"));
1530   else if(items[itOrbIllusion] && !passable(c, NULL, P_MIRROR))
1531     addMessage(XLAT("Cannot cast illusion here!"));
1532   else if(items[itOrbTeleport] && teleportAction() == 0) {
1533     addMessage(XLAT("All players are in the game!"));
1534     }
1535   else if(items[itOrbTeleport] && !passable(c, NULL, P_TELE | P_ISPLAYER)) {
1536     addMessage(XLAT("Cannot teleport here!"));
1537     }
1538   else if(items[itOrbMatter] && !tempWallPossibleAt(c)) {
1539     if(c->monst)
1540       addMessage(XLAT("Cannot create temporary matter on a monster!"));
1541     else if(c->item)
1542       addMessage(XLAT("Cannot create temporary matter on an item!"));
1543     else addMessage(XLAT("Cannot create temporary matter here!"));
1544     }
1545   else if(items[itOrbSummon] && !summonedAt(c)) {
1546     if(c->monst)
1547       addMessage(XLAT("Cannot summon on a monster!"));
1548     else
1549       addMessage(XLAT("No summoning possible here!"));
1550     }
1551   else if(items[itOrbTeleport] && c->item) {
1552     addMessage(XLAT("Cannot teleport on an item!"));
1553     }
1554   else if(items[itOrbDragon] && !makeflame(c, 20, true)) {
1555     addMessage(XLAT("Cannot throw fire there!"));
1556     }
1557   else return eItem(0);
1558 
1559   return eItem(-1);
1560   }
1561 
orbcharges(eItem it)1562 EX int orbcharges(eItem it) {
1563   switch(it) {
1564     case itRevolver: //pickup-key
1565       return 6 - items[itRevolver];
1566     case itOrbShield:
1567       return inv::on ? 30 : 20;
1568     case itOrbDiscord:
1569       return inv::on ? 46 : 23;
1570     case itOrbLove:
1571     case itOrbUndeath:
1572     case itOrbSpeed: //"pickup-speed");
1573     case itOrbInvis:
1574     case itOrbAether:
1575       return 30;
1576     case itOrbGravity:
1577       return 45;
1578     case itOrbChoice:
1579       return 60;
1580     case itOrbIntensity:
1581       return inv::on ? 150 : 50;
1582     case itOrbWinter: // "pickup-winter"
1583        return inv::on ? 45 : 30;
1584     case itOrbBeauty:
1585     case itOrbEmpathy:
1586     case itOrbFreedom:
1587       return 40;
1588     case itOrbFrog:
1589     case itOrbDash:
1590     case itOrbPhasing:
1591     case itOrbWoods:
1592       return 45;
1593     case itOrb37:
1594     case itOrbEnergy:
1595       return 50;
1596     case itOrbRecall:
1597     case itOrbNature:
1598     case itOrbStone:
1599     case itOrbStunning:
1600     case itOrbLuck:
1601       return 60;
1602     case itOrbWater:
1603     case itOrbMatter:
1604     case itOrbHorns:
1605     case itOrbBull:
1606     case itOrbShell:
1607     case itOrbAir:
1608     case itOrbSlaying:
1609       return 66;
1610     case itOrbTime:
1611     case itOrbSpace:
1612     case itOrbThorns:
1613     case itOrbLightning:
1614     case itOrbFlash:
1615     case itOrbIllusion:
1616     case itOrbPsi:
1617     case itOrbDigging:
1618     case itOrbTeleport:
1619     case itOrbMagnetism:
1620       return 77;
1621     case itOrbDomination:
1622       return 90;
1623     case itOrbSummon:
1624       return 120;
1625 
1626     case itOrbSword:
1627       return 60 + 30 * multi::activePlayers();
1628     case itOrbSword2:
1629       return 40 + 20 * multi::activePlayers();
1630     case itOrbFish:
1631       return 20 + 10 * multi::activePlayers();
1632     case itOrbFire:
1633       return sphere ? 3 : 30;
1634     case itOrbDragon:
1635        return sphere ? 10 : 77;
1636 
1637     case itOrbMorph:
1638       return 60;
1639 
1640     case itOrbLava:
1641       return 50;
1642 
1643     case itOrbSide1:
1644     case itOrbSide2:
1645     case itOrbSide3:
1646       return 50;
1647 
1648     case itOrbImpact:
1649       return 50;
1650 
1651     case itOrbChaos:
1652       return 60;
1653 
1654     case itOrbPlague:
1655       return 30;
1656 
1657     case itOrbPurity:
1658       return 15;
1659 
1660     case itCurseWeakness:
1661       return 20;
1662 
1663     case itCurseDraining:
1664       return 30;
1665 
1666     case itCurseWater:
1667       return 20;
1668 
1669     case itCurseFatigue:
1670       return 30;
1671 
1672     case itCurseRepulsion:
1673       return 30;
1674 
1675     case itCurseGluttony:
1676       return 30;
1677 
1678     default:
1679       return 0;
1680     }
1681   }
1682 
isShmupLifeOrb(eItem it)1683 EX bool isShmupLifeOrb(eItem it) {
1684   return
1685     it == itOrbLife || it == itOrbFriend ||
1686     it == itOrbNature || it == itOrbEmpathy ||
1687     it == itOrbUndeath || it == itOrbLove ||
1688     it == itOrbDomination || it == itOrbGravity;
1689   }
1690 
makelava(cell * c,int i)1691 EX void makelava(cell *c, int i) {
1692   if(!pseudohept(c)) return;
1693   if(isPlayerOn(c)) return;
1694   if(c->wall == waFire && c->wparam < i)
1695     c->wparam = i, orbused[itOrbLava] = true;
1696   else if(makeflame(c, i, false)) orbused[itOrbLava] = true;
1697   }
1698 
orboflava(int i)1699 EX void orboflava(int i) {
1700   if(items[itOrbLava])
1701     for(cell *c: dcal) {
1702       if(c->cpdist > 5) break;
1703       if(!c->monst || isFriendly(c) || survivesFire(c->monst)) continue;
1704       forCellEx(c2, c) makelava(c2, i);
1705       }
1706   }
1707 }
1708