1 // Copyright (C) 2011-2019 Zeno Rogue, see 'hyper.cpp' for details
2
3 /** \file gameutil.cpp
4 * \brief routines related to attacking and killing
5 */
6
7 #include "hyper.h"
8
9 namespace hr {
10
11 /** how many instances of each monster type has been killed */
12 EX array<int, motypes> kills;
13
14 int* killtable[] = {
15 &kills[moYeti], &kills[moWolf], &
16 kills[moRanger], &kills[moTroll], &kills[moGoblin], &
17 kills[moWorm], &kills[moDesertman], &kills[moIvyRoot], &
18 kills[moMonkey], &kills[moEagle], &kills[moSlime], &kills[moSeep], &
19 kills[moRunDog], &
20 kills[moCultist], &kills[moTentacle], &kills[moPyroCultist], &
21 kills[moLesser], &kills[moGreater], &
22 kills[moZombie], &kills[moGhost], &kills[moNecromancer], &
23 kills[moHedge], &kills[moFireFairy], &
24 kills[moCrystalSage], &kills[moShark], &kills[moGreaterShark], &
25 kills[moMiner], &kills[moFlailer], &kills[moLancer], &
26 kills[moVineSpirit], &kills[moVineBeast], &
27 kills[moBug0], &kills[moBug1], &kills[moBug2], &
28 kills[moDarkTroll], &kills[moEarthElemental], &
29 kills[moWitch], &kills[moEvilGolem], &kills[moWitchFlash], &kills[moWitchFire], &
30 kills[moWitchWinter], &kills[moWitchSpeed], &
31 kills[moCultistLeader], &
32 kills[moPirate], &kills[moCShark], &kills[moParrot], &
33 kills[moHexSnake], &kills[moRedTroll], &
34 kills[moPalace], &kills[moSkeleton], &kills[moFatGuard], &kills[moVizier], &
35 kills[moViking], &kills[moFjordTroll], &kills[moWaterElemental], &
36 kills[moAlbatross], &kills[moBomberbird], &
37 kills[moAirElemental], &kills[moFireElemental], &
38 kills[moGargoyle], &kills[moFamiliar], &kills[moOrangeDog], &
39 items[itMutant], &kills[moMetalBeast], &kills[moMetalBeast2], &
40 kills[moOutlaw], &kills[moForestTroll], &kills[moStormTroll], &
41 kills[moRedFox], &kills[moWindCrow], &
42 kills[moFalsePrincess], &kills[moRoseLady], &
43 kills[moRoseBeauty], &
44 kills[moRatling], &kills[moRatlingAvenger], &
45 kills[moDragonHead], &
46 kills[moGadfly], &kills[moSparrowhawk], &kills[moResearcher],
47 &kills[moKrakenH], &kills[moDraugr],
48 &kills[moBat], &kills[moReptile],
49 &kills[moHerdBull], &kills[moSleepBull], &kills[moRagingBull],
50 &kills[moButterfly],
51 &kills[moNarciss], &kills[moMirrorSpirit],
52 &kills[moHunterDog], &kills[moIceGolem], &kills[moVoidBeast],
53 &kills[moJiangshi], &kills[moTerraWarrior],
54 &kills[moSalamander], &kills[moLavaWolf],
55 &kills[moSwitch1], &kills[moSwitch2],
56 &kills[moMonk], &kills[moCrusher], &kills[moHexDemon], &kills[moAltDemon], &kills[moPair],
57 &kills[moBrownBug], &kills[moAcidBird],
58 &kills[moFallingDog], &kills[moVariantWarrior], &kills[moWestHawk],
59 &kills[moPike], &kills[moRusalka], &kills[moFrog], &kills[moPhaser], &kills[moVaulter],
60 &kills[moHexer], &kills[moAnimatedDie], &kills[moAngryDie],
61 NULL
62 };
63
tkills()64 EX int tkills() {
65 int res = 0;
66 for(int i=0; killtable[i]; i++) res += killtable[i][0];
67 return res;
68 }
69
killtypes()70 EX int killtypes() {
71 int res = 0;
72 for(int i=0; killtable[i]; i++) if(killtable[i][0]) res++;
73 return res;
74 }
75
76
arrow_stuns(eMonster m)77 EX bool arrow_stuns(eMonster m) {
78 return among(m, moCrusher, moMonk, moAltDemon, moHexDemon, moGreater, moGreaterM, moHedge);
79 }
80
canAttack(cell * c1,eMonster m1,cell * c2,eMonster m2,flagtype flags)81 EX bool canAttack(cell *c1, eMonster m1, cell *c2, eMonster m2, flagtype flags) {
82
83 // cannot eat worms
84 if((flags & AF_EAT) && isWorm(m2)) return false;
85
86 if(m1 == passive_switch || m2 == passive_switch) return false;
87
88 if((flags & AF_GETPLAYER) && isPlayerOn(c2)) m2 = moPlayer;
89
90 if(!m2) return false;
91
92 if(m2 == moPlayer && peace::on) return false;
93
94 if((flags & AF_WEAK) && isIvy(c2)) return false;
95
96 if((flags & AF_MUSTKILL) && attackJustStuns(c2, flags, m1))
97 return false;
98
99 if((flags & AF_ONLY_FRIEND) && m2 != moPlayer && !isFriendly(c2)) return false;
100 if((flags & AF_ONLY_FBUG) && m2 != moPlayer && !isFriendlyOrBug(c2)) return false;
101 if((flags & AF_ONLY_ENEMY) && (m2 == moPlayer || isFriendlyOrBug(c2))) return false;
102
103 if(isFriendly(c2) && markOrb(itOrbEmpathy) && (flags & (AF_STAB | AF_MAGIC | AF_HORNS | AF_SWORD | AF_SWORD_INTO | AF_SIDE | AF_PLAGUE)))
104 return false;
105
106 if(m1 == moArrowTrap && arrow_stuns(m2)) return true;
107
108 if(isDie(m2) && !(flags & (AF_MAGIC | AF_CRUSH))) return false;
109
110 if(among(m2, moAltDemon, moHexDemon, moPair, moCrusher, moNorthPole, moSouthPole, moMonk) && !(flags & (AF_EAT | AF_MAGIC | AF_BULL | AF_CRUSH)))
111 return false;
112
113 if(m2 == moHedge && !(flags & (AF_STAB | AF_TOUGH | AF_EAT | AF_MAGIC | AF_LANCE | AF_SWORD_INTO | AF_HORNS | AF_BULL | AF_CRUSH)))
114 if(!checkOrb(m1, itOrbThorns)) return false;
115
116 // krakens do not try to fight even with Discord
117 if((m1 == moKrakenT || m1 == moKrakenH) &&
118 (m2 == moKrakenT || m2 == moKrakenH))
119 return false;
120
121 if(m2 == moDraugr && !(flags & (AF_SWORD | AF_MAGIC | AF_SWORD_INTO | AF_HORNS | AF_CRUSH))) return false;
122
123 // if(m2 == moHerdBull && !(flags & AF_MAGIC)) return false;
124 if(isBull(m2) && !(flags & (AF_MAGIC | AF_HORNS | AF_SWORD_INTO | AF_CRUSH))) return false;
125 if(m2 == moButterfly && !(flags & (AF_MAGIC | AF_BULL | AF_HORNS | AF_SWORD_INTO | AF_CRUSH))) return false;
126
127 if(!(flags & AF_NOSHIELD) && ((flags & AF_NEXTTURN) ? checkOrb2 : checkOrb)(m2, itOrbShield)) return false;
128
129 if((flags & AF_STAB) && m2 != moHedge)
130 if(!checkOrb(m1, itOrbThorns)) return false;
131
132 if(flags & AF_BACK) {
133 if(m2 == moFlailer && !c2->stuntime) flags |= AF_IGNORE_UNARMED;
134 else if(m2 == moVizier && !isUnarmed(m1)) ;
135 else return false;
136 }
137
138 if(flags & AF_APPROACH) {
139 if(m2 == moLancer) ;
140 else if((flags & AF_HORNS) && checkOrb(m1, itOrbHorns)) ;
141 else return false;
142 }
143
144 if(!(flags & AF_IGNORE_UNARMED) && isUnarmed(m1)) return false;
145
146 if(m2 == moGreater || m2 == moGreaterM)
147 if(!(flags & (AF_MAGIC | AF_SWORD_INTO | AF_HORNS | AF_CRUSH))) return false;
148
149 if(!(flags & (AF_GUN | AF_SWORD | AF_SWORD_INTO | AF_MAGIC | AF_PLAGUE)))
150 if(c1 != c2 && !logical_adjacent(c1, m1, c2)) return false;
151
152 if(!(flags & (AF_LANCE | AF_STAB | AF_BACK | AF_APPROACH | AF_GUN | AF_MAGIC | AF_PLAGUE | AF_SIDE)))
153 if(c1 && c2 && againstRose(c1, c2) && !ignoresSmell(m1))
154 return false;
155
156 if(m2 == moShadow && !(flags & (AF_SWORD | AF_SWORD_INTO | AF_CRUSH))) return false;
157 if(isWorm(m2) && m2 != moTentacleGhost && !isDragon(m2)) return false;
158
159 // dragon can't attack itself, or player who mounted it
160 if(c1 && c2 && isWorm(c1->monst) && isWorm(c2->monst) && wormhead(c1) == wormhead(c2)
161 && m1 != moTentacleGhost && m2 != moTentacleGhost)
162 return false;
163
164 // if(m2 == moTortoise && !(flags & AF_MAGIC)) return false;
165
166 if(m2 == moRoseBeauty)
167 if(!(flags & (AF_MAGIC | AF_LANCE | AF_GUN | AF_SWORD_INTO | AF_BULL | AF_CRUSH)))
168 if(!isMimic(m1))
169 if(!checkOrb(m1, itOrbBeauty) && !checkOrb(m1, itOrbAether) && !checkOrb(m1, itOrbShield))
170 if(!c1 || !c2 || !withRose(c1,c2))
171 return false;
172
173 if(m2 == moFlailer && !c2->stuntime)
174 if(!(flags & (AF_MAGIC | AF_TOUGH | AF_EAT | AF_HORNS | AF_LANCE | AF_BACK | AF_SWORD_INTO | AF_BULL | AF_CRUSH))) return false;
175
176 if(m2 == moVizier && c2->hitpoints > 1 && !c2->stuntime)
177 if(!(flags & (AF_MAGIC | AF_TOUGH | AF_EAT | AF_HORNS | AF_LANCE | AF_BACK | AF_FAST | AF_BULL | AF_CRUSH))) return false;
178
179 return true;
180 }
181
petrify(cell * c,eWall walltype,eMonster m)182 EX bool petrify(cell *c, eWall walltype, eMonster m) {
183 destroyHalfvine(c);
184 destroyTrapsOn(c);
185 playSound(c, "die-troll");
186
187 if(walltype == waIcewall && !isIcyLand(c->land))
188 return false;
189
190 if(c->land == laWestWall) return false;
191
192 if(isWateryOrBoat(c) && c->land == laWhirlpool) {
193 c->wall = waSea;
194 return false;
195 }
196
197 if(c->wall == waRoundTable) return false;
198
199 if(walltype == waGargoyle && cellUnstableOrChasm(c))
200 walltype = waGargoyleFloor;
201 else if(walltype == waGargoyle && isWatery(c))
202 walltype = waGargoyleBridge;
203 else if(walltype == waPetrified && isWatery(c))
204 walltype = waPetrifiedBridge;
205 else if((c->wall == waTempBridge || c->wall == waTempBridgeBlocked) && c->land == laWhirlpool) {
206 c->wall = waTempBridgeBlocked;
207 return true;
208 }
209 else if(!doesnotFall(c)) {
210 fallingFloorAnimation(c, walltype, m);
211 return true;
212 }
213
214 if(isReptile(c->wall)) kills[moReptile]++;
215 destroyHalfvine(c);
216 c->wall = walltype;
217 c->wparam = m;
218 c->item = itNone;
219 return true;
220 }
221
killIvy(cell * c,eMonster who)222 EX void killIvy(cell *c, eMonster who) {
223 if(c->monst == moIvyDead) return;
224 changes.ccell(c);
225 if(checkOrb(who, itOrbStone)) petrify(c, waPetrified, c->monst);
226 c->monst = moIvyDead; // NEWYEARFIX
227 for(int i=0; i<c->type; i++) if(c->move(i))
228 if(isIvy(c->move(i)) && c->move(i)->mondir == c->c.spin(i))
229 killIvy(c->move(i), who), kills[moIvyDead]++;
230 }
231
prespill(cell * c,eWall t,int rad,cell * from)232 EX void prespill(cell* c, eWall t, int rad, cell *from) {
233 if(againstWind(c, from)) return;
234 changes.ccell(c);
235 // these monsters block spilling
236 if(c->monst == moSeep || c->monst == moVineSpirit || c->monst == moShark ||
237 c->monst == moGreaterShark || c->monst == moParrot || c->monst == moCShark)
238 return;
239 // turn statues into Slimes!
240 if(among(c->wall, waBigStatue, waTerraWarrior) && t != waNone) {
241 c->wall = waNone;
242 c->monst = moSlimeNextTurn;
243 }
244 // slimedeath spill
245 if((c->monst == moSlime || c->monst == moSlimeNextTurn) && t == waNone) {
246 c->wall = waNone; attackMonster(c, 0, moNone);
247 }
248 if(c->wall == waClosedGate) {
249 c->wall = waPalace;
250 return;
251 }
252 // no slime in Whirlpool
253 if(c->land == laWhirlpool) return;
254 // these walls block spilling completely
255 if(c->wall == waIcewall || c->wall == waBarrier || c->wall == waWarpGate ||
256 c->wall == waDeadTroll || c->wall == waDeadTroll2 ||
257 c->wall == waDune || c->wall == waAncientGrave ||
258 c->wall == waThumperOff || c->wall == waThumperOn ||
259 c->wall == waFreshGrave || c->wall == waColumn || c->wall == waPartialFire ||
260 c->wall == waDeadwall || c->wall == waWaxWall || c->wall == waCamelot || c->wall == waRoundTable ||
261 c->wall == waBigStatue || c->wall == waRed1 || c->wall == waRed2 || c->wall == waRed3 ||
262 c->wall == waTower ||
263 c->wall == waPalace ||
264 c->wall == waPlatform || c->wall == waStone || c->wall == waTempWall ||
265 c->wall == waTempFloor || c->wall == waTempBridge || c->wall == waPetrifiedBridge || c->wall == waTempBridgeBlocked ||
266 c->wall == waSandstone || c->wall == waCharged || c->wall == waGrounded ||
267 c->wall == waMetal || c->wall == waSaloon || c->wall == waFan ||
268 c->wall == waBarrowDig || c->wall == waBarrowWall ||
269 c->wall == waMirrorWall)
270 return;
271 if(c->wall == waFireTrap) {
272 if(c->wparam == 0) c->wparam = 1;
273 return;
274 }
275 if(c->wall == waExplosiveBarrel)
276 explodeBarrel(c);
277 destroyTrapsOn(c);
278 // these walls block further spilling
279 if(c->wall == waCavewall || cellUnstable(c) || c->wall == waSulphur ||
280 c->wall == waSulphurC || c->wall == waLake || c->wall == waChasm ||
281 c->wall == waBigTree || c->wall == waSmallTree || c->wall == waTemporary ||
282 c->wall == waVinePlant || isFire(c) || c->wall == waBonfireOff || c->wall == waVineHalfA || c->wall == waVineHalfB ||
283 c->wall == waCamelotMoat || c->wall == waSea || c->wall == waCTree ||
284 c->wall == waRubble || c->wall == waGargoyleFloor || c->wall == waGargoyle ||
285 c->wall == waRose || c->wall == waPetrified || c->wall == waPetrifiedBridge || c->wall == waRuinWall ||
286 among(c->wall, waDeepWater, waShallow))
287 t = waTemporary;
288
289 if(c->wall == waSulphur) {
290 // remove the center as it would not look good
291 for(int i=0; i<c->type; i++) if(c->move(i) && c->move(i)->wall == waSulphurC)
292 c->move(i)->wall = waSulphur;
293 }
294
295 if(isReptile(c->wall)) {
296 if(c->monst || isPlayerOn(c)) kills[moReptile]++;
297 else c->monst = moReptile, c->stuntime = 3, c->hitpoints = 3;
298 }
299
300 destroyHalfvine(c);
301 if(c->wall == waTerraWarrior) kills[waTerraWarrior]++;
302 c->wall = t;
303 // destroy items...
304 c->item = itNone;
305 // block spill
306 if(t == waTemporary) return;
307 // cwt.at->item = itNone;
308 if(rad) for(cell *c2: adj_minefield_cells(c))
309 prespill(c2, t, rad-1, c);
310 }
311
spillfix(cell * c,eWall t,int rad)312 EX void spillfix(cell* c, eWall t, int rad) {
313 if(c->wall == waTemporary) {
314 changes.ccell(c);
315 c->wall = t;
316 }
317 if(rad) for(cell *c2: adj_minefield_cells(c))
318 spillfix(c2, t, rad-1);
319 }
320
spill(cell * c,eWall t,int rad)321 EX void spill(cell* c, eWall t, int rad) {
322 prespill(c,t,rad, c); spillfix(c,t,rad);
323 }
324
degradeDemons()325 EX void degradeDemons() {
326 addMessage(XLAT("You feel more experienced in demon fighting!"));
327 playSound(cwt.at, "levelup");
328 int dcs = isize(dcal);
329 for(int i=0; i<dcs; i++) {
330 cell *c = dcal[i];
331 if(c->monst == moGreaterM || c->monst == moGreater) {
332 changes.ccell(c);
333 achievement_gain_once("DEMONSLAYER");
334 if(c->monst == moGreaterM) c->monst = moLesserM;
335 if(c->monst == moGreater) c->monst = moLesser;
336 }
337 }
338 shmup::degradeDemons();
339 }
340
stunMonster(cell * c2,eMonster killer,flagtype flags)341 EX void stunMonster(cell *c2, eMonster killer, flagtype flags) {
342 int newtime = (
343 c2->monst == moFatGuard ? 2 :
344 c2->monst == moSkeleton && c2->land != laPalace && c2->land != laHalloween ? 7 :
345 c2->monst == moTerraWarrior ? min(int(c2->stuntime + 8 - c2->hitpoints), 7) :
346 isMetalBeast(c2->monst) ? 7 :
347 c2->monst == moTortoise ? 7 :
348 c2->monst == moWorldTurtle ? 7 :
349 c2->monst == moReptile ? 7 :
350 isPrincess(c2->monst) ? 6 :
351 // spear stunning
352 isBull(c2->monst) ? 3 :
353 (c2->monst == moGreater || c2->monst == moGreaterM) ? 5 :
354 c2->monst == moButterfly ? 2 :
355 c2->monst == moDraugr ? 1 :
356 c2->monst == moVizier ? 0 :
357 c2->monst == moHedge ? 1 :
358 c2->monst == moFlailer ? 1 :
359 c2->monst == moSalamander ? 6 :
360 c2->monst == moBrownBug ? 3 :
361 ((flags & AF_WEAK) && !attackJustStuns(c2, flags &~ AF_WEAK, killer)) ? min(5+c2->stuntime, 15) :
362 3);
363 if(killer == moArrowTrap) newtime = min(newtime + 3, 7);
364 if(!(flags & AF_WEAK) && !isMetalBeast(c2->monst) && !among(c2->monst, moSkeleton, moReptile, moSalamander, moTortoise, moWorldTurtle, moBrownBug)) {
365 c2->hitpoints--;
366 if(c2->monst == moPrincess)
367 playSound(c2, princessgender() ? "hit-princess" : "hit-prince");
368 }
369 if(c2->stuntime < newtime) c2->stuntime = newtime;
370 if(isBull(c2->monst)) c2->mondir = NODIR;
371 checkStunKill(c2);
372 }
373
attackJustStuns(cell * c2,flagtype f,eMonster attacker)374 EX bool attackJustStuns(cell *c2, flagtype f, eMonster attacker) {
375 if(f & AF_WEAK)
376 return true;
377 if(f & AF_HORNS)
378 return hornStuns(c2);
379 else if(attacker == moArrowTrap && arrow_stuns(c2->monst))
380 return true;
381 else if((f & AF_SWORD) && c2->monst == moSkeleton)
382 return false;
383 else if(f & (AF_CRUSH | AF_MAGIC | AF_FALL | AF_EAT | AF_GUN | AF_PSI))
384 return false;
385 else
386 return isStunnable(c2->monst) && c2->hitpoints > 1;
387 }
388
minerEffect(cell * c)389 EX void minerEffect(cell *c) {
390 changes.ccell(c);
391 eWall ow = c->wall;
392 if(c->wall == waOpenGate || c->wall == waFrozenLake || c->wall == waBoat ||
393 c->wall == waStrandedBoat ||
394 c->wall == waCIsland || c->wall == waCIsland2 || c->wall == waTrapdoor ||
395 c->wall == waGiantRug) ;
396 else if(c->wall == waMineUnknown || c->wall == waMineMine || c->wall == waMineOpen)
397 c->wall = waMineOpen;
398 else if(c->wall == waRed2) c->wall = waRed1;
399 else if(c->wall == waRed3) c->wall = waRed2;
400 else if(isReptile(c->wall))
401 c->wparam = 1; // wake up next turn
402 else if(c->wall == waTempFloor) c->wall = waChasm;
403 else if(c->wall == waTempBridge || c->wall == waPetrifiedBridge || c->wall == waTempBridgeBlocked)
404 placeWater(c, NULL);
405 else if(doesFall(c))
406 ow = waNone;
407 else
408 c->wall = waNone;
409 if(c->wall != ow && ow) drawParticles(c, winf[ow].color, 16);
410 }
411
killMutantIvy(cell * c,eMonster who)412 EX void killMutantIvy(cell *c, eMonster who) {
413 if(checkOrb(who, itOrbStone)) petrify(c, waPetrified, moMutant);
414 changes.ccell(c);
415 removeIvy(c);
416 for(int i=0; i<c->type; i++)
417 if(c->move(i)->mondir == c->c.spin(i) && (isMutantIvy(c->move(i)) || c->move(i)->monst == moFriendlyIvy))
418 kills[c->move(i)->monst]++, killMutantIvy(c->move(i), who);
419 if(c->land == laClearing) clearing::imput(c);
420 }
421
ivy_total()422 EX bignum ivy_total() {
423 return kills[moMutant] + kills[moFriendlyIvy] +
424 kills[moIvyRoot] + kills[moIvyHead] + kills[moIvyBranch] + kills[moIvyWait] + kills[moIvyDead]
425 + clearing::imputed;
426 }
427
428 EX void killMonster(cell *c, eMonster who, flagtype deathflags IS(0)) {
429 eMonster m = c->monst;
430 DEBBI(DF_TURN, ("killmonster ", dnameof(m)));
431
432 if(!m) return;
433
434 if(m == moKrakenH) return;
435 if(m == moKrakenT) {
436 if(c->hitpoints && m == moKrakenT) kills[moKrakenT]++;
437 c->hitpoints = 0;
438 c->stuntime = 1;
439 cell *head = kraken::head(c);
440 if(kraken::totalhp(head) == 0) {
441 kraken::kill(head, who);
442 playSound(head, "die-kraken");
443 }
444 return;
445 }
446
447 if(isDragon(m)) {
448 if(c->hitpoints && m != moDragonHead) kills[moDragonTail]++;
449 c->hitpoints = 0;
450 c->stuntime = 1;
451 cell *head = dragon::findhead(c);
452 if(dragon::totalhp(head) == 0) dragon::kill(head, who);
453 return;
454 }
455 if(isWorm(c) && m != moTentacleGhost) return;
456
457 bool fallanim = (deathflags & AF_FALL) && m != moMimic;
458
459 int pcount = fallanim ? 0 : 16;
460 if(m == moShadow)
461 kill_shadow_at(c);
462
463 #if CAP_HISTORY
464 if(!isBug(m) && !isAnyIvy(m)) {
465 history::killhistory.push_back(make_pair(c,m));
466 }
467 #endif
468
469 if(m == moGolemMoved) m = moGolem;
470 if(m == moKnightMoved) m = moKnight;
471 if(m == moSlimeNextTurn) m = moSlime;
472 if(m == moLesserM) m = moLesser;
473 if(m == moGreater) m = moLesser;
474 if(m == moGreaterM) m = moLesser;
475 if(isPrincess(m)) m = moPrincess;
476 if(m == moTentacleGhost) m = moGhost;
477 if(m == moHunterGuard) m = moHunterDog;
478 if(m == moHunterChanging) m = moHunterDog;
479 if(m == moWolfMoved) m = moWolf;
480 if(!isBulletType(m) && m != moIvyDead) kills[m]++;
481
482 if(saved_tortoise_on(c)) c->item = itNone;
483
484 if(!c->item) if(m == moButterfly && (deathflags & AF_BULL))
485 c->item = itBull;
486
487 if(isRatling(m) && c->wall == waBoat) {
488 bool avenge = false;
489 for(int i=0; i<c->type; i++) if(!isWarpedType(c->move(i)->land))
490 avenge = true;
491 if(avenge)
492 changes.value_add(avengers, 2);
493 }
494
495 if(m == moMirrorSpirit && who != moMimic && !(deathflags & (AF_MAGIC | AF_CRUSH))) {
496 kills[m]--;
497 changes.value_inc(mirrorspirits);
498 }
499
500 if(isMutantIvy(m) || m == moFriendlyIvy) {
501 pcount = 0;
__anon0c8e29550102null502 if(isMutantIvy(m)) changes.at_commit([] { clearing::direct++; });
503 changes.value_keep(clearing::imputed);
504 bignum s = ivy_total() - 1;
505 killMutantIvy(c, who);
506 s = ivy_total() - s;
507 if(vid.bubbles_special && s > bignum(1))
508 drawBubble(c, 0xFFFF00, s.get_str(100), .5);
509 if(isize(clearing::imputed.digits) > 11)
510 achievement_gain_once("KILLMUTANT");
511 }
512
513 if(m == moPrincess) {
514 princess::info *i = princess::getPrincessInfo(c);
515 if(i) {
516 changes.value_keep(*i);
517 i->princess = NULL;
518 if(i->bestdist == OUT_OF_PALACE) {
519 items[itSavedPrincess]--;
520 if(items[itSavedPrincess] < 0) {
521 printf("princess below 0\n");
522 items[itSavedPrincess] = 0;
523 }
524 if(items[itSavedPrincess] == 0 && !inv::on) {
525 items[itOrbLove] = 0;
526 changes.value_keep(princess::reviveAt);
527 princess::reviveAt = gold(NO_LOVE) + 20;
528 }
529 }
__anon0c8e29550202null530 if(princess::challenge) changes.at_commit([] { showMissionScreen(); });
531 }
532 }
533
534 if(m == moGargoyle && c->wall != waMineMine) {
535 bool connected = false;
536
537 if(isGravityLand(c->land)) {
538 for(int i=0; i<c->type; i++) {
539 cell *c2 = c->move(i);
540 if(c2->wall == waPlatform || c2->wall == waGargoyle || c2->wall == waBarrier ||
541 c2->wall == waDeadTroll || c2->wall == waDeadTroll2 || c2->wall == waTrunk ||
542 c2->wall == waPetrified || isAlchAny(c2->wall))
543 connected = true;
544 }
545 }
546 else {
547 for(int i=0; i<c->type; i++) {
548 cell *c2 = c->move(i);
549 if(!cellUnstableOrChasm(c2) && !isWatery(c2)) connected = true;
550 }
551 }
552
553 if(connected) petrify(c, waGargoyle, m), pcount = 0;
554 }
555
556 if(m == moIceGolem) {
557 if(petrify(c, waIcewall, m)) pcount = 0;
558 heat::affect(c, -1);
forCellEx(c2,c)559 forCellEx(c2, c) {
560 changes.ccell(c2);
561 heat::affect(c2, -.5);
562 }
563 }
564
565 if(m == moTroll) {
566 petrify(c, waDeadTroll, m); pcount = 0;
forCellEx(c1,c)567 forCellEx(c1, c) {
568 changes.ccell(c1);
569 c1->item = itNone;
570 if(c1->wall == waDeadwall) c1->wall = waCavewall;
571 if(c1->wall == waDeadfloor2 && !c1->monst && !isPlayerOn(c1)) c1->wall = waCavewall;
572 if(c1->wall == waDeadfloor) c1->wall = waCavefloor;
573 }
574 }
575 if(m == moFjordTroll || m == moForestTroll || m == moStormTroll) {
576 petrify(c, waDeadTroll2, m);
577 }
578 if(m == moMiner) {
579 pcount = 32;
580 playSound(c, "splash" + pick12());
581 destroyHalfvine(c);
582 minerEffect(c);
583 #if CAP_COMPLEX2
584 brownian::dissolve_brownian(c, 1);
585 #endif
586 forCellEx(c1, c) if(passable(c1, c, P_MONSTER | P_MIRROR | P_CLIMBUP | P_CLIMBDOWN)) {
587 changes.ccell(c1);
588 destroyHalfvine(c1);
589 minerEffect(c1);
590 #if CAP_COMPLEX2
591 brownian::dissolve_brownian(c1, 1);
592 #endif
593 if(c1->monst == moSlime || c1->monst == moSlimeNextTurn)
594 killMonster(c1, who);
595 }
forCellEx(c2,c)596 forCellEx(c2, c) {
597 changes.ccell(c2);
598 if(c2->wall == waPalace) c2->wall = waRubble;
599 if(c2->wall == waDeadwall) c2->wall = waDeadfloor2;
600 if(c2->wall == waExplosiveBarrel) explodeBarrel(c2);
601 }
602 }
603 if(m == moOrangeDog) {
604 if(pcount) for(int i=0; i<8; i++) {
605 drawParticle(c, 0xFFFFFF);
606 drawParticle(c, 0x202020);
607 }
608 pcount = 0;
609 }
610 if(m == moDraugr) {
611 if(pcount) drawParticles(c, 0x804000, 8);
612 pcount = 0;
613 }
614 if(m == moPalace) {
615 if(pcount) {
616 pcount = 4;
617 for(int i=0; i<12; i++) drawParticle(c, 0x20C020);
618 }
619 }
620 if(m == moPrincess) {
621 playSound(c, princessgender() ? "die-princess" : "die-prince");
622 }
623 if(m == moVineBeast)
624 petrify(c, waVinePlant, m), pcount = 0;
625 if(isBird(m)) moveEffect(movei(c, FALL), moDeadBird);
626 if(m == moAcidBird) {
627 playSound(c, "die-bomberbird");
628 pcount = 64;
629 #if CAP_COMPLEX2
630 brownian::dissolve(c, 1);
631 #endif
632 }
633 if(m == moBomberbird || m == moTameBomberbird) {
634 pcount = 0;
635 playSound(c, "die-bomberbird");
636 if(c->wall == waNone || c->wall == waMineUnknown || c->wall == waMineOpen ||
637 c->wall == waCavefloor || c->wall == waDeadfloor || c->wall == waDeadfloor2 ||
638 c->wall == waRubble || c->wall == waGargoyleFloor ||
639 c->wall == waCIsland || c->wall == waCIsland2 ||
640 c->wall == waStrandedBoat || c->wall == waRed1 || c->wall == waGiantRug) {
641 c->wall = waMineMine;
642 if(c->item) explodeMine(c);
643 else if(c->land == laMinefield) c->landparam = 1;
644 }
645 else if(c->wall == waFrozenLake)
646 c->wall = waLake;
647 else if(c->wall == waGargoyleBridge)
648 placeWater(c, c);
649 else if(c->wall == waRed3 || c->wall == waRed2) {
650 c->wall = waRed1;
651 for(int i=0; i<c->type; i++) if(c->move(i)->wall == waRed3)
652 c->move(i)->wall = waRed2;
653 c->item = itNone;
654 }
655 eWall w = c->wall;
656 if(isFire(c) || c->wall == waRose || isReptile(c->wall)) {
657 c->wall = waMineMine;
658 explodeMine(c);
659 if(isReptile(w)) kills[moReptile]++;
660 if(w == waReptile) c->wall = waChasm;
661 if(w == waReptileBridge) placeWater(c, NULL);
662 }
663 }
664 if(m == moVineSpirit) {
665 pcount = 32;
666 playSound(c, "die-vinespirit");
667 destroyHalfvine(c);
668 if(!isFire(c)) c->wall = waNone;
669 }
670 if(m == moRedTroll) {
671 playSound(c, "die-troll");
672 if(doesFall(c)) fallingFloorAnimation(c, waRed1, m), pcount = 0;
673 else if(snakepile(c, m)) pcount = 0;
674 }
675 if(m == moBrownBug) {
676 if(doesFall(c)) ;
677 else if(snakepile(c, m)) pcount = 0;
678 }
679 if(m == moDarkTroll) {
680 playSound(c, "die-troll");
681 if(doesFall(c)) fallingFloorAnimation(c, waDeadwall, m), pcount = 0;
682 else if(c->wall == waRed1 || c->wall == waRed2 || c->wall == waRed3)
683 c->wall = waDeadwall, pcount = 0;
684 else if(snakepile(c, m))
685 pcount = 0;
686 }
687 if(isWitch(m) && (isFire(c) || passable(c, NULL, P_MONSTER)) && !c->item) {
688 if(m == moWitchFire) c->item = itOrbFire;
689 if(m == moWitchFlash) c->item = itOrbFlash;
690 if(m == moWitchGhost) c->item = itOrbAether;
691 if(m == moWitchWinter) c->item = itOrbWinter;
692 if(m == moWitchSpeed) c->item = itOrbSpeed;
693 if(isFire(c) && itemBurns(c->item))
694 c->item = itNone;
695 }
696 if(who == moPlayer || (isFriendly(who) && items[itOrbEmpathy])) {
697 eItem o = frog_power(m);
698 if(o && who != moPlayer) markOrb2(itOrbEmpathy);
699 if(o) items[o] += 5;
700 }
701 if(checkOrb(who, itOrbStone))
702 petrify(c, waPetrified, m), pcount = 0;
703 if(m == moFireFairy) {
704 drawFireParticles(c, 16); pcount = 0;
705 playSound(c, "die-fairy");
706 playSound(c, "fire");
707 makeflame(c, 50, false);
708 }
709 if(c->monst == moMetalBeast2 && !c->item && who == moLightningBolt && c->wall != waPetrified && c->wall != waChasm)
710 c->item = itFulgurite; // this is actually redundant in many cases
711 if(m == moPyroCultist && c->item == itNone && c->wall != waChasm && c->wall != waPetrified) {
712 // a reward for killing him before he shoots!
713 c->item = itOrbDragon;
714 }
715 if(m == moOutlaw && (c->item == itNone || c->item == itRevolver) && c->wall != waChasm)
716 c->item = itBounty;
717 // note: an Orb appears underwater!
718 if(m == moWaterElemental && c->item == itNone)
719 c->item = itOrbWater;
720
721 if(m == moPirate && isOnCIsland(c) && c->item == itNone && (
722 eubinary ||
723 (c->master->alt && celldistAlt(c) <= 2-getDistLimit()) ||
724 isHaunted(c->land)) && !cryst) {
725 bool toomany = false;
726 for(int i=0; i<c->type; i++) {
727 cell *c2 = c->move(i);
728 if(c2 && c2->item == itCompass) toomany = true;
729 if(c2 && BITRUNCATED) for(int j=0; j<c2->type; j++)
730 if(c2->move(j) && c2->move(j)->item == itCompass)
731 toomany = true;
732 }
733 if(!toomany) c->item = itCompass;
734 }
735 if(m == moSlime) {
736 pcount = 0;
737 drawParticles(c, winf[c->wall].color, 80, 200);
738 playSound(c, "splash" + pick12());
739 c->monst = moNone;
740 spill(c, c->wall, 2);
741 }
742 // if(c->monst == moShark) c->heat += 1;
743 // if(c->monst == moGreaterShark) c->heat += 10;
744 if(isIcyLand(c)) {
745 if(m == moCultist) HEAT(c) += 3;
746 if(m == moPyroCultist) HEAT(c) += 6;
747 if(m == moLesser) HEAT(c) += 10;
748 }
749 if(m == moLesser && !(kills[m] % 10))
750 degradeDemons();
751 if(m == moLesser) {
752 if(kills[m] % 10) {
753 if(vid.bubbles_special)
754 drawBubble(c, 0xFF0000, its(kills[m]%10), 1);
755 }
756 else {
757 if(vid.bubbles_special)
758 drawBubble(c, 0xFF8000, "+1 XP", .8);
759 degradeDemons();
760 }
761 }
762 if(isIvy(c)) {
763 pcount = 0;
764 eMonster m = c->monst;
765 bignum s = ivy_total() - 1;
766 /*if((m == moIvyBranch || m == moIvyHead) && c->move(c->mondir)->monst == moIvyRoot)
767 ivynext(c, moIvyNext); */
768 killIvy(c, who);
769 s = ivy_total() - s;
770 if(s > bignum(1) && vid.bubbles_special)
771 drawBubble(c, 0xFFFF00, s.get_str(100), .5);
772 if(m == moIvyBranch || m == moIvyHead || m == moIvyNext) {
773 int qty = 0;
774 cell *c2 = proper(c, c->mondir) ? c->move(c->mondir) : nullptr;
775 if(!c2) c2 = c; /* should not happen */
776 for(int i=0; i<c2->type; i++)
777 if(c2->move(i)->monst == moIvyWait && c2->move(i)->mondir == c2->c.spin(i))
778 qty++;
779 if(c2->monst == moIvyRoot || qty) {
780 c->monst = moIvyNext;
781 /* c->monst = moIvyHead;
782 ivynext(c);
783 if(c->monst == moIvyHead) c->monst = moIvyNext;
784 else c->monst = moNone; */
785 }
786 else {
787 changes.ccell(c2);
788 c2->monst = moIvyHead;
789 }
790 }
791 }
792 else if(c->monst == moTentacleGhost)
793 c->monst = moTentacletail;
794 else c->monst = moNone;
795
796 if(m == moPair && c->move(c->mondir)->monst == moPair) {
797 changes.ccell(c->move(c->mondir));
798 killMonster(c->move(c->mondir), who, deathflags);
799 }
800
801 if(isMagneticPole(m) && c->move(c->mondir)->monst == otherpole(m)) {
802 changes.ccell(c->move(c->mondir));
803 killMonster(c->move(c->mondir), who, deathflags);
804 }
805
806 if(m == moEarthElemental) earthWall(c);
807 if(m == moAlbatross && items[itOrbLuck])
808 useupOrb(itOrbLuck, items[itOrbLuck] / 2);
809
810 if(m == moAirElemental) {
811 changes.value_keep(airmap);
812 airmap.clear();
813 for(int i=0; i<isize(dcal); i++)
814 if(dcal[i]->monst == moAirElemental)
815 airmap.push_back(make_pair(dcal[i],0));
816 buildAirmap();
817 }
818 if(m == moMimic) {
819 for(auto& m: mirror::mirrors) if(c == m.second.at) {
820 drawParticles(c, mirrorcolor(m.second.mirrored), pcount);
821 if(c->wall == waMirrorWall)
822 drawParticles(c, mirrorcolor(!m.second.mirrored), pcount);
823 }
824 pcount = 0;
825 }
826
827 drawParticles(c, minf[m].color, pcount);
828 if(fallanim) {
829 fallingMonsterAnimation(c, m);
830 }
831 }
832
fightmessage(eMonster victim,eMonster attacker,bool stun,flagtype flags)833 EX void fightmessage(eMonster victim, eMonster attacker, bool stun, flagtype flags) {
834
835 if(isBird(attacker)) {
836 playSound(NULL, "hit-axe"+pick123());
837 addMessage(XLAT("%The1 claws %the2!", attacker, victim));
838 }
839
840 else if(isGhost(attacker))
841 addMessage(XLAT("%The1 scares %the2!", attacker, victim));
842
843 else if(isSlimeMover(attacker) && !stun) {
844 playSound(NULL, "hit-crush"+pick123());
845 addMessage(XLAT("%The1 eats %the2!", attacker, victim));
846 }
847
848 else if(flags & AF_EAT) {
849 playSound(NULL, "hit-crush"+pick123());
850 addMessage(XLAT("%The1 eats %the2!", attacker, victim));
851 }
852
853 else if(attacker == moLancer) {
854 playSound(NULL, "hit-rose");
855 addMessage(XLAT("%The1 pierces %the2!", attacker, victim));
856 }
857
858 else if(attacker == moEarthElemental) {
859 playSound(NULL, "hit-crush"+pick123());
860 addMessage(XLAT("%The1 punches %the2!", attacker, victim));
861 }
862
863 else if(attacker == moPlayer) {
864 if(flags & (AF_SWORD | AF_SWORD_INTO)) {
865 playSound(NULL, "hit-axe"+pick123());
866 addMessage(XLAT("You slash %the1.", victim));
867 if(victim == moGoblin)
868 achievement_gain_once("GOBLINSWORD");
869 }
870 else if(victim == moKrakenT || victim == moDragonTail || victim == moDragonHead) {
871 playSound(NULL, "hit-sword"+pick123());
872 addMessage(XLAT("You hit %the1.", victim)); // normal
873 }
874 else if(stun && victim == moVizier) {
875 playSound(NULL, "hit-sword"+pick123());
876 addMessage(XLAT("You hit %the1.", victim)); // normal
877 }
878 else if(stun) {
879 playSound(NULL, "hit-sword"+pick123());
880 addMessage(XLAT("You stun %the1.", victim)); // normal
881 }
882 else if(isNonliving(victim)) {
883 playSound(NULL, "hit-sword"+pick123());
884 addMessage(XLAT("You destroy %the1.", victim)); // normal
885 }
886 else if(flags & AF_STAB) {
887 playSound(NULL, "hit-axe"+pick123());
888 addMessage(XLAT("You stab %the1.", victim)); // normal
889 }
890 else if(flags & AF_APPROACH) {
891 playSound(NULL, "hit-sword"+pick123());
892 if(victim == moLancer)
893 addMessage(XLAT("You trick %the1.", victim)); // normal
894 else
895 addMessage(XLAT("You pierce %the1.", victim)); // normal
896 }
897 else if(!peace::on) {
898 playSound(NULL, "hit-sword"+pick123());
899 addMessage(XLAT("You kill %the1.", victim)); // normal
900 }
901 }
902
903 else {
904 if(victim == moKrakenT || victim == moDragonTail || victim == moDragonHead) {
905 playSound(NULL, "hit-crush"+pick123());
906 addMessage(XLAT("%The1 hits %the2.", attacker, victim)); // normal
907 }
908 else if(stun && victim == moVizier) {
909 playSound(NULL, "hit-sword"+pick123());
910 addMessage(XLAT("%The1 hits %the2.", attacker, victim)); // normal
911 }
912 else if(stun) {
913 playSound(NULL, "hit-sword"+pick123());
914 addMessage(XLAT("%The1 attacks %the2!", attacker, victim)); // normal
915 }
916 else if(isNonliving(victim)) {
917 playSound(NULL, "hit-sword"+pick123());
918 addMessage(XLAT("%The1 destroys %the2!", attacker, victim)); // normal
919 }
920 else if(flags & AF_STAB) {
921 playSound(NULL, "hit-axe"+pick123());
922 addMessage(XLAT("%The1 stabs %the2.", attacker, victim));
923 }
924 else if(flags & AF_APPROACH) {
925 playSound(NULL, "hit-sword"+pick123());
926 addMessage(XLAT("%The1 tricks %the2.", attacker, victim));
927 }
928 else {
929 playSound(NULL, "hit-sword"+pick123());
930 addMessage(XLAT("%The1 kills %the2!", attacker, victim));
931 }
932 }
933 }
934
notthateasy(eMonster m)935 EX bool notthateasy(eMonster m) {
936 return
937 isMultitile(m) || isStunnable(m) || m == moDraugr;
938 }
939
attackMonster(cell * c,flagtype flags,eMonster killer)940 EX bool attackMonster(cell *c, flagtype flags, eMonster killer) {
941
942 if((flags & AF_GETPLAYER) && isPlayerOn(c)) {
943 killThePlayerAt(killer, c, flags);
944 return true;
945 }
946
947 eMonster m = c->monst;
948 int tk = tkills();
949
950 int tkt = killtypes();
951
952 bool dostun = attackJustStuns(c, flags, killer);
953
954 if((flags & AF_BULL) && c->monst == moVizier && c->hitpoints > 1) {
955 dostun = true;
956 c->stuntime = 2;
957 }
958
959 bool eu = landUnlocked(laElementalWall);
960 bool tu = landUnlocked(laTrollheim);
961
962 if(flags & AF_MSG) fightmessage(m, killer, dostun, flags);
963 if(dostun)
964 stunMonster(c, killer, flags);
965 else
966 killMonster(c, killer, flags);
967
968 if(peace::on) return false;
969
970 int ntk = tkills();
971 int ntkt = killtypes();
972
973 if(tkt < R20 && ntkt >= R20 && in_full_game()) {
974 addMessage(XLAT("You hear a distant roar!"));
975 playSound(NULL, "message-roar");
976 }
977
978 if(tk == 0 && ntk > 0 && in_full_game() && !cheater) {
979 if(notthateasy(m))
980 addMessage(XLAT("Quite tough, for your first fight."));
981 else {
982 bool more = false;
983 forCellEx(c2, cwt.at) forCellEx(c3, c2)
984 if(c3->monst) more = true;
985 if(!more)
986 addMessage(XLAT("That was easy, but groups could be dangerous."));
987 }
988 }
989
990 if(tk < 10 && ntk >= 10 && in_full_game() && !big_unlock)
991 addMessage(XLAT("Good to know that your fighting skills serve you well in this strange world."));
992
993 if(tk < R100/2 && ntk >= R100/2 && in_full_game())
994 addMessage(XLAT("You wonder where all these monsters go, after their death..."));
995
996 if(tk < R100 && ntk >= R100 && in_full_game())
997 addMessage(XLAT("You feel that the souls of slain enemies pull you to the Graveyard..."));
998
999 if(!tu && landUnlocked(laTrollheim) && in_full_game()) {
1000 playSound(c, "message-troll");
1001 addMessage(XLAT("%The1 says, \"I die, but my clan in Trollheim will avenge me!\"", m));
1002 }
1003
1004 if(!eu && landUnlocked(laElementalWall) && in_full_game())
1005 addMessage(XLAT("After killing %the1, you feel able to reach the Elemental Planes!", m));
1006
1007 if(m == moVizier && c->monst != moVizier && kills[moVizier] == 1 && in_full_game()) {
1008 addMessage(XLAT("Hmm, he has been training in the Emerald Mine. Interesting..."));
1009 princess::forceMouse = true;
1010 }
1011
1012 if(m == moIvyRoot && ntk>tk)
1013 achievement_gain_once("IVYSLAYER");
1014
1015 return ntk > tk;
1016 }
1017
pushMonster(const movei & mi)1018 EX void pushMonster(const movei& mi) {
1019 if(mi.s->monst == moPair || isMagneticPole(mi.s)) {
1020 cell *other = mi.s->move(mi.s->mondir);
1021 int id = neighborId(mi.t, other);
1022 if(id == -1) {
1023 killMonster(mi.s, moPlayer);
1024 killMonster(other, moPlayer);
1025 return;
1026 }
1027 else {
1028 moveMonster(mi);
1029 if(mi.t->monst) {
1030 mi.t->mondir = id;
1031 other->mondir = mi.t->c.spin(id);
1032 }
1033 }
1034 }
1035 else
1036 moveMonster(mi);
1037 auto& cf = mi.s;
1038 auto& ct = mi.t;
1039 if(ct->monst == moBrownBug) {
1040 int t = snakelevel(ct) - snakelevel(cf);
1041 if(t > 0)
1042 ct->stuntime = min(ct->stuntime + 2 * t, 7);
1043 }
1044 if(isBull(ct->monst)) ct->monst = moRagingBull;
1045 }
1046
killFriendlyIvy()1047 EX void killFriendlyIvy() {
1048 forCellEx(c2, cwt.at) if(c2->monst == moFriendlyIvy)
1049 killMonster(c2, moPlayer, 0);
1050 }
1051
monsterPushable(cell * c2)1052 EX bool monsterPushable(cell *c2) {
1053 if(markOrb(itCurseWeakness) && (c2->stuntime < 2 || attackJustStuns(c2, 0, moPlayer))) return false;
1054 if(isMultitile(c2->monst)) return false;
1055 return (c2->monst != moFatGuard && !(isMetalBeast(c2->monst) && c2->stuntime < 2) && c2->monst != moTortoise && c2->monst != moTerraWarrior && c2->monst != moVizier && c2->monst != moWorldTurtle);
1056 }
1057
should_switchplace(cell * c1,cell * c2)1058 EX bool should_switchplace(cell *c1, cell *c2) {
1059 if(isPrincess(c2->monst) || among(c2->monst, moGolem, moIllusion, moMouse, moFriendlyGhost))
1060 return true;
1061
1062 if(peace::on) return c2->monst;
1063 return false;
1064 }
1065
switchplace_prevent(cell * c1,cell * c2,struct pcmove & m)1066 EX bool switchplace_prevent(cell *c1, cell *c2, struct pcmove& m) {
1067 if(!should_switchplace(c1, c2)) return false;
1068 if(peace::on && (isMultitile(c2->monst) || saved_tortoise_on(c2) || isDie(c2->monst))) {
1069 if(m.vmsg(miRESTRICTED)) addMessage(XLAT("Cannot switch places with %the1!", c2->monst));
1070 return true;
1071 }
1072 if(c1->monst && c1->monst != moFriendlyIvy) {
1073 if(m.vmsg(miRESTRICTED)) addMessage(XLAT("There is no room for %the1!", c2->monst));
1074 return true;
1075 }
1076 if(passable(c1, c2, P_ISFRIEND | (c2->monst == moTameBomberbird ? P_FLYING : 0))) return false;
1077 if(warningprotection_hit(c2->monst)) return true;
1078 return false;
1079 }
1080
handle_switchplaces(cell * c1,cell * c2,bool & switchplaces)1081 EX void handle_switchplaces(cell *c1, cell *c2, bool& switchplaces) {
1082 if(should_switchplace(c1, c2)) {
1083 bool pswitch = false;
1084 if(c2->monst == moMouse)
1085 princess::mouseSqueak(c2);
1086 else if(isPrincess(c2->monst)) {
1087 princess::line(c2);
1088 princess::move(match(c2, c1));
1089 }
1090 else
1091 pswitch = true;
1092 c1->hitpoints = c2->hitpoints;
1093 c1->stuntime = c2->stuntime;
1094 placeGolem(c1, c2, c2->monst);
1095 if(cwt.at->monst != moNone && pswitch)
1096 addMessage(XLAT("You switch places with %the1.", c2->monst));
1097 c2->monst = moNone;
1098 switchplaces = true;
1099 }
1100 }
1101
flashWouldKill(cell * c,flagtype extra)1102 EX bool flashWouldKill(cell *c, flagtype extra) {
1103 for(int t=0; t<c->type; t++) {
1104 cell *c2 = c->move(t);
1105 for(int u=0; u<c2->type; u++) {
1106 cell *c3 = c2->move(u);
1107 if(isWorm(c3)) continue; // immune to Flash
1108 if(isFriendly(c3)) continue; // player's allies and mounts don't count
1109 if(c3->monst == moEvilGolem) continue; // evil golems don't count
1110 if(c3 != c && (c3->monst || isPlayerOn(c3))) {
1111 bool b = canAttack(NULL, moWitchFlash, c3, c3->monst, AF_MAGIC | extra);
1112 if(b) return true;
1113 }
1114 }
1115 }
1116 return false;
1117 }
1118
gun_targets(cell * c)1119 EX vector<cell*> gun_targets(cell *c) {
1120 manual_celllister cl;
1121 vector<int> dists;
1122 cl.add(c); dists.push_back(0);
1123 for(int i=0; i<isize(dists); i++) {
1124 cell *c1 = cl.lst[i];
1125 if(dists[i] <= 2)
1126 forCellEx(c2, c1)
1127 if(passable(c2, c1, P_BULLET | P_FLYING | P_MONSTER))
1128 if(cl.add(c2)) dists.push_back(dists[i] + 1);
1129 }
1130 return cl.lst;
1131 }
1132
1133 EX void fallMonster(cell *c, flagtype flags IS(0)) {
1134 attackMonster(c, flags, moNone);
1135 }
1136
killHardcorePlayer(int id,flagtype flags)1137 EX void killHardcorePlayer(int id, flagtype flags) {
1138 charstyle& cs = getcs(id);
1139 cell *c = playerpos(id);
1140 if(flags & AF_FALL)
1141 fallingMonsterAnimation(c, moPlayer);
1142 else for(int i=0; i<6; i++) {
1143 drawParticle(c, cs.skincolor >> 8, 100);
1144 drawParticle(c, cs.haircolor >> 8, 125);
1145 if(cs.charid)
1146 drawParticle(c, cs.dresscolor >> 8, 150);
1147 else
1148 drawParticle(c, cs.skincolor >> 8, 150);
1149 if(cs.charid == 3)
1150 drawParticle(c, cs.dresscolor2 >> 8, 175);
1151 else
1152 drawParticle(c, cs.skincolor >> 8, 150);
1153 drawParticle(c, cs.swordcolor >> 8, 200);
1154 }
1155
1156 if(multi::players > 1 && multi::activePlayers() > 1) {
1157 multi::leaveGame(id);
1158 }
1159 else {
1160 canmove = false;
1161 achievement_final(true);
1162 }
1163 }
1164
killThePlayer(eMonster m,int id,flagtype flags)1165 EX void killThePlayer(eMonster m, int id, flagtype flags) {
1166 if(markOrb(itOrbShield)) return;
1167 if(shmup::on) {
1168 multi::cpid = id;
1169 shmup::killThePlayer(m);
1170 }
1171 else if(items[itOrbDomination] && playerpos(id)->monst) {
1172 addMessage(XLAT("%The1 tries to dismount you!", m));
1173 attackMonster(playerpos(id), AF_NORMAL, m);
1174 useupOrb(itOrbDomination, items[itOrbDomination]/2);
1175 }
1176 else if(items[itOrbShell] && !(flags & AF_EAT)) {
1177 addMessage(XLAT("%The1 attacks your shell!", m));
1178 useupOrb(itOrbShell, 10);
1179 if(items[itOrbShell] < 1) items[itOrbShell] = 1, orbused[itOrbShell] = true;
1180 }
1181 else if(hardcore) {
1182 addMessage(XLAT("You are killed by %the1!", m));
1183 killHardcorePlayer(id, flags);
1184 }
1185 else if(m == moLightningBolt && lastmovetype == lmAttack && isAlchAny(playerpos(id))) {
1186 addMessage(XLAT("You are killed by %the1!", m));
1187 addMessage(XLAT("Don't play with slime and electricity next time, okay?"));
1188 kills[moPlayer]++;
1189 items[itOrbSafety] = 0;
1190 }
1191 else {
1192 // printf("confused!\n");
1193 addMessage(XLAT("%The1 is confused!", m));
1194 }
1195 }
1196
killThePlayerAt(eMonster m,cell * c,flagtype flags)1197 EX void killThePlayerAt(eMonster m, cell *c, flagtype flags) {
1198 for(int i: player_indices())
1199 if(playerpos(i) == c)
1200 killThePlayer(m, i, flags);
1201 }
1202
1203 #if HDR
do_swords(movei mi,eMonster who,const T & f)1204 template<class T> void do_swords(movei mi, eMonster who, const T& f) {
1205 for(int bb=0; bb<2; bb++) if(who == moPlayer && sword::orbcount(bb)) {
1206 cell *sf = sword::pos(mi.s, sword::dir[multi::cpid], bb);
1207 cell *st = sword::pos(mi.t, sword::shift(mi, sword::dir[multi::cpid]), bb);
1208 f(st, bb);
1209 if(sf != st && !isNeighbor(sf,st)) {
1210 // also attack the in-transit cell
1211 if(S3 == 3) {
1212 forCellEx(sb, sf) if(isNeighbor(sb, st) && sb != mi.s && sb != mi.t) f(sb, bb);
1213 }
1214 else {
1215 forCellEx(sb, mi.s) if(isNeighbor(sb, st) && sb != mi.t) f(sb, bb);
1216 forCellEx(sb, mi.t) if(isNeighbor(sb, sf) && sb != mi.s) f(sb, bb);
1217 }
1218 }
1219 }
1220 }
1221 #endif
1222
1223 int lastdouble = -3;
1224
1225 EX void stabbingAttack(movei mi, eMonster who, int bonuskill IS(0)) {
1226 int numsh = 0, numflail = 0, numlance = 0, numslash = 0, numbb[2];
1227
1228 numbb[0] = numbb[1] = 0;
1229
1230 cell *mf = mi.s;
1231 cell *mt = mi.t;
1232 int backdir = mi.rev_dir_mirror();
1233
__anon0c8e29550302(cell *c, int bb) 1234 do_swords(mi, who, [&] (cell *c, int bb) { if(swordAttack(mt, who, c, bb)) numbb[bb]++, numslash++; });
1235
1236 for(int bb=0; bb<2; bb++) achievement_count("SLASH", numbb[bb], 0);
1237
1238 if(peace::on) return;
1239
1240 for(int t=0; t<mf->type; t++) {
1241 cell *c = mf->move(t);
1242 if(!c) continue;
1243
1244 bool stabthere = false, away = true;
1245 if(logical_adjacent(mt, who, c)) stabthere = true, away = false;
1246
1247 if(stabthere && c->wall == waExplosiveBarrel && markOrb(itOrbThorns))
1248 explodeBarrel(c);
1249
1250 if(stabthere && canAttack(mt,who,c,c->monst,AF_STAB)) {
1251 changes.ccell(c);
1252 if(c->monst != moHedge) {
1253 markOrb(itOrbThorns); if(who != moPlayer) markOrb(itOrbEmpathy);
1254 }
1255 eMonster m = c->monst;
1256 int k = tkills();
1257 if(attackMonster(c, AF_STAB | AF_MSG, who)) {
1258 spread_plague(mt, c, t, who);
1259 produceGhost(c, m, who);
1260 }
1261 if(tkills() > k) numsh++;
1262 }
1263
1264 if(away && c != mt && canAttack(mf,who,c,c->monst,AF_BACK)) {
1265 changes.ccell(c);
1266 if(c->monst == moVizier && c->hitpoints > 1) {
1267 fightmessage(c->monst, who, true, AF_BACK);
1268 c->hitpoints--;
1269 // c->stuntime = 1;
1270 }
1271 else {
1272 eMonster m = c->monst;
1273 if(c->monst != moFlailer) {
1274 playSound(NULL, "hit-sword"+pick123());
1275 fightmessage(c->monst, who, false, AF_BACK);
1276 }
1277 else {
1278 playSound(NULL, "hit-sword"+pick123());
1279 if(who != moPlayer)
1280 addMessage(XLAT("%The1 tricks %the2.", who, c->monst));
1281 else
1282 addMessage(XLAT("You trick %the1.", c->monst));
1283 }
1284 if(c->monst == moFlailer && isPrincess(who) && isUnarmed(who))
1285 achievement_gain_once("PRINCESS_PACIFIST");
1286
1287 if(attackMonster(c, 0, who)) {
1288 numflail++;
1289 spread_plague(mf, c, t, who);
1290 produceGhost(c, m, who);
1291 }
1292 }
1293 }
1294 }
1295
forCellIdEx(c,t,mt)1296 if(!isUnarmed(who)) forCellIdEx(c, t, mt) {
1297 if(!logical_adjacent(mt, who, c)) continue;
1298 eMonster mm = c->monst;
1299 int flag = AF_APPROACH;
1300 if(proper(mt, backdir) && anglestraight(mt, backdir, t)) flag |= AF_HORNS;
1301 if(canAttack(mt,who,c,c->monst, flag)) {
1302 changes.ccell(c);
1303 if(attackMonster(c, flag | AF_MSG, who)) numlance++;
1304 spread_plague(mt, c, t, who);
1305 produceGhost(c, mm, who);
1306 }
1307 }
1308
1309 if(who == moPlayer) {
1310 if(numsh) achievement_count("STAB", numsh, 0);
1311
1312 if(numlance && numflail && numsh) achievement_gain_once("MELEE3");
1313
1314 if(numlance + numflail + numsh + numslash + bonuskill >= 5) achievement_gain_once("MELEE5");
1315
1316 if(numsh == 2) {
1317 if(lastdouble == turncount-1) achievement_count("STAB", 4, 0);
1318 changes.value_set(lastdouble, turncount);
1319 }
1320 }
1321 }
1322
1323 }
1324