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