1 /*
2 * PROPRIETARY INFORMATION. This software is proprietary to POWDER
3 * Development, and is not to be reproduced, transmitted, or disclosed
4 * in any way without written permission.
5 *
6 * Produced by: Jeff Lait
7 *
8 * POWDER Development
9 *
10 * NAME: action.cpp ( POWDER Library, C++ )
11 *
12 * COMMENTS:
13 * This file is responsible for implementing all the ui-independent
14 * actions that a MOB can perform. These actions should encompass
15 * any action which the Player can perform. Their arguments should
16 * fully qualify the players choice (Ie: which item, what direction,
17 * etc).
18 *
19 * The theory is that if the actions are properly abstracted, it
20 * will be equally easy to perform them from the AI routines as
21 * from the User Interface routines.
22 *
23 * Note that most actions are implicitly stacked. actionClimb
24 * will resolve itself to actionClimbUp or actionClimbDown depending
25 * on the context.
26 */
27
28 #include "mygba.h"
29
30 #include <stdio.h>
31 #include <ctype.h>
32 #include "assert.h"
33 #include "artifact.h"
34 #include "creature.h"
35 #include "glbdef.h"
36 #include "rand.h"
37 #include "map.h"
38 #include "gfxengine.h"
39 #include "msg.h"
40 #include "grammar.h"
41 #include "item.h"
42 #include "itemstack.h"
43 #include "intrinsic.h"
44 #include "control.h"
45 #include "victory.h"
46 #include "piety.h"
47 #include "encyc_support.h"
48 #include "stylus.h"
49
50 void writeGlobalActionBar(bool useemptyslot);
51 void hideSideActionBar();
52
53 extern bool glbSuppressAutoClimb;
54 extern void attemptunlock();
55
56 //
57 // ACTIONS
58 //
59
60 bool
actionBump(int dx,int dy)61 MOB::actionBump(int dx, int dy)
62 {
63 int nx, ny;
64
65 nx = getX() + dx;
66 ny = getY() + dy;
67
68 // Check if a monster is here.
69 // Is it hostile? Kill!
70 // Is it friendly? Shove.
71 // Is it neutral? Kill!
72 MOB *mob;
73 ITEM *item;
74 mob = glbCurLevel->getMob(nx, ny);
75 if (mob == this)
76 mob = 0;
77
78 // This is very fucked up.
79 // It should be rewritten by someone not drunk.
80 // This has now been rewritten by someone not drunk. It doesn't
81 // necessarily work any better.
82 if (!mob && getSize() >= SIZE_GARGANTUAN)
83 {
84 if (dx)
85 {
86 if (dx < 0)
87 mob = glbCurLevel->getMob(nx, ny+1);
88 else
89 {
90 mob = glbCurLevel->getMob(nx+1, ny);
91 if (!mob)
92 mob = glbCurLevel->getMob(nx+1, ny+1);
93 }
94 }
95 else
96 {
97 if (dy < 0)
98 mob = glbCurLevel->getMob(nx+1, ny);
99 else
100 {
101 mob = glbCurLevel->getMob(nx, ny+1);
102 if (!mob)
103 mob = glbCurLevel->getMob(nx+1, ny+1);
104 }
105 }
106 if (mob == this)
107 mob = 0;
108 if (mob)
109 {
110 dx = mob->getX() - getX();
111 dy = mob->getY() - getY();
112 }
113 }
114 if (mob)
115 {
116 if (getAttitude(mob) == ATTITUDE_HOSTILE)
117 {
118 // Attack!
119 return actionAttack(dx, dy);
120 }
121 else if (getAttitude(mob) == ATTITUDE_NEUTRAL)
122 {
123 // Attack! THis is bloodthirsty game, after all.
124 // Only the avatar is bloodthirsty. Other people
125 // are nice and kind.
126 if (isAvatar())
127 return actionAttack(dx, dy);
128 else
129 return false; // No bump, no swap.
130 }
131 else if (getAttitude(mob) == ATTITUDE_FRIENDLY)
132 {
133 // Check if the friendship is reciprocated. If so,
134 // we can swap position.
135 if (mob->getAttitude(this) == ATTITUDE_FRIENDLY)
136 {
137 // swap.
138 // Okay, this is extradorinarily frustrating!
139 // Dancing grid bugs: No more!
140 // Only the avatar can swap by defualt (if he is
141 // polyied into a critter which is swappable, etc)
142 if (isAvatar())
143 {
144 // Check if we are to talk
145 if (mob->isTalkative())
146 return actionTalk(dx, dy);
147 else
148 return actionSwap(dx, dy);
149 }
150 else
151 return false;
152 }
153 else
154 {
155 // Nothing to do.
156 return false;
157 }
158 }
159 }
160
161 // Check if creature can physically move in that direction...
162 if (canMoveDelta(dx, dy, false))
163 {
164 // We can physically move that way. However, this is no
165 // guarantee of freedom.
166
167 // Check for item here.
168 item = glbCurLevel->getItem(nx, ny);
169 if (item && !item->isPassable())
170 {
171 // Can we push it?
172 // Boulder is in the way, try and push.
173 return actionPush(dx, dy);
174 }
175
176 // An empty, valid square.
177 // Will it hurt us? Are we smart enough to know that?
178
179 // All clear! Move with no embellishments.
180 return actionWalk(dx, dy);
181 }
182 else
183 {
184 // Find out why. Perhaps the creature has an alternative action?
185 if (nx < 0 || nx >= MAP_WIDTH ||
186 ny < 0 || ny >= MAP_HEIGHT)
187 {
188 // Simple enough reason, abort!
189 // Add a funny message depending on direction
190 if (isAvatar())
191 {
192 if (dx)
193 {
194 formatAndReport("%U <hear> the sound of someone mashing buttons.");
195 }
196 else
197 {
198 formatAndReport("Do not go there! You will break the backlight!");
199 }
200 }
201 return false;
202 }
203
204 SQUARE_NAMES tile;
205
206 tile = glbCurLevel->getTile(nx, ny);
207 switch (tile)
208 {
209 case SQUARE_BLOCKEDDOOR:
210 // Only avatar can open these
211 if (!isAvatar())
212 break;
213 // FALL THROUGH
214
215 case SQUARE_DOOR:
216 case SQUARE_MAGICDOOR:
217 // If we are smart enough, open it.
218 if (getSmarts() >= SMARTS_FOOL)
219 {
220 return actionOpen(dx, dy);
221 }
222 break;
223
224 default:
225 break;
226 }
227
228 // Check for the ability to dig...
229 if (hasIntrinsic(INTRINSIC_DIG))
230 {
231 return actionDig(dx, dy, 0, 1, false);
232 }
233
234 // Report nothing, just fail.
235 return false;
236 }
237 }
238
239 bool
actionTalk(int dx,int dy)240 MOB::actionTalk(int dx, int dy)
241 {
242 applyConfusion(dx, dy);
243
244 MOB *mob;
245 mob = glbCurLevel->getMob(getX()+dx, getY()+dy);
246
247 if (!mob)
248 {
249 formatAndReport("%U <talk> into empty air.");
250 return false;
251 }
252 if (mob == this)
253 {
254 formatAndReport("%U <talk> to %A.");
255 return false;
256 }
257 if (!mob->isTalkative())
258 {
259 formatAndReport("%U <talk> to %MA.", mob);
260 formatAndReport("%MU does not answer.", mob);
261 return false;
262 }
263
264 // Well, for now it means the village elder!
265 if (!isAvatar())
266 return false;
267
268 // No talking to the elder!
269 if (glbStressTest)
270 return false;
271
272 encyc_viewentry("TALK", TALK_ELDER);
273 if (gfx_yesnomenu("Leave for the feast?"))
274 {
275 glbVictoryScreen(true, 0, this, 0);
276 glbHasAlreadyWon = true;
277
278 // Remove the avatar and elder to the feast
279 glbCurLevel->unregisterMob(this);
280 glbCurLevel->unregisterMob(mob);
281
282 setAvatar(0);
283
284 delete mob;
285 delete this;
286
287 // required as we're dead
288 return false;
289 }
290 return false;
291 }
292
293 bool
actionBreathe(int dx,int dy)294 MOB::actionBreathe(int dx, int dy)
295 {
296 ATTACK_NAMES attack;
297 BUF buf;
298
299 applyConfusion(dx, dy);
300
301 attack = getBreathAttack();
302
303 if (attack == ATTACK_NONE)
304 {
305 if (hasIntrinsic(INTRINSIC_NOBREATH))
306 formatAndReport("%U <do> not need to breathe.");
307 else if (hasIntrinsic(INTRINSIC_STRANGLE))
308 formatAndReport("%U <be> unable to breathe.");
309 else if (hasIntrinsic(INTRINSIC_SUBMERGED))
310 formatAndReport("%U <choke>.");
311 else
312 formatAndReport("%U <breathe> heavily.");
313 return true;
314 }
315
316 if (!dx && !dy)
317 {
318 // Breathing on self.
319 formatAndReport("%U <check> %r breath.");
320 return true;
321 }
322
323 // Check if we are out of breath...
324 if (hasIntrinsic(INTRINSIC_TIRED))
325 {
326 formatAndReport("%U <belch>");
327 return true;
328 }
329
330 buf = formatToString("%U <breathe> %B1.",
331 this, 0, 0, 0,
332 getBreathSubstance(), 0);
333 reportMessage(buf);
334
335 // Breathing fire may kill ourselves, so we must set the
336 // intrinsic prior to sending the ray.
337 // After this, we want to time out for the breath delay time...
338 setTimedIntrinsic(this, INTRINSIC_TIRED,
339 rand_dice(glb_mobdefs[myDefinition].breathdelay));
340
341 // Now, run the attack with the breath...
342 MOBREF myself;
343
344 myself.setMob(this);
345
346 ourEffectAttack = getBreathAttack();
347 glbCurLevel->fireRay(getX(), getY(),
348 dx, dy,
349 getBreathRange(),
350 MOVE_STD_FLY,
351 glb_attackdefs[ourEffectAttack].reflect,
352 areaAttackCBStatic,
353 &myself);
354 return true;
355 }
356
357 bool
actionJump(int dx,int dy)358 MOB::actionJump(int dx, int dy)
359 {
360 int nx, ny, mx, my;
361 int tile;
362 bool leapattack = false;
363 bool singlestep = false;
364
365 if (!hasIntrinsic(INTRINSIC_JUMP))
366 {
367 formatAndReport("%U cannot jump very far.");
368 return false;
369 }
370
371 // If we are standing on liquid, we can only jump if we have water walk
372 // or we don't have enough traction.
373 tile = glbCurLevel->getTile(getX(), getY());
374 if (!(glb_squaredefs[tile].movetype & MOVE_WALK))
375 {
376 if (!hasIntrinsic(INTRINSIC_WATERWALK) ||
377 !(glb_squaredefs[tile].movetype & MOVE_SWIM))
378 {
379 // The ground isn't solid enough!
380 formatAndReport("%U <lack> traction to jump.");
381 return false;
382 }
383 }
384 else if (hasIntrinsic(INTRINSIC_SUBMERGED))
385 {
386 // We are buried alive.
387 formatAndReport("%U <lack> room to jump.");
388 return false;
389 }
390
391 if (glb_squaredefs[tile].isstars && !defn().spacewalk)
392 {
393 // Can't jump in space
394 formatAndReport("%U <fail> to brace yourself against vacuum.");
395 return false;
396 }
397
398 if (hasIntrinsic(INTRINSIC_INPIT))
399 {
400 // Jumping out of a pit only moves you a single square.
401 singlestep = true;
402 }
403
404 applyConfusion(dx, dy);
405
406 // Verify the square they will jump over is passable.
407 nx = getX() + dx;
408 ny = getY() + dy;
409 mx = nx;
410 my = ny;
411
412 tile = glbCurLevel->getTile(nx, ny);
413 if (!(glb_squaredefs[tile].movetype & MOVE_STD_FLY))
414 {
415 // You try and jump through a wall or something.
416 formatAndReport("%U <prepare> to jump, then %U <think> better of it.");
417 return false;
418 }
419
420 if (!singlestep)
421 {
422 nx = nx + dx;
423 ny = ny + dy;
424 }
425
426 // Note we want to not jump to unmapped squares. However, that is
427 // avatar only. The only time isLit does not suffice is if we
428 // are jumping blind. Hence we prohibit blind jumping.
429 if (!canMove(nx, ny, true) ||
430 !glbCurLevel->isLit(nx, ny) ||
431 hasIntrinsic(INTRINSIC_BLIND))
432 {
433 formatAndReport("%U <balk> at the leap!");
434 return false;
435 }
436
437 // Prohibit you jumping onto a creature if a creature is in the way.
438 if (glbCurLevel->getMob(nx, ny) && (glbCurLevel->getMob(nx, ny) != this))
439 {
440 // Check if we have the leap attack skill.
441 if (!hasSkill(SKILL_LEAPATTACK) ||
442 getSize() >= SIZE_GARGANTUAN ||
443 glbCurLevel->getMob(nx, ny)->getSize() >= SIZE_GARGANTUAN)
444 {
445 formatAndReport("%U <balk> at the leap!");
446 return false;
447 }
448
449 // Do a leap attack!
450 leapattack = true;
451 }
452
453 // Report that we are leaping.
454 formatAndReport("%U <leap>.");
455
456 // Clearly jumping should noise your feet.
457 makeEquipNoise(ITEMSLOT_FEET);
458
459
460 if (leapattack)
461 {
462 // Push the creature at our destination out of the way.
463 // First knock back the intermediate creature into our own square.
464 if (glbCurLevel->getMob(mx, my))
465 {
466 // Allow us to overwrite our own position as we'll
467 // be moving shortly anyways.
468 glbCurLevel->knockbackMob(mx, my, -dx, -dy, true, true);
469 }
470 // Next, knockback our target creature into the middle square.
471 glbCurLevel->knockbackMob(nx, ny, -dx, -dy, true);
472 }
473
474 // Move! We may die here, hence the test.
475 if (!move(nx, ny))
476 return true;
477
478 // Attack the vacated creature if still alive.
479 if (leapattack && glbCurLevel->getMob(mx, my))
480 {
481 // Note that because we attack backwards I think you can
482 // trigger a charge by running away and then leaping back...
483 // Most curious... I wonder if anyone will notice, and if they
484 // do, consider it a bug?
485 actionAttack(-dx, -dy, 1);
486 }
487
488 // Report what we stepped on...
489 // We recheck isAvatar() here as it will go false if this is deleted.
490 if (isAvatar())
491 {
492 glbCurLevel->describeSquare(getX(), getY(),
493 hasIntrinsic(INTRINSIC_BLIND),
494 false);
495 }
496
497 return true;
498 }
499
500 bool
actionWalk(int dx,int dy,bool spacewalk)501 MOB::actionWalk(int dx, int dy, bool spacewalk)
502 {
503 int nx, ny;
504 ITEM *item;
505 MOB *mob;
506
507 applyConfusion(dx, dy);
508
509 nx = getX() + dx;
510 ny = getY() + dy;
511
512 if (!canMoveDelta(dx, dy, true))
513 return false;
514
515 // If there is a monster, can't move.
516 mob = glbCurLevel->getMob(nx, ny);
517 if (mob && (mob != this))
518 {
519 // Check to see if we can swap
520 if (getAttitude(mob) == ATTITUDE_FRIENDLY)
521 {
522 // Check if the friendship is reciprocated. If so,
523 // we can swap position.
524 if (mob->getAttitude(this) == ATTITUDE_FRIENDLY)
525 {
526 // swap.
527 // Okay, this is extradorinarily frustrating!
528 // Dancing grid bugs: No more!
529 // Only the avatar can swap by defualt (if he is
530 // polyied into a critter which is swappable, etc)
531 if (isAvatar())
532 return actionSwap(dx, dy);
533 }
534 }
535 return false;
536 }
537
538 // If there is an item, and it is not steppable, no move.
539 item = glbCurLevel->getItem(nx, ny);
540 if (item && !item->isPassable())
541 return false;
542
543 // If we are on a star tile, we can only move if we can space
544 // walk
545 if (!spacewalk && !defn().spacewalk)
546 {
547 SQUARE_NAMES oldtile;
548
549 oldtile = glbCurLevel->getTile(getX(), getY());
550 if (glb_squaredefs[oldtile].isstars)
551 {
552 formatAndReport("%U <flail> against unyielding vacuum!");
553 return true;
554 }
555 }
556
557 // If we are in a pit, we first must climb out of it.
558 if (hasIntrinsic(INTRINSIC_INPIT))
559 {
560 // The user must first climb out of the pit. Thus, we try
561 // to climb:
562 return actionClimbUp();
563 }
564
565 if (hasIntrinsic(INTRINSIC_INTREE))
566 {
567 SQUARE_NAMES newtile;
568 // The user must first climb out of the tree. Thus, we try
569 // to climb:
570 // We are allowed to travel from tree to tree, however.
571 newtile = glbCurLevel->getTile(nx, ny);
572 if (newtile != SQUARE_FOREST &&
573 newtile != SQUARE_FORESTFIRE)
574 return actionClimbDown();
575 }
576
577 // If we are submerged, and our destination isn't something
578 // you can be submerged in, we must first climb out.
579 if (hasIntrinsic(INTRINSIC_SUBMERGED))
580 {
581 SQUARE_NAMES newtile;
582
583 newtile = glbCurLevel->getTile(nx, ny);
584 if (newtile != SQUARE_WATER &&
585 newtile != SQUARE_LAVA &&
586 newtile != SQUARE_ACID)
587 {
588 return actionClimbUp();
589 }
590 }
591
592 // Because we move, make any noise that stems from our
593 // footwear.
594 makeEquipNoise(ITEMSLOT_FEET);
595
596 // Move.
597 if (!move(nx, ny))
598 return true;
599
600 // Report what we stepped on...
601 if (isAvatar())
602 {
603 // Store the delta.
604 ourAvatarDx = dx;
605 ourAvatarDy = dy;
606 ourAvatarMoveOld = false;
607 glbCurLevel->describeSquare(getX(), getY(),
608 hasIntrinsic(INTRINSIC_BLIND),
609 false);
610 }
611
612 return true;
613 }
614
615 bool
actionPush(int dx,int dy)616 MOB::actionPush(int dx, int dy)
617 {
618 int nx, ny, px, py;
619 MOB *mob;
620 ITEM *item, *blockitem;
621 BUF buf;
622
623 applyConfusion(dx, dy);
624
625 nx = getX() + dx;
626 ny = getY() + dy;
627
628 mob = glbCurLevel->getMob(nx, ny);
629 if (mob)
630 {
631 // TODO: Aggro the victim?
632 // TODO: Strength/size check, and allow pushing into water?
633 formatAndReport("%U <push> %MU.", mob);
634 return true;
635 }
636
637 item = glbCurLevel->getItem(nx, ny);
638
639 if (item)
640 {
641 // Check to see if there is room.
642 px = nx + dx;
643 py = ny + dy;
644 if (px < 0 || px >= MAP_WIDTH ||
645 py < 0 || py >= MAP_HEIGHT ||
646 !glbCurLevel->canMove(px, py, MOVE_STD_FLY)
647 )
648 {
649 // Can't move as no room for the item.
650 formatAndReport("%U <strain>, but %IU <I:do> not budge.", item);
651 return true;
652 }
653
654 mob = glbCurLevel->getMob(px, py);
655 if (mob)
656 {
657 formatAndReportExplicit("%IU <I:be> blocked by %MU.", mob, item);
658 return true;
659 }
660
661 // Determine if it is blocked by another item.
662 blockitem = glbCurLevel->getItem(px, py);
663 if (blockitem && !blockitem->isPassable())
664 {
665 buf = MOB::formatToString("%U <be> blocked by %IU.", 0, item, 0, blockitem);
666 reportMessage(buf);
667 return true;
668 }
669
670 // We report prior to moving as moving may cause it to be deleted,
671 // for example if it falls into a hole.
672 formatAndReport("%U <push> %IU.", item);
673
674 // Move the item.
675 glbCurLevel->dropItem(item);
676 glbCurLevel->acquireItem(item, px, py, this);
677
678 return true;
679 }
680
681 // Reduced to pushing map features.
682 // TODO: Pushing doors, walls, etc.
683 formatAndReport("%U <push> thin air.");
684 return false;
685 }
686
687 bool
actionAttack(int dx,int dy,int multiplierbonus)688 MOB::actionAttack(int dx, int dy, int multiplierbonus)
689 {
690 int nx, ny;
691 MOB *mob, *thismob;
692 ITEM *weapon;
693 BUF buf;
694 int ox, oy, pdx, pdy;
695 bool didhit = false;
696 bool firstweapon = true;
697 bool improvised = true;
698 bool ischarge = false;
699
700 applyConfusionNoSelf(dx, dy);
701
702 ox = getX();
703 oy = getY();
704 nx = getX() + dx;
705 ny = getY() + dy;
706
707 mob = glbCurLevel->getMob(nx, ny);
708
709 if (!mob)
710 {
711 formatAndReport("%U <swing> wildly at the air!");
712 return true;
713 }
714
715 getLastMoveDirection(pdx, pdy);
716 // See if this is a charge
717 if (pdx == dx && pdy == dy && (dx || dy))
718 {
719 if (hasSkill(SKILL_CHARGE))
720 ischarge = true;
721 }
722
723 if (ischarge)
724 {
725 const char *chargemsg[] =
726 {
727 "%U <charge>!",
728 "%U <yell> a warcry!",
729 "%U <brace> your weapon!",
730 "%U <rush> into battle!",
731 0
732 };
733 formatAndReport(rand_string(chargemsg));
734
735 multiplierbonus++;
736 }
737
738 // Apply damage to mob.
739 const ATTACK_DEF *attack;
740
741 // Determining attack is simple. If we have anything in our right
742 // hand, we use that. Otherwise, we use our native attack
743 // from our definition file.
744 weapon = getEquippedItem(ITEMSLOT_RHAND);
745 if (weapon)
746 {
747 attack = weapon->getAttack();
748
749 // We have a chance of iding a weapon any time it is used.
750 if (isAvatar())
751 {
752 if (!weapon->isKnownEnchant())
753 {
754 int skill, chance;
755
756 skill = getWeaponSkillLevel(weapon, ATTACKSTYLE_MELEE);
757 chance = 5;
758 if (skill >= 1)
759 chance += 5;
760 if (skill >= 2)
761 chance += 10;
762
763 // About 20 hits to figure it out with 0 skill, 10 for
764 // one level skill, and 5 for 2 levels skill.
765 if (rand_chance(chance))
766 {
767 weapon->markEnchantKnown();
768 buf = MOB::formatToString("%U <know> %r %Iu better. ", this, 0, 0, weapon);
769 // This is important enough to be broadcast.
770 msg_announce(gram_capitalize(buf));
771 }
772 }
773 }
774 }
775 else
776 attack = &glb_attackdefs[glb_mobdefs[myDefinition].attack];
777
778 thismob = this;
779
780 while (attack)
781 {
782 mob->receiveAttack(attack, thismob, weapon, 0,
783 ATTACKSTYLE_MELEE, &didhit, multiplierbonus);
784
785 // Refetch the mob as it may have died, teleported, or what
786 // have you...
787 if (mob != glbCurLevel->getMob(nx, ny))
788 break;
789
790 // Refetch ourselves, as we may have died in the attack.
791 if (thismob != glbCurLevel->getMob(ox, oy))
792 break;
793
794 if (attack->nextattack != ATTACK_NONE)
795 attack = &glb_attackdefs[attack->nextattack];
796 else
797 attack = 0;
798
799 // If we have run out of attacks with this weapon, see if
800 // we should break it!
801 if (!attack && weapon && didhit &&
802 (thismob == glbCurLevel->getMob(ox, oy)))
803 {
804 bool didbreak;
805
806 didbreak = weapon->doBreak(10, this, this, glbCurLevel, nx, ny);
807
808 // Don't want to use special abilities of broken weapons :>
809 if (didbreak)
810 weapon = 0;
811 }
812
813 // If we have run out of attacks with a weapon, see if we
814 // should invoke its special skill.
815 if (!attack && weapon && didhit &&
816 (thismob == glbCurLevel->getMob(ox, oy)) &&
817 (mob == glbCurLevel->getMob(nx, ny)))
818 {
819 // 10% chance of power move.
820 if (skillProc(weapon->getSpecialSkill()) && hasSkill(weapon->getSpecialSkill()))
821 {
822 // Perform special attack.
823 switch (weapon->getSpecialSkill())
824 {
825 case SKILL_WEAPON_BLEEDINGWOUND:
826 if (!mob->isBloodless())
827 {
828 formatAndReport("%U <stab> deeply!");
829 mob->setTimedIntrinsic(thismob, INTRINSIC_BLEED, rand_range(4,6));
830 }
831 break;
832
833 case SKILL_WEAPON_DISARM:
834 {
835 weapon = mob->getEquippedItem(ITEMSLOT_RHAND);
836 if (weapon)
837 {
838 const char *slotname = mob->getSlotName(ITEMSLOT_RHAND);
839 formatAndReportExplicit(
840 "%U <knock> %IU from %Mr %B1!",
841 mob, weapon,
842 (slotname ? slotname : "grasp"));
843 weapon = mob->dropItem(weapon->getX(), weapon->getY());
844 UT_ASSERT(weapon != 0);
845 if (weapon)
846 {
847 glbCurLevel->acquireItem(weapon, mob->getX(), mob->getY(), this);
848
849 }
850 }
851 else
852 {
853 // Weaponless creaturs are thrown
854 // off balance.
855 formatAndReport("%U <trip> %MU.", mob);
856 mob->setTimedIntrinsic(thismob, INTRINSIC_OFFBALANCE, 3);
857 }
858 break;
859 }
860
861 case SKILL_WEAPON_KNOCKOUT:
862 // Only can knock unconscious conscious critters :>
863 if (mob->getSmarts() > SMARTS_NONE &&
864 !mob->hasIntrinsic(INTRINSIC_RESISTSLEEP) &&
865 !mob->hasIntrinsic(INTRINSIC_ASLEEP))
866 {
867 formatAndReport("%U <knock> %MU unconscious!", mob);
868 mob->setTimedIntrinsic(thismob, INTRINSIC_ASLEEP, rand_range(6,10));
869 }
870 break;
871
872 case SKILL_WEAPON_STUN:
873 // We confuse the poor critter.
874 if (mob->getSmarts() > SMARTS_NONE)
875 {
876 formatAndReport("%U <stun> %MU!", mob);
877 mob->setTimedIntrinsic(thismob, INTRINSIC_CONFUSED, rand_range(3,5));
878 }
879 break;
880
881 case SKILL_WEAPON_KNOCKBACK:
882 // Knock the target back
883 // Infinite mass is set as this is an attack.
884 glbCurLevel->knockbackMob(nx, ny, dx, dy, true);
885 break;
886
887 case SKILL_WEAPON_IMPALE:
888 {
889 // We drive our weapon straight through the
890 // creature, possibly impaling whoever is standing
891 // behind!
892 MOB *nextvictim = 0;
893 int nnx, nny;
894
895 nnx = nx + SIGN(dx);
896 nny = ny + SIGN(dy);
897
898 if (nnx >= 0 && nnx < MAP_WIDTH &&
899 nny >= 0 && nny < MAP_HEIGHT)
900 {
901 nextvictim = glbCurLevel->getMob(nnx, nny);
902 }
903
904 if (nextvictim)
905 {
906 formatAndReportExplicit("%U <drive> %r %Iu straight through %MU!", mob, weapon);
907
908 // Do a second attack..
909 actionAttack(nnx-ox, nny-oy);
910 }
911 break;
912 }
913
914 default:
915 break;
916 }
917 }
918 }
919
920 if (!attack && firstweapon &&
921 (thismob == glbCurLevel->getMob(ox, oy)))
922 {
923 // Try switching to our second weapon.
924 firstweapon = false;
925 didhit = false;
926
927 // Get the second weapon
928 weapon = getEquippedItem(ITEMSLOT_LHAND);
929 if (weapon)
930 {
931 // Set up our new attack.
932 attack = weapon->getAttack();
933 }
934
935 // See if we pass the chance.
936 if (!rand_chance(getSecondWeaponChance()))
937 attack = 0;
938
939 // We will now go on to the next attack..
940 }
941
942 // Check for the chance to proc on some artifact equipment.
943 if (!attack && !firstweapon && improvised &&
944 (thismob == glbCurLevel->getMob(ox, oy)))
945 {
946 int procchance = 5;
947 // See if we are eligible to proc.
948 if (hasSkill(SKILL_WEAPON_IMPROVISE))
949 {
950 procchance = 20;
951 }
952 improvised = false;
953
954 ITEMSLOT_NAMES slot;
955 ITEMSLOT_NAMES validchoice[NUM_ITEMSLOTS];
956 int numchoice = 0;
957 ITEM *item;
958
959 // The right way would be FOREACH_ITEMSLOT followed
960 // by getEquippedItem, but we happen to know we have
961 // O(n) search inside getEquippedItem, so might as well
962 // make this 8 times faster.
963 for (item = myInventory; item; item = item->getNext())
964 {
965 // Ignore not equipped
966 if (item->getX())
967 continue;
968
969 // Cast over slot number
970 slot = (ITEMSLOT_NAMES) item->getY();
971
972 switch (slot)
973 {
974 // The shield is only valid if it is a
975 // shield. Artifact shields can get double
976 // weapon proc by both this code path
977 // and the two handed code path.
978 case ITEMSLOT_LHAND:
979 {
980 if (!item->isShield())
981 break;
982 // FALL THROUGH
983 }
984 case ITEMSLOT_HEAD:
985 case ITEMSLOT_BODY:
986 case ITEMSLOT_RRING:
987 case ITEMSLOT_LRING:
988 case ITEMSLOT_FEET:
989 {
990 const ATTACK_DEF *attack;
991
992 // Check that the attack is something
993 // weird.
994 attack = item->getAttack();
995 if (attack == &glb_attackdefs[ATTACK_MISUSED] ||
996 attack == &glb_attackdefs[ATTACK_MISUSED_BUTWEAPON] ||
997 attack == &glb_attackdefs[ATTACK_MISTHROWN])
998 {
999 break;
1000 }
1001
1002 // Valid choice!
1003 validchoice[numchoice++] = slot;
1004 break;
1005 }
1006
1007 // I really can't think of a way that the
1008 // amulet can be used.
1009 case ITEMSLOT_AMULET:
1010 // The right hand is covered by normal combat.
1011 case ITEMSLOT_RHAND:
1012 // This ensures we cover all valid code paths
1013 case NUM_ITEMSLOTS:
1014 break;
1015 }
1016 }
1017
1018 // Given our base procchance of procchance,
1019 // and a number of choices of numchoice, we want a combined
1020 // choice total.
1021 // This is so that the probability of procing with both
1022 // a helmet and a shield is greater than with just one.
1023 // Our formula is:
1024 // 1 - (1 - procchance)^numchoice
1025 // This is slightly unfair as we don't allow two attacks
1026 // in a single round so you get a penalty for double equipping,
1027 // but I'm hoping it is close enough.
1028
1029 // First, we handle common & trivial cases.
1030 if (!numchoice)
1031 procchance = 0;
1032 else if (numchoice > 1) // Equality leaves it unchanged.
1033 {
1034 int negchance, iter, basechance;
1035
1036 negchance = 100 - procchance;
1037 // Make base 1024.
1038 negchance *= 1024;
1039 negchance += 50;
1040 negchance /= 100;
1041 basechance = negchance;
1042 // Repeatedly exponentiate and normalize the base.
1043 // Start at 1 as we already have a single chance.
1044 for (iter = 1; iter < numchoice; iter++)
1045 {
1046 negchance *= basechance;
1047 // Remove the 1024 base.
1048 negchance >>= 10;
1049 }
1050 // Back to base 100
1051 negchance *= 100;
1052 negchance += 512;
1053 negchance >>= 10;
1054 // And invert...
1055 procchance = 100 - negchance;
1056 }
1057
1058 // Choose a random value if possible
1059 if (rand_chance(procchance))
1060 {
1061 slot = validchoice[rand_choice(numchoice)];
1062 ITEM *weapon;
1063
1064 weapon = getEquippedItem(slot);
1065 UT_ASSERT(weapon != 0);
1066
1067 // Create some pretty text.
1068 switch (slot)
1069 {
1070 case ITEMSLOT_HEAD:
1071 formatAndReport("%U head <butt> with %r %Iu.", weapon);
1072 break;
1073 case ITEMSLOT_BODY:
1074 formatAndReport("%U body <slam> with %r %Iu.", weapon);
1075 break;
1076 case ITEMSLOT_LHAND:
1077 formatAndReport("%U shield <slam> with %r %Iu.", weapon);
1078 break;
1079 case ITEMSLOT_RRING:
1080 case ITEMSLOT_LRING:
1081 formatAndReport("%U <punch> with %r %Iu.", weapon);
1082 break;
1083 case ITEMSLOT_FEET:
1084 formatAndReport("%U <kick> with %r %Iu.", weapon);
1085 break;
1086
1087 // These should not occur.
1088 case ITEMSLOT_AMULET:
1089 case ITEMSLOT_RHAND:
1090 case NUM_ITEMSLOTS:
1091 UT_ASSERT(!"Invalid itemslot selected");
1092 break;
1093 }
1094
1095 // This triggers us to try once more with the
1096 // new weapon.
1097 attack = weapon->getAttack();
1098 }
1099 }
1100 }
1101
1102 return true;
1103 }
1104
1105 bool
actionSwap(int dx,int dy)1106 MOB::actionSwap(int dx, int dy)
1107 {
1108 int nx, ny;
1109 MOB *mob;
1110 bool doswap, taketurn;
1111
1112 applyConfusion(dx, dy);
1113
1114 nx = getX() + dx;
1115 ny = getY() + dy;
1116
1117 if (!canMoveDelta(dx, dy, false))
1118 {
1119 formatAndReport("%U cannot go there, so cannot swap places.");
1120 return false;
1121 }
1122
1123 if (hasIntrinsic(INTRINSIC_INPIT))
1124 {
1125 formatAndReport("%U cannot swap places while in a pit.");
1126 return false;
1127 }
1128 if (hasIntrinsic(INTRINSIC_INTREE))
1129 {
1130 formatAndReport("%U cannot swap places while up a tree.");
1131 return false;
1132 }
1133 if (hasIntrinsic(INTRINSIC_SUBMERGED))
1134 {
1135 formatAndReport("%U cannot swap places while submerged.");
1136 return false;
1137 }
1138 // Check to see if there is an item in the way. Note that
1139 // you can swap places with a boulder!
1140 if (!canMoveDelta(dx, dy, true))
1141 {
1142 ITEM *boulder;
1143
1144 boulder = glbCurLevel->getItem(nx, ny);
1145 if (boulder && boulder->getDefinition() == ITEM_BOULDER)
1146 {
1147 // Check to see if you have anything equipped.
1148 if (hasAnyEquippedItems())
1149 {
1150 formatAndReport("%U <be> wearing too much to swap places with %IU.", boulder);
1151 return false;
1152 }
1153
1154 // We can swap with the boulder.
1155 // Should be strength check?
1156 formatAndReport("%U <swap> positions with %IU.", boulder);
1157
1158 // Move the boulder.
1159 glbCurLevel->dropItem(boulder);
1160 glbCurLevel->acquireItem(boulder, getX(), getY(), this);
1161
1162 // Move the avatar.
1163 move(nx, ny);
1164
1165 // Report what we stepped on...
1166 // As both mob & this moved, we report if either of them is
1167 // the avatar.
1168 // If either died, the MOB::getAvatar should be reset to 0,
1169 // so neither of these paths should be taken.
1170 if (isAvatar())
1171 {
1172 glbCurLevel->describeSquare(getX(), getY(),
1173 hasIntrinsic(INTRINSIC_BLIND),
1174 false);
1175 }
1176 return true;
1177 }
1178 else if (boulder)
1179 formatAndReport("%U cannot swap places with %IU.", boulder);
1180 else
1181 formatAndReport("%U cannot go there, so cannot swap places.");
1182
1183 return false;
1184 }
1185
1186 // We can only swap with a monster.
1187 mob = glbCurLevel->getMob(nx, ny);
1188 if (!mob)
1189 {
1190 reportMessage("There is no one there to swap places with.");
1191 return false;
1192 }
1193
1194 // If either ourselves or the MOB is giant sized, we can't swap.
1195 if (getSize() >= SIZE_GARGANTUAN)
1196 {
1197 formatAndReport("%U <be> too big to swap places.");
1198 return false;
1199 }
1200 if (mob->getSize() >= SIZE_GARGANTUAN)
1201 {
1202 formatAndReport("%MU <M:be> too big to swap places.", mob);
1203 return false;
1204 }
1205
1206 // Check that the mob can move onto our square.
1207 if (!mob->canMove(getX(), getY(), true))
1208 {
1209 formatAndReport("%MU cannot step here!", mob);
1210 return false;
1211 }
1212
1213 // If either you or the mob is in a pit, you can't swap,
1214 // no free exits!
1215 // We check for yourself at the start so we don't allow swapping
1216 // with boulders to escape.
1217 if (mob->hasIntrinsic(INTRINSIC_INPIT))
1218 {
1219 formatAndReport("%U cannot swap places with %MU because %Mp <M:be> in a pit.", mob);
1220 return false;
1221 }
1222 if (mob->hasIntrinsic(INTRINSIC_INTREE))
1223 {
1224 formatAndReport("%U cannot swap places with %MU because %Mp <M:be> up a tree.", mob);
1225 return false;
1226 }
1227 if (mob->hasIntrinsic(INTRINSIC_SUBMERGED))
1228 {
1229 formatAndReport("%U cannot swap places with %MU because %Mp <M:be> submerged.", mob);
1230 return false;
1231 }
1232
1233 // If mob is friendly, swap for free.
1234 // If neutral, swap with strength check
1235 // If hostile, no go.
1236 doswap = false;
1237 taketurn = false;
1238 switch (mob->getAttitude(this))
1239 {
1240 case ATTITUDE_FRIENDLY:
1241 doswap = true;
1242 taketurn = true;
1243 formatAndReport("%U <swap> places with %MU.", mob);
1244 break;
1245 case ATTITUDE_NEUTRAL:
1246 taketurn = true;
1247 // TODO: Chance of becoming hostile?
1248 switch (strengthCheck(mob->getStrength()))
1249 {
1250 case -1:
1251 formatAndReport("%U cannot budge %MU.", mob);
1252 doswap = false;
1253 break;
1254 case 0:
1255 formatAndReport("%U cannot quite shift %MU.", mob);
1256 doswap = false;
1257 break;
1258 case 1:
1259 formatAndReport("%U <push> %MU out of the way.", mob);
1260 doswap = true;
1261 break;
1262 case 2:
1263 formatAndReport("%U <muscle> past %MU.", mob);
1264 doswap = true;
1265 break;
1266 }
1267 break;
1268
1269 case ATTITUDE_HOSTILE:
1270 taketurn = false;
1271 doswap = false;
1272 formatAndReport("%U cannot swap places with someone %p <be> fighting!");
1273 break;
1274
1275 case NUM_ATTITUDES:
1276 UT_ASSERT(!"UNKNOWN ATTITUDE!");
1277 taketurn = false;
1278 doswap = false;
1279 break;
1280 }
1281
1282 if (doswap)
1283 {
1284 // Swap...
1285 // Note either party may die here, but we don't use them
1286 // herein.
1287 mob->move(getX(), getY());
1288 move(nx, ny);
1289 // Report what we stepped on...
1290 // As both mob & this moved, we report if either of them is
1291 // the avatar.
1292 // If either died, the MOB::getAvatar should be reset to 0,
1293 // so neither of these paths should be taken.
1294 if (isAvatar())
1295 {
1296 glbCurLevel->describeSquare(getX(), getY(),
1297 hasIntrinsic(INTRINSIC_BLIND),
1298 false);
1299 }
1300 if (mob->isAvatar())
1301 {
1302 glbCurLevel->describeSquare(mob->getX(), mob->getY(),
1303 mob->hasIntrinsic(INTRINSIC_BLIND),
1304 false);
1305 }
1306 }
1307 return taketurn;
1308 }
1309
1310 bool
actionSleep()1311 MOB::actionSleep()
1312 {
1313 formatAndReport("%U <lie> down to sleep.");
1314
1315 if (hasIntrinsic(INTRINSIC_RESISTSLEEP))
1316 {
1317 formatAndReport("%U <stay> wide awake!");
1318 return true;
1319 }
1320
1321 // Set our sleep flag.
1322 setTimedIntrinsic(this, INTRINSIC_ASLEEP, 50);
1323
1324 return true;
1325 }
1326
1327 static MAGICTYPE_NAMES
getSudokuType(int idx,unsigned int seed)1328 getSudokuType(int idx, unsigned int seed)
1329 {
1330 int x, y, i, j;
1331 MAGICTYPE_NAMES type;
1332
1333 int pattern[4][4] =
1334 {
1335 { 0, 1, 2, 3 },
1336 { 2, 3, 0, 1 },
1337
1338 { 1, 0, 3, 2 },
1339 { 3, 2, 1, 0 }
1340 };
1341
1342 MAGICTYPE_NAMES reorder[4] =
1343 { MAGICTYPE_WAND, MAGICTYPE_SCROLL, MAGICTYPE_POTION, MAGICTYPE_RING };
1344
1345 // Reorder according to reorder pattern.
1346 for (i = 0; i < 4; i++)
1347 {
1348 j = i + (seed % (4 - i));
1349 seed = rand_wanginthash(seed);
1350 if (i != j)
1351 {
1352 type = reorder[i];
1353 reorder[i] = reorder[j];
1354 reorder[j] = type;
1355 }
1356 }
1357 for (y = 0; y < 4; y++)
1358 for (x = 0; x < 4; x++)
1359 pattern[y][x] = reorder[pattern[y][x]];
1360
1361 // Now, permute our pattern according to the seed.
1362 if (seed & 1)
1363 {
1364 for (y = 0; y < 4; y++)
1365 {
1366 i = pattern[y][0];
1367 pattern[y][0] = pattern[y][1];
1368 pattern[y][1] = i;
1369 }
1370 }
1371 if (seed & 2)
1372 {
1373 for (y = 0; y < 4; y++)
1374 {
1375 i = pattern[y][2];
1376 pattern[y][2] = pattern[y][3];
1377 pattern[y][3] = i;
1378 }
1379 }
1380 if (seed & 4)
1381 {
1382 for (x = 0; x < 4; x++)
1383 {
1384 i = pattern[0][x];
1385 pattern[0][x] = pattern[1][x];
1386 pattern[1][x] = i;
1387 }
1388 }
1389 if (seed & 8)
1390 {
1391 for (x = 0; x < 4; x++)
1392 {
1393 i = pattern[2][x];
1394 pattern[2][x] = pattern[3][x];
1395 pattern[3][x] = i;
1396 }
1397 }
1398
1399 // We should now permute entire squares, but I am drunk now.
1400 // But, I just realized that swapping squares == flipping on x
1401 // plus two column flips.
1402 x = idx & 3;
1403 y = idx >> 2;
1404
1405 // Flip/flop
1406 if (seed & 16)
1407 x = 3 - x;
1408 if (seed & 32)
1409 y = 3 - y;
1410 if (seed & 64)
1411 {
1412 i = x;
1413 x = y;
1414 y = i;
1415 }
1416
1417 return (MAGICTYPE_NAMES) pattern[y][x];
1418 }
1419
1420 static int
consumeSudokuKeys(ITEMSTACK & stack,int idx,long seed,int & eaten)1421 consumeSudokuKeys(ITEMSTACK &stack, int idx, long seed, int &eaten)
1422 {
1423 int i;
1424 int valid = 0;
1425 ITEM *item;
1426 MAGICTYPE_NAMES type;
1427 BUF buf;
1428
1429 type = getSudokuType(idx, seed);
1430
1431 for (i = 0; i < stack.entries(); i++)
1432 {
1433 item = stack(i);
1434 if (item->getMagicType() != type)
1435 {
1436 item->formatAndReport("%U <disappear> in a puff of smoke.");
1437 glbCurLevel->dropItem(item);
1438 delete item;
1439 eaten++;
1440 }
1441 else
1442 {
1443 // A valid item, vibrate and set true
1444 item->formatAndReport("%U <vibrate>.");
1445 valid = 1;
1446 }
1447 }
1448
1449 return valid;
1450 }
1451
1452 bool
actionMagicDoorEffect(int x,int y)1453 MOB::actionMagicDoorEffect(int x, int y)
1454 {
1455 // Search the level for the sudoku keys (ie, altars...)
1456 int numaltar = 0, numused, eaten;
1457 int altarx[16], altary[16];
1458 int mx, my;
1459 int idx;
1460 ITEMSTACK items;
1461
1462 for (my = 0; my < MAP_HEIGHT; my++)
1463 {
1464 if (numaltar >= 16)
1465 break;
1466 for (mx = 0; mx < MAP_WIDTH; mx++)
1467 {
1468 if (numaltar >= 16)
1469 break;
1470 if (glbCurLevel->getTile(mx, my) == SQUARE_FLOORALTAR)
1471 {
1472 altarx[numaltar] = mx;
1473 altary[numaltar] = my;
1474 numaltar++;
1475 }
1476 }
1477 }
1478
1479 // If we don't have 16 altars, nothing to do.
1480 if (numaltar != 16)
1481 {
1482 encyc_viewentry("MAGICDOOR", MAGICDOOR_NOTENOUGHDOORS);
1483 return false;
1484 }
1485
1486 // Inspect the contents of each altar.
1487 numused = 0;
1488 eaten = 0;
1489 for (idx = 0; idx < 16; idx++)
1490 {
1491 items.clear();
1492 glbCurLevel->getItemStack(items, altarx[idx], altary[idx]);
1493
1494 numused += consumeSudokuKeys(items, idx, glbCurLevel->getSeed(), eaten);
1495 }
1496
1497 if (numused < 16 && eaten)
1498 {
1499 encyc_viewentry("MAGICDOOR", MAGICDOOR_INSUFFICIENTITEMS);
1500 return true;
1501 }
1502 else if (numused < 16)
1503 {
1504 encyc_viewentry("MAGICDOOR", MAGICDOOR_NOITEMS);
1505 return true;
1506 }
1507
1508 // Door opens!
1509 glbCurLevel->setTile(x, y, SQUARE_OPENDOOR);
1510 encyc_viewentry("MAGICDOOR", MAGICDOOR_SUCCESS);
1511
1512 return true;
1513 }
1514
1515 bool
actionOpen(int dx,int dy)1516 MOB::actionOpen(int dx, int dy)
1517 {
1518 SQUARE_NAMES tile;
1519 bool consumed = false, doopen = false;
1520
1521 applyConfusion(dx, dy);
1522
1523 // Now check to see if the square itself is openable.
1524 tile = glbCurLevel->getTile(getX() + dx, getY() + dy);
1525
1526 switch (tile)
1527 {
1528 case SQUARE_BLOCKEDDOOR:
1529 if (!isAvatar())
1530 {
1531 formatAndReport("$U <find> the door latched.");
1532 break;
1533 }
1534 // FALL THROUGH
1535 case SQUARE_DOOR:
1536 // Check if we were strong enough.
1537 switch (strengthCheck(STRENGTH_HUMAN))
1538 {
1539 case -1:
1540 formatAndReport("%U cannot budge the door!");
1541 break;
1542
1543 case 0:
1544 formatAndReport("%U cannot quite shift the door.");
1545 break;
1546
1547 case 1:
1548 doopen = true;
1549 formatAndReport("%U <force> the door open.");
1550 break;
1551
1552 case 2:
1553 doopen = true;
1554 formatAndReport("%U <open> the door smoothly.");
1555 break;
1556 }
1557
1558 if (doopen)
1559 {
1560 glbCurLevel->setTile(getX() + dx, getY() + dy, SQUARE_OPENDOOR);
1561 }
1562 consumed = true;
1563 break;
1564 case SQUARE_OPENDOOR:
1565 // Can't reopen...
1566 reportMessage("The door is already open.");
1567 break;
1568 case SQUARE_MAGICDOOR:
1569 if (!isAvatar())
1570 {
1571 // Non-avatar's can't trigger this door.
1572 return false;
1573 }
1574 consumed = actionMagicDoorEffect(getX() + dx, getY() + dy);
1575 break;
1576 default:
1577 reportMessage("That is not openable!");
1578 break;
1579 }
1580 return consumed;
1581 }
1582
1583 bool
actionClose(int dx,int dy)1584 MOB::actionClose(int dx, int dy)
1585 {
1586 SQUARE_NAMES tile;
1587 bool consumed = false, doclose = false;
1588 MOB *mob;
1589 ITEM *item;
1590
1591 applyConfusion(dx, dy);
1592
1593 // Now check to see if the square itself is openable.
1594 tile = glbCurLevel->getTile(getX() + dx, getY() + dy);
1595
1596 switch (tile)
1597 {
1598 case SQUARE_OPENDOOR:
1599 // Check if there is a mob in the way.
1600 mob = glbCurLevel->getMob(getX() + dx, getY() + dy);
1601 if (mob)
1602 {
1603 formatAndReport("%MU <M:hold> the door open!", mob);
1604 return true;
1605 }
1606
1607 // Check to see if there is an item.
1608 item = glbCurLevel->getItem(getX() + dx, getY() + dy);
1609 if (item)
1610 {
1611 formatAndReport("The door is blocked by %IU.", item);
1612 return true;
1613 }
1614
1615 // Check if we were strong enough.
1616 // Doors are much easier to close than open.
1617 switch (strengthCheck(STRENGTH_WEAK))
1618 {
1619 case -1:
1620 formatAndReport("%U cannot budge the door!");
1621 break;
1622
1623 case 0:
1624 formatAndReport("%U cannot quite shift the door!");
1625 break;
1626
1627 case 1:
1628 doclose = true;
1629 formatAndReport("%U <close> the door.");
1630 break;
1631
1632 case 2:
1633 doclose = true;
1634 formatAndReport("%U <slam> the door shut.");
1635 break;
1636 }
1637
1638 if (doclose)
1639 {
1640 glbCurLevel->setTile(getX() + dx, getY() + dy, SQUARE_DOOR);
1641 }
1642 consumed = true;
1643 break;
1644 case SQUARE_BLOCKEDDOOR:
1645 case SQUARE_DOOR:
1646 // Can't reclose...
1647 reportMessage("The door is already closed.");
1648 break;
1649 default:
1650 reportMessage("That is not closeable!");
1651 break;
1652 }
1653 return consumed;
1654 }
1655
1656 extern bool glbHasJustSearched;
1657
1658 bool
actionSearch()1659 MOB::actionSearch()
1660 {
1661 int dx, dy;
1662 bool found = false;
1663 ITEM *item;
1664 bool shouldreveal = false;
1665
1666 if (isAvatar())
1667 {
1668 glbHasJustSearched = true;
1669 }
1670
1671 // We always map everything around us.
1672 // No need for LOS here.
1673 // No need for LOS?
1674 // Really?
1675 // Even if an NPC decides to invoke actionSearch?
1676 // Because of, perhaps, wielding a ring of searching?
1677 // Past-Jeff was stupid.
1678 // The easy answer is to just make this test if we are the avatar,
1679 // but I know those people at armoredtactis will be reading this change,
1680 // so I might as well throw in an exception whereby your pets will
1681 // be allowed to map the level for you.
1682 // Not that is particularly useful since they either follow or stay,
1683 // but some glorious future they may get an explore command?
1684 if (isAvatar() || hasCommonMaster(MOB::getAvatar()))
1685 shouldreveal = true;
1686
1687 if (shouldreveal)
1688 glbCurLevel->markMapped(getX(), getY(), 1, 1, false);
1689
1690 // Now, examine every square around us...
1691 for (dy = -1; dy <= 1; dy++)
1692 {
1693 if (getY() + dy < 0 || getY() + dy >= MAP_HEIGHT)
1694 continue;
1695 for (dx = -1; dx <= 1; dx++)
1696 {
1697 if (getX() + dx < 0 || getX() + dx >= MAP_WIDTH)
1698 continue;
1699
1700 // We want to mark any items on this square as
1701 // known.
1702 // For speed, we just mark the top item as mapped.
1703 // Really, we should mark the entire stack...
1704 if (shouldreveal)
1705 {
1706 item = glbCurLevel->getItem(getX()+dx, getY()+dy);
1707 if (item)
1708 item->markMapped();
1709 }
1710
1711 // Don't examine own square, may kill us!
1712 if (!dx && !dy)
1713 continue;
1714
1715 found |= searchSquare(getX() + dx, getY() + dy, true);
1716 }
1717 }
1718
1719 // Last to search is our own square, in case we die.
1720 found |= searchSquare(getX(), getY(), true);
1721
1722 if (!found)
1723 {
1724 // I think this is too noisy so have axed it.
1725 // NOTE: Not safe to enable it, because we may be dead now!
1726 #if 0
1727 formatAndReport("%U <search> and find nothing.");
1728 #endif
1729 }
1730
1731 // Consume...
1732 return true;
1733 }
1734
1735 bool
actionClimb()1736 MOB::actionClimb()
1737 {
1738 SQUARE_NAMES tile;
1739 int trap;
1740
1741 tile = glbCurLevel->getTile(getX(), getY());
1742
1743 trap = glb_squaredefs[tile].trap;
1744 switch ((TRAP_NAMES) trap)
1745 {
1746 case TRAP_TELEPORT:
1747 return actionClimbDown();
1748
1749 case TRAP_NONE:
1750 case TRAP_POISONVENT:
1751 case TRAP_SMOKEVENT:
1752 case NUM_TRAPS:
1753 break;
1754
1755 case TRAP_HOLE:
1756 return actionClimbDown();
1757
1758 case TRAP_PIT:
1759 case TRAP_SPIKEDPIT:
1760 if (hasIntrinsic(INTRINSIC_INPIT))
1761 return actionClimbUp();
1762 else
1763 return actionClimbDown();
1764 }
1765
1766 // If we are submerged, it is straight forward: climbup.
1767 if (hasIntrinsic(INTRINSIC_SUBMERGED))
1768 return actionClimbUp();
1769
1770 switch (tile)
1771 {
1772 case SQUARE_LADDERUP:
1773 return actionClimbUp();
1774
1775 case SQUARE_LADDERDOWN:
1776 return actionClimbDown();
1777
1778 case SQUARE_DIMDOOR:
1779 return actionClimbDown();
1780
1781 // If you are in the relevant liquid, you will have already
1782 // gone to climb up.
1783 case SQUARE_LAVA:
1784 return actionClimbDown();
1785 case SQUARE_WATER:
1786 return actionClimbDown();
1787 case SQUARE_ACID:
1788 return actionClimbDown();
1789
1790 case SQUARE_FOREST:
1791 case SQUARE_FORESTFIRE:
1792 if (hasIntrinsic(INTRINSIC_INTREE))
1793 return actionClimbDown();
1794 else
1795 return actionClimbUp();
1796
1797 default:
1798 formatAndReport("%U <try> to climb thin air!");
1799 return false;
1800 }
1801 }
1802
1803 bool
actionClimbUp()1804 MOB::actionClimbUp()
1805 {
1806 SQUARE_NAMES tile;
1807 int trap;
1808 BUF buf;
1809
1810 tile = glbCurLevel->getTile(getX(), getY());
1811
1812 trap = glb_squaredefs[tile].trap;
1813
1814 switch ((TRAP_NAMES) trap)
1815 {
1816 case TRAP_HOLE:
1817 case TRAP_NONE:
1818 case TRAP_TELEPORT:
1819 case TRAP_POISONVENT:
1820 case TRAP_SMOKEVENT:
1821 case NUM_TRAPS:
1822 break;
1823
1824 case TRAP_PIT:
1825 case TRAP_SPIKEDPIT:
1826 {
1827 if (hasIntrinsic(INTRINSIC_INPIT))
1828 {
1829 if (canFly())
1830 {
1831 // Trivially escape the pit.
1832 formatAndReport("%U <fly> out of the pit.");
1833 clearIntrinsic(INTRINSIC_INPIT);
1834 return true;
1835 }
1836 // Try to climb out...
1837 makeEquipNoise(ITEMSLOT_FEET);
1838 if (rand_chance(50))
1839 {
1840 // Success.
1841 formatAndReport("%U <climb> out of the pit.");
1842 clearIntrinsic(INTRINSIC_INPIT);
1843 }
1844 else
1845 {
1846 formatAndReport("%U <slip> and <fall> back into the pit.");
1847 }
1848 return true;
1849 }
1850 else
1851 {
1852 // You are already out of the pit!
1853 // Will fall through to thin air.
1854 }
1855 }
1856 }
1857
1858 if (hasIntrinsic(INTRINSIC_SUBMERGED))
1859 {
1860 // Try to swim to the surface...
1861 int chance;
1862 BUF success;
1863 bool haswaterwalk;
1864
1865 haswaterwalk = hasIntrinsic(INTRINSIC_WATERWALK);
1866
1867 switch (tile)
1868 {
1869 case SQUARE_LAVA:
1870 chance = 90; // Easy to get out of lava.
1871 buf = formatToString("%U <struggle> in the lava.",
1872 this, 0, 0, 0);
1873 if (haswaterwalk)
1874 success = formatToString("%U <step> out of the lava.",
1875 this, 0, 0, 0);
1876 else
1877 success = formatToString("%U <pull> out of the lava.",
1878 this, 0, 0, 0);
1879 if (haswaterwalk)
1880 chance = 100;
1881 break;
1882 case SQUARE_WATER:
1883 chance = 50; // Harder to keep on top of.
1884 buf = formatToString("%U <flounder> in the water.",
1885 this, 0, 0, 0);
1886 if (haswaterwalk)
1887 success = formatToString("%U <step> to the surface.",
1888 this, 0, 0, 0);
1889 else
1890 success = formatToString("%U <swim> to the surface.",
1891 this, 0, 0, 0);
1892 if (haswaterwalk)
1893 chance = 100;
1894 break;
1895 case SQUARE_ACID:
1896 chance = 50; // Harder to keep on top of.
1897 buf = formatToString("%U <flounder> in the acid.",
1898 this, 0, 0, 0);
1899 if (haswaterwalk)
1900 success = formatToString("%U <step> to the surface.",
1901 this, 0, 0, 0);
1902 else
1903 success = formatToString("%U <swim> to the surface.",
1904 this, 0, 0, 0);
1905 if (haswaterwalk)
1906 chance = 100;
1907 break;
1908 case SQUARE_ICE:
1909 chance = 0;
1910 buf = formatToString("%U <pound> on the ice in vain.",
1911 this, 0, 0, 0);
1912 break;
1913 default:
1914 // Buried alive.
1915 chance = 0;
1916 buf = formatToString("The enclosing rock fastly imprisons %U.",
1917 this, 0, 0, 0);
1918 break;
1919 }
1920
1921 if (chance && rand_chance(chance))
1922 {
1923 reportMessage(success);
1924 clearIntrinsic(INTRINSIC_SUBMERGED);
1925 }
1926 else
1927 {
1928 reportMessage(buf);
1929 }
1930
1931 return true;
1932 }
1933
1934 switch (tile)
1935 {
1936 case SQUARE_LADDERUP:
1937 {
1938 // Climb to the given map...
1939 MAP *nextmap;
1940 int x, y, ox, oy;
1941
1942 if (!canFly())
1943 makeEquipNoise(ITEMSLOT_FEET);
1944
1945 // Special case of climbing top stair case...
1946 if (glbCurLevel->getDepth() == 1)
1947 {
1948 if (isAvatar())
1949 {
1950 // Determine if we have the heart of baezl'bub...
1951 bool hasheart = false;
1952
1953 hasheart = hasItem(ITEM_BLACKHEART);
1954
1955 if (hasheart)
1956 {
1957 formatAndReport("%U <return> to the sweet air of the surface world.");
1958 }
1959 else
1960 {
1961 formatAndReport("Until %U <have> slain Baezl'bub and "
1962 "have his black heart, the surface "
1963 "world cannot help %p.");
1964 return true;
1965 }
1966 }
1967 else
1968 {
1969 // Some critter is trying to escape, they think
1970 // better of it.
1971 formatAndReport("%U <recoil> from the light of the surface world.");
1972 return true;
1973 }
1974 }
1975
1976 nextmap = glbCurLevel->getMapUp();
1977 if (!nextmap)
1978 {
1979 formatAndReport("%U <bonk> %r head on an invisible barrier.");
1980 return true;
1981 }
1982
1983 // See if there is room...
1984 if (!nextmap->findTile(SQUARE_LADDERDOWN, x, y) ||
1985 !nextmap->findCloseTile(x, y, getMoveType()))
1986 {
1987 formatAndReport("%U cannot find room at the end of the ladder.");
1988 return true;
1989 }
1990
1991 if (canFly())
1992 formatAndReport("%U <fly> up the ladder.");
1993 else
1994 formatAndReport("%U <climb> the ladder.");
1995
1996 ox = getX();
1997 oy = getY();
1998
1999 // Move to it...
2000 glbCurLevel->unregisterMob(this);
2001 if (!move(x, y, true))
2002 return true;
2003 nextmap->registerMob(this);
2004
2005 glbCurLevel->chaseMobOnStaircase(SQUARE_LADDERDOWN,
2006 ox, oy, this, nextmap);
2007
2008 if (isAvatar())
2009 {
2010 glbSuppressAutoClimb = true;
2011
2012 MAP::changeCurrentLevel(nextmap);
2013 // Need to rebuild our screen! Refreshing will be handled
2014 // by the message wait code.
2015 gfx_scrollcenter(getX(), getY());
2016 glbCurLevel->buildFOV(getX(), getY(), 7, 5);
2017 if (!hasIntrinsic(INTRINSIC_BLIND))
2018 glbCurLevel->markMapped(getX(), getY(), 7, 5);
2019
2020 glbCurLevel->describeSquare(getX(), getY(),
2021 hasIntrinsic(INTRINSIC_BLIND),
2022 false);
2023 }
2024
2025 return true;
2026 }
2027
2028 case SQUARE_FOREST:
2029 case SQUARE_FORESTFIRE:
2030 {
2031 if (!hasIntrinsic(INTRINSIC_INTREE))
2032 {
2033 if (canFly())
2034 formatAndReport("%U <fly> up into the tree.");
2035 else
2036 formatAndReport("%U <climb> up the tree.");
2037
2038 setIntrinsic(INTRINSIC_INTREE);
2039
2040 return true;
2041 }
2042 else
2043 {
2044 formatAndReport("%U <be> already at the top of the tree.");
2045 return false;
2046 }
2047 }
2048
2049 default:
2050 if (canFly())
2051 formatAndReport("%U <fly> up and hit the roof.");
2052 else
2053 formatAndReport("%U <try> to climb up thin air!");
2054 return false;
2055 }
2056 }
2057
2058 bool
actionClimbDown()2059 MOB::actionClimbDown()
2060 {
2061 SQUARE_NAMES tile;
2062 TRAP_NAMES trap;
2063
2064 tile = glbCurLevel->getTile(getX(), getY());
2065
2066 trap = (TRAP_NAMES) glb_squaredefs[tile].trap;
2067
2068 switch (trap)
2069 {
2070 case TRAP_NONE:
2071 case TRAP_POISONVENT:
2072 case TRAP_SMOKEVENT:
2073 case NUM_TRAPS:
2074 break;
2075
2076 case TRAP_TELEPORT:
2077 {
2078 // Trigger the teleport trap.
2079 if (canFly())
2080 formatAndReport("%U <fly> into the teleporter.");
2081 else
2082 formatAndReport("%U <climb> into the teleporter.");
2083 return actionTeleport();
2084 }
2085
2086 case TRAP_HOLE:
2087 {
2088 // Climb to the given map...
2089 MAP *nextmap;
2090 int x, y;
2091
2092 makeEquipNoise(ITEMSLOT_FEET);
2093 nextmap = glbCurLevel->getMapDown();
2094 if (!nextmap || !nextmap->isUnlocked())
2095 {
2096 formatAndReport("%U <be> stopped by an invisible barrier.");
2097 return true;
2098 }
2099
2100 // See if there is room...
2101 // We just go somewhere random (but moveable!)
2102 if (!nextmap->findRandomLoc(x, y, getMoveType(), false,
2103 getSize() >= SIZE_GARGANTUAN,
2104 false, false, false, true))
2105 {
2106 formatAndReport("%U <find> the hole blocked.");
2107 return true;
2108 }
2109
2110 // Move to it...
2111 glbCurLevel->unregisterMob(this);
2112 if (!move(x, y, true))
2113 return true;
2114 nextmap->registerMob(this);
2115
2116 if (canFly())
2117 formatAndReport("%U <fly> down the hole.");
2118 else
2119 formatAndReport("%U <jump> into the hole.");
2120
2121 if (isAvatar())
2122 {
2123 MAP::changeCurrentLevel(nextmap);
2124 glbCurLevel->describeSquare(getX(), getY(),
2125 hasIntrinsic(INTRINSIC_BLIND),
2126 false);
2127 }
2128
2129 return true;
2130 }
2131
2132 case TRAP_PIT:
2133 case TRAP_SPIKEDPIT:
2134 {
2135 if (hasIntrinsic(INTRINSIC_INPIT))
2136 {
2137 // Already inside the pit, can't dig deeper.
2138 // Will fall through to scramble on ground.
2139 }
2140 else
2141 {
2142 makeEquipNoise(ITEMSLOT_FEET);
2143 // You can safely descend into the pit with
2144 // no chance of damage.
2145 formatAndReport("%U carefully <climb> into the pit.");
2146 setIntrinsic(INTRINSIC_INPIT);
2147
2148 // Report what we stepped on...
2149 if (isAvatar())
2150 {
2151 glbCurLevel->describeSquare(getX(), getY(),
2152 hasIntrinsic(INTRINSIC_BLIND),
2153 false);
2154 }
2155
2156 return true;
2157 }
2158 }
2159 }
2160
2161 switch (tile)
2162 {
2163 case SQUARE_LADDERDOWN:
2164 {
2165 // Climb to the given map...
2166 MAP *nextmap;
2167 int x, y;
2168 int ox, oy;
2169
2170 makeEquipNoise(ITEMSLOT_FEET);
2171 nextmap = glbCurLevel->getMapDown();
2172 if (!nextmap)
2173 {
2174 formatAndReport("%U <be> stopped by an invisible barrier.");
2175 return true;
2176 }
2177
2178 if (!nextmap->isUnlocked())
2179 {
2180 formatAndReport("%U <find> that the ladder is locked.");
2181
2182 attemptunlock();
2183 return false;
2184 }
2185
2186 // See if there is room...
2187 if (!nextmap->findTile(SQUARE_LADDERUP, x, y) ||
2188 !nextmap->findCloseTile(x, y, getMoveType()))
2189 {
2190 formatAndReport("%U cannot find room at the end of the ladder.");
2191 return true;
2192 }
2193
2194 if (canFly())
2195 formatAndReport("%U <fly> down the ladder.");
2196 else
2197 formatAndReport("%U <climb> the ladder.");
2198
2199 ox = getX();
2200 oy = getY();
2201
2202 // Move to it...
2203 glbCurLevel->unregisterMob(this);
2204 if (!move(x, y, true))
2205 return true;
2206 nextmap->registerMob(this);
2207
2208 glbCurLevel->chaseMobOnStaircase(SQUARE_LADDERUP,
2209 ox, oy, this, nextmap);
2210
2211 if (isAvatar())
2212 {
2213 glbSuppressAutoClimb = true;
2214
2215 MAP::changeCurrentLevel(nextmap);
2216 // Need to rebuild our screen! Refreshing will be handled
2217 // by the message wait code.
2218 gfx_scrollcenter(getX(), getY());
2219 glbCurLevel->buildFOV(getX(), getY(), 7, 5);
2220 if (!hasIntrinsic(INTRINSIC_BLIND))
2221 glbCurLevel->markMapped(getX(), getY(), 7, 5);
2222
2223 glbCurLevel->describeSquare(getX(), getY(),
2224 hasIntrinsic(INTRINSIC_BLIND),
2225 false);
2226 }
2227
2228 return true;
2229 }
2230
2231 case SQUARE_DIMDOOR:
2232 {
2233 // Climb to the portal map.
2234 MAP *nextmap;
2235 int x, y;
2236 int ox, oy;
2237
2238 // Make sure you have the dimdoor key!
2239 if (!hasItem(ITEM_YRUNE))
2240 {
2241 formatAndReport("The portal refuses to open without the key.");
2242 return true;
2243 }
2244
2245 makeEquipNoise(ITEMSLOT_FEET);
2246 nextmap = glbCurLevel->getMapBranch();
2247 if (!nextmap || !nextmap->isUnlocked())
2248 {
2249 formatAndReport("%U <recoil> from a seeming infinite abyss.");
2250 return true;
2251 }
2252
2253 // See if there is room...
2254 if (!nextmap->findTile(SQUARE_DIMDOOR, x, y) ||
2255 !nextmap->findCloseTile(x, y, getMoveType()))
2256 {
2257 formatAndReport("%U cannot find room at the end of the portal.");
2258 return true;
2259 }
2260
2261 if (canFly())
2262 formatAndReport("%U <fly> into the portal.");
2263 else
2264 formatAndReport("%U <climb> into the portal.");
2265
2266 ox = getX();
2267 oy = getY();
2268
2269 // Move to it...
2270 glbCurLevel->unregisterMob(this);
2271 if (!move(x, y, true))
2272 return true;
2273 nextmap->registerMob(this);
2274
2275 glbCurLevel->chaseMobOnStaircase(SQUARE_DIMDOOR,
2276 ox, oy, this, nextmap);
2277
2278 if (isAvatar())
2279 {
2280 MAP::changeCurrentLevel(nextmap);
2281 // Need to rebuild our screen! Refreshing will be handled
2282 // by the message wait code.
2283 gfx_scrollcenter(getX(), getY());
2284 glbCurLevel->buildFOV(getX(), getY(), 7, 5);
2285 if (!hasIntrinsic(INTRINSIC_BLIND))
2286 glbCurLevel->markMapped(getX(), getY(), 7, 5);
2287
2288 glbCurLevel->describeSquare(getX(), getY(),
2289 hasIntrinsic(INTRINSIC_BLIND),
2290 false);
2291 BRANCH_NAMES branch = glbCurLevel->branchName();
2292 formatAndReport(glb_branchdefs[branch].welcome);
2293 }
2294
2295 return true;
2296 }
2297
2298 case SQUARE_LAVA:
2299 {
2300 formatAndReport("%U <dive> into the lava.");
2301 setIntrinsic(INTRINSIC_SUBMERGED);
2302 // Report what we stepped on...
2303 if (isAvatar())
2304 {
2305 glbCurLevel->describeSquare(getX(), getY(),
2306 hasIntrinsic(INTRINSIC_BLIND),
2307 false);
2308 }
2309
2310 return true;
2311 }
2312
2313 case SQUARE_WATER:
2314 {
2315 formatAndReport("%U <dive> into the water.");
2316 setIntrinsic(INTRINSIC_SUBMERGED);
2317 // Report what we stepped on...
2318 if (isAvatar())
2319 {
2320 glbCurLevel->describeSquare(getX(), getY(),
2321 hasIntrinsic(INTRINSIC_BLIND),
2322 false);
2323 }
2324
2325 return true;
2326 }
2327
2328 case SQUARE_ACID:
2329 {
2330 formatAndReport("%U <dive> into the acid.");
2331 setIntrinsic(INTRINSIC_SUBMERGED);
2332 // Report what we stepped on...
2333 if (isAvatar())
2334 {
2335 glbCurLevel->describeSquare(getX(), getY(),
2336 hasIntrinsic(INTRINSIC_BLIND),
2337 false);
2338 }
2339
2340 return true;
2341 }
2342
2343 case SQUARE_FOREST:
2344 case SQUARE_FORESTFIRE:
2345 if (hasIntrinsic(INTRINSIC_INTREE))
2346 {
2347 if (canFly())
2348 formatAndReport("%U <fly> down from the tree.");
2349 else
2350 formatAndReport("%U <climb> down the tree.");
2351
2352 clearIntrinsic(INTRINSIC_INTREE);
2353
2354 return true;
2355 }
2356 else
2357 {
2358 // FALLTHROUGH
2359 }
2360
2361 default:
2362 formatAndReport("%U <scrabble> at the floor!");
2363 return false;
2364 }
2365 }
2366
2367 bool
actionPickUp(ITEM * item,ITEM ** newitem)2368 MOB::actionPickUp(ITEM *item, ITEM **newitem)
2369 {
2370 BUF buf;
2371 bool submerged;
2372
2373 submerged = hasIntrinsic(INTRINSIC_INPIT) ||
2374 hasIntrinsic(INTRINSIC_SUBMERGED);
2375
2376 if (hasIntrinsic(INTRINSIC_INTREE))
2377 {
2378 // Items never stay in tree branches, so people in tree
2379 // can't get anything.
2380 formatAndReport("%U cannot reach %IU.", item);
2381 return false;
2382 }
2383
2384 if (!item)
2385 {
2386 formatAndReport("%U <grope> on the floor foolishly!");
2387 return false;
2388 }
2389
2390 // Verify the item is in square.
2391 if (item->getX() != getX() || item->getY() != getY())
2392 {
2393 formatAndReport("%U cannot reach %IU.", item);
2394 return false;
2395 }
2396
2397 // Verify we can reach it.
2398 if (item->isBelowGrade() != submerged)
2399 {
2400 formatAndReport("%U cannot reach %IU.", item);
2401 return false;
2402 }
2403
2404 glbCurLevel->dropItem(item);
2405
2406 // Try to acquire the item ourselves....
2407 // We must get the item name first as the item may be deleted by
2408 // acquireItem.
2409 BUF itemname = item->getName();
2410
2411 if (acquireItem(item, newitem))
2412 {
2413 // Success!
2414 buf = formatToString("%U <pick> up %B1.", this, 0, 0, 0,
2415 itemname.buffer());
2416 reportMessage(buf);
2417 }
2418 else
2419 {
2420 // Drop back on the floor.
2421 glbCurLevel->acquireItem(item, getX(), getY(), this, newitem);
2422 buf = formatToString("%U <try> to pick up %B1 and <fail>.",
2423 this, 0, 0, 0,
2424 itemname.buffer());
2425 reportMessage(buf);
2426
2427 return false;
2428 }
2429
2430 return true;
2431 }
2432
2433 bool
actionDrop(int ix,int iy)2434 MOB::actionDrop(int ix, int iy)
2435 {
2436 ITEM *item, *drop;
2437
2438 item = getItem(ix, iy);
2439
2440 // Verify there is an item.
2441 if (!item)
2442 {
2443 formatAndReport("%U <drop> nothing.");
2444 return true;
2445 }
2446
2447 // Verify the item is not equipped.
2448 if (!ix)
2449 {
2450 formatAndReport("%U cannot drop %IU because it equipped.", item);
2451 return true;
2452 }
2453
2454 // Drop the item...
2455 drop = dropItem(ix, iy);
2456 UT_ASSERT(drop == item);
2457
2458 // Report the item dropped before acquiring to the ground,
2459 // this prevents out of order messages if it then sinks.
2460 formatAndReport("%U <drop> %IU.", item);
2461
2462 // Mark the item below grade if we are.
2463 if (hasIntrinsic(INTRINSIC_INPIT) ||
2464 hasIntrinsic(INTRINSIC_SUBMERGED))
2465 {
2466 drop->markBelowGrade(true);
2467 }
2468
2469 // Check if item falls to the ground
2470 if (hasIntrinsic(INTRINSIC_INTREE))
2471 {
2472 formatAndReport("%IU <I:drop> to the ground.", item);
2473 // Try to break the item
2474 if (item->doBreak(10, this, 0, glbCurLevel, getX(), getY()))
2475 return true;
2476 }
2477
2478 glbCurLevel->acquireItem(drop, getX(), getY(), this);
2479 return true;
2480 }
2481
2482 static int
glb_itemsortcompare(const void * v1,const void * v2)2483 glb_itemsortcompare(const void *v1, const void *v2)
2484 {
2485 const ITEM *i1, *i2;
2486
2487 i1 = *((const ITEM **) v1);
2488 i2 = *((const ITEM **) v2);
2489
2490 // Favorite items always go first.
2491 if (i1->isFavorite() && !i2->isFavorite())
2492 return -1;
2493 if (!i1->isFavorite() && i2->isFavorite())
2494 return 1;
2495
2496 if (i1->getItemType() < i2->getItemType())
2497 return -1;
2498 if (i1->getItemType() > i2->getItemType())
2499 return 1;
2500
2501 // Itemtypes are equal, sort by exact item.
2502 if (i1->getDefinition() < i2->getDefinition())
2503 return -1;
2504 if (i1->getDefinition() > i2->getDefinition())
2505 return 1;
2506
2507 int comp;
2508
2509 // Compare our item names.
2510 comp = i1->getName().strcmp(i2->getName());
2511 if (comp != 0)
2512 return comp;
2513 return 0;
2514 }
2515
2516 bool
actionSort()2517 MOB::actionSort()
2518 {
2519 formatAndReport("%U <rummage> through %r backpack.");
2520
2521 // Sort all the items...
2522 ITEM *item;
2523 int numitems, i, j;
2524
2525 // Count how many items we have so we can make a flat array.
2526 numitems = 0;
2527 for (item = myInventory; item; item = item->getNext())
2528 {
2529 // Only counts if item is not equipped, as we don't sort
2530 // equipped items.
2531 if (item->getX() == 0)
2532 continue;
2533
2534 numitems++;
2535 }
2536
2537 // This is a trivially true path.
2538 if (!numitems)
2539 return false;
2540
2541 ITEM **flatarray;
2542
2543 // Create a flat array of all the items.
2544 flatarray = new ITEM *[numitems];
2545
2546 i = 0;
2547 for (item = myInventory; item; item = item->getNext())
2548 {
2549 // Only counts if item is not equipped, as we don't sort
2550 // equipped items.
2551 if (item->getX() == 0)
2552 continue;
2553
2554 flatarray[i] = item;
2555 i++;
2556 }
2557
2558 // Now, we sort the item pointers according to our rules:
2559 qsort(flatarray, numitems, sizeof(ITEM *), glb_itemsortcompare);
2560
2561 // Now merge adjacent items. We rely on identical items
2562 // getting sorted together. This likely fails for id vs non id
2563 // cases.
2564 // We thus escape this by continuing to look for merge potentials
2565 // until we get the first item with a different definition.
2566 #if 1
2567 for (i = 0; i < numitems; i++)
2568 {
2569 // If this entry is null, we already merged it
2570 if (!flatarray[i])
2571 continue;
2572
2573 for (j = i+1; j < numitems; j++)
2574 {
2575 // Skip already merged items.
2576 if (!flatarray[j])
2577 continue;
2578
2579 // If the items have different definitions, we must be done.
2580 if (flatarray[i]->getDefinition() != flatarray[j]->getDefinition())
2581 break;
2582
2583 // Check to see if we can merge item j onto item i.
2584 if (flatarray[i]->canMerge(flatarray[j]))
2585 {
2586 ITEM *drop;
2587
2588 drop = dropItem(flatarray[j]->getX(), flatarray[j]->getY());
2589 if (drop != flatarray[j])
2590 {
2591 UT_ASSERT(!"Drop failed!");
2592 break;
2593 }
2594 else
2595 {
2596 flatarray[i]->mergeStack(flatarray[j]);
2597 delete flatarray[j];
2598 flatarray[j] = 0;
2599 }
2600 }
2601 }
2602 }
2603 #endif
2604
2605 // Now, using the flat array, assign new item positions for everything.
2606 j = 0;
2607 for (i = 0; i < numitems; i++)
2608 {
2609 if (flatarray[i])
2610 {
2611 flatarray[i]->setPos( (j / MOBINV_HEIGHT) + 1,
2612 j % MOBINV_HEIGHT );
2613 j++;
2614 }
2615 }
2616
2617 delete [] flatarray;
2618
2619 return false;
2620 }
2621
2622 bool
actionEat(int ix,int iy)2623 MOB::actionEat(int ix, int iy)
2624 {
2625 ITEM *item, *drop, *newitem = 0, *stackitem = 0;
2626 BUF buf;
2627 bool doeat = false, triedeat = false;
2628 bool skippoison = false;
2629 int foodval = 0;
2630
2631 item = getItem(ix, iy);
2632
2633 // Verify there is an item.
2634 if (!item)
2635 {
2636 formatAndReport("%U <eat> thin air.");
2637 return false;
2638 }
2639
2640 // Verify the item is not equipped.
2641 if (!ix)
2642 {
2643 // Exception - you can eat from left and right hands.
2644 if (iy != ITEMSLOT_LHAND && iy != ITEMSLOT_RHAND)
2645 {
2646 formatAndReport("%U cannot eat %IU because it is equipped.", item);
2647 return false;
2648 }
2649 }
2650
2651 if (getHungerLevel() >= HUNGER_FULL)
2652 {
2653 formatAndReport("%U <be> too full to eat anything more.");
2654 return false;
2655 }
2656
2657 if (item->getStackCount() > 1)
2658 {
2659 stackitem = item;
2660 item = stackitem->splitStack(1);
2661 }
2662
2663 switch (item->getMagicType())
2664 {
2665 case MAGICTYPE_POTION:
2666 {
2667 int magicclass;
2668
2669 doeat = true;
2670 triedeat = true;
2671
2672 // Magic potions have a food value of 10.
2673 foodval = 10;
2674
2675 newitem = ITEM::create(ITEM_BOTTLE, false, true);
2676
2677 // Print the drink message....
2678 formatAndReport("%U <drink> %IU.", item);
2679
2680 // Get the magic type...
2681 magicclass = item->getMagicClass();
2682 UT_ASSERT(item->getMagicType() == MAGICTYPE_POTION);
2683
2684 // If it is ided on drinking, make it so!
2685 // If it is ided on drinking, also id it if the avatar
2686 // can see the drinker.
2687 if (glb_potiondefs[magicclass].autoid &&
2688 (isAvatar() ||
2689 (MOB::getAvatar() && MOB::getAvatar()->canSense(this))))
2690 item->markIdentified();
2691
2692 // Apply any bonus effects due to it being an artifact.
2693 if (item->isArtifact())
2694 {
2695 const ARTIFACT *art;
2696 const char *intrinsics;
2697 int min_range, max_range;
2698
2699 if (item->isBlessed())
2700 max_range = 2000;
2701 else
2702 max_range = 500;
2703 if (item->isCursed())
2704 min_range = 2;
2705 else
2706 min_range = 300;
2707
2708 art = item->getArtifact();
2709 intrinsics = art->intrinsics;
2710 while (intrinsics && *intrinsics)
2711 {
2712 setTimedIntrinsic(this,
2713 (INTRINSIC_NAMES) *intrinsics,
2714 rand_range(min_range, max_range));
2715 intrinsics++;
2716 }
2717 }
2718
2719 // Apply the effect:
2720 switch ((POTION_NAMES) magicclass)
2721 {
2722 case POTION_HEAL:
2723 {
2724 int heal;
2725
2726 if (item->isBlessed())
2727 heal = rand_dice(1, 10, 10);
2728 else if (item->isCursed())
2729 heal = rand_dice(1, 10, 0);
2730 else
2731 heal = rand_dice(1, 20, 0);
2732
2733 if (receiveHeal(heal, this))
2734 {
2735 formatAndReport("The %Iu <I:heal> %U.", item);
2736
2737 if (isAvatar() ||
2738 (MOB::getAvatar() &&
2739 MOB::getAvatar()->canSense(this)))
2740 item->markIdentified();
2741 }
2742 else
2743 {
2744 // Grant bonus magic...
2745 if (item->isBlessed())
2746 heal = rand_dice(3, 2, -2);
2747 else if (item->isCursed())
2748 heal = 1;
2749 else
2750 heal = rand_dice(2, 2, -1);
2751
2752 incrementMaxHP(heal);
2753
2754 formatAndReport("%U <feel> more robust.");
2755 if (isAvatar() ||
2756 (MOB::getAvatar() &&
2757 MOB::getAvatar()->canSense(this)))
2758 item->markIdentified();
2759 }
2760 break;
2761 }
2762
2763 case POTION_ACID:
2764 if (!receiveDamage(ATTACK_ACIDPOTION, 0, item, 0,
2765 ATTACKSTYLE_MISC))
2766 {
2767 // Do nothing more if we die.
2768 // TODO: This will then drop the bottle we ate???
2769 if (stackitem)
2770 delete item;
2771 return true;
2772 }
2773 break;
2774
2775 case POTION_BLIND:
2776 {
2777 int turns;
2778
2779 // You get 3d20 turns of blindness.
2780 if (item->isBlessed())
2781 turns = rand_dice(3, 10, 30);
2782 else if (item->isCursed())
2783 turns = rand_dice(3, 10, 0);
2784 else
2785 turns = rand_dice(3, 20, 0);
2786
2787 // Determine if we notice anything...
2788 if (isAvatar() &&
2789 !hasIntrinsic(INTRINSIC_BLIND))
2790 {
2791 item->markIdentified();
2792 }
2793
2794 setTimedIntrinsic(this, INTRINSIC_BLIND, turns);
2795 }
2796 break;
2797
2798 case POTION_POISON:
2799 {
2800 int turns;
2801 POISON_NAMES poison;
2802
2803 turns = rand_dice(4, 5, 0);
2804 if (item->isBlessed())
2805 poison = POISON_STRONG;
2806 else if (item->isCursed())
2807 poison = POISON_MILD;
2808 else
2809 poison = POISON_NORMAL;
2810
2811 setTimedIntrinsic(this, (INTRINSIC_NAMES)
2812 glb_poisondefs[poison].intrinsic,
2813 turns);
2814 break;
2815 }
2816
2817 case POTION_CURE:
2818 {
2819 bool add_resist, cured;
2820
2821 add_resist = item->isBlessed();
2822 cured = receiveCure();
2823
2824 if (add_resist)
2825 {
2826 // We grant poison resistance for a 5 + 1d5 turns.
2827 formatAndReport("%R metabolism stabilizes.");
2828 setTimedIntrinsic(this, INTRINSIC_RESISTPOISON,
2829 rand_dice(1, 5, 6));
2830 }
2831 if (cured)
2832 {
2833 formatAndReport("The poison is expunged from %R veins.");
2834 }
2835
2836 // Check for identification
2837 if (cured || add_resist)
2838 {
2839 if (isAvatar() ||
2840 (MOB::getAvatar() &&
2841 MOB::getAvatar()->canSense(this)))
2842 item->markIdentified();
2843 }
2844 else
2845 {
2846 formatAndReport("Nothing happens.");
2847 }
2848 break;
2849 }
2850
2851 case POTION_GREEKFIRE:
2852 {
2853 formatAndReport("The liquid catches on fire as it leaves the bottle! Burning liquid covers %U!");
2854
2855 if (!receiveDamage(ATTACK_GREEKFIREPOTION, 0, item, 0,
2856 ATTACKSTYLE_MISC))
2857 {
2858 // Do nothing more if we die.
2859 // TODO: Does this get rid of the bottle?
2860 if (stackitem)
2861 delete item;
2862 return true;
2863 }
2864 break;
2865 }
2866
2867 case POTION_ENLIGHTENMENT:
2868 {
2869 formatAndReport("%U <gain> insight.");
2870
2871 if (isAvatar())
2872 {
2873 glbShowIntrinsic(this);
2874 }
2875 break;
2876 }
2877
2878 case POTION_SMOKE:
2879 {
2880 formatAndReport("Smoke billows out of %IU.", item);
2881
2882 ITEM::setZapper(this);
2883
2884 // Identification is handled by the grenadecallback.
2885 glbCurLevel->fireBall(getX(), getY(), 1, true,
2886 ITEM::grenadeCallbackStatic,
2887 item);
2888 break;
2889 }
2890
2891 case POTION_MANA:
2892 {
2893 int mana;
2894
2895 if (item->isBlessed())
2896 mana = rand_dice(3, 10, 30);
2897 else if (item->isCursed())
2898 mana = rand_dice(3, 10, 0);
2899 else
2900 mana = rand_dice(3, 20, 0);
2901
2902 if (receiveMana(mana, this))
2903 {
2904 formatAndReport("The %Iu <I:recharge> %U.", item);
2905
2906 if (isAvatar() ||
2907 (MOB::getAvatar() &&
2908 MOB::getAvatar()->canSense(this)))
2909 item->markIdentified();
2910 }
2911 else
2912 {
2913 // Grant bonus magic...
2914 if (item->isBlessed())
2915 mana = rand_dice(3, 2, -2);
2916 else if (item->isCursed())
2917 mana = 1;
2918 else
2919 mana = rand_dice(2, 2, -1);
2920
2921 incrementMaxMP(mana);
2922
2923 formatAndReport("%U <receive> a boost.");
2924 if (isAvatar() ||
2925 (MOB::getAvatar() &&
2926 MOB::getAvatar()->canSense(this)))
2927 item->markIdentified();
2928 }
2929 break;
2930 }
2931
2932 case NUM_POTIONS:
2933 UT_ASSERT(!"Unknown potion class!");
2934 break;
2935 }
2936
2937 break;
2938 }
2939
2940 case MAGICTYPE_SPELLBOOK:
2941 {
2942 SPELLBOOK_NAMES book = (SPELLBOOK_NAMES) item->getMagicClass();
2943
2944 const u8 *spells;
2945 int min_range, max_range;
2946
2947 if (!canDigest(item) || glb_itemdefs[item->getDefinition()].isquest)
2948 {
2949 // If you can't actually eat the book, you shouldn't
2950 // gain the bonus.
2951 break;
2952 }
2953
2954 if (item->isBlessed())
2955 max_range = 200;
2956 else
2957 max_range = 80;
2958 if (item->isCursed())
2959 min_range = 2;
2960 else
2961 min_range = 30;
2962
2963 min_range *= item->getCharges();
2964 max_range *= item->getCharges();
2965
2966 spells = (const u8 *) glb_spellbookdefs[book].spells;
2967 while (spells && *spells)
2968 {
2969 setTimedIntrinsic(this,
2970 (INTRINSIC_NAMES) glb_spelldefs[*spells].intrinsic,
2971 rand_range(min_range, max_range));
2972 spells++;
2973 }
2974 spells = (const u8 *) glb_spellbookdefs[book].skills;
2975 while (spells && *spells)
2976 {
2977 setTimedIntrinsic(this,
2978 (INTRINSIC_NAMES) glb_skilldefs[*spells].intrinsic,
2979 rand_range(min_range, max_range));
2980 spells++;
2981 }
2982 break;
2983 }
2984
2985 case MAGICTYPE_NONE:
2986 case MAGICTYPE_SCROLL:
2987 case MAGICTYPE_RING:
2988 case MAGICTYPE_HELM:
2989 case MAGICTYPE_WAND:
2990 case MAGICTYPE_AMULET:
2991 case MAGICTYPE_BOOTS:
2992 case MAGICTYPE_STAFF:
2993 break;
2994
2995 case NUM_MAGICTYPES:
2996 UT_ASSERT(!"Invalid magic type");
2997 break;
2998 }
2999
3000 switch (item->getDefinition())
3001 {
3002 case ITEM_WATER:
3003 {
3004 doeat = true;
3005 triedeat = true;
3006 foodval = 1;
3007
3008 newitem = ITEM::create(ITEM_BOTTLE, false, true);
3009
3010 // Print the drink message....
3011 formatAndReport("%U <drink> %IU.", item);
3012
3013 // Apply any bonus effects due to it being an artifact.
3014 if (item->isArtifact())
3015 {
3016 const ARTIFACT *art;
3017 const char *intrinsics;
3018 int min_range, max_range;
3019
3020 if (item->isBlessed())
3021 max_range = 2000;
3022 else
3023 max_range = 500;
3024 if (item->isCursed())
3025 min_range = 2;
3026 else
3027 min_range = 300;
3028
3029 art = item->getArtifact();
3030 intrinsics = art->intrinsics;
3031 while (intrinsics && *intrinsics)
3032 {
3033 setTimedIntrinsic(this,
3034 (INTRINSIC_NAMES) *intrinsics,
3035 rand_range(min_range, max_range));
3036 intrinsics++;
3037 }
3038 }
3039
3040 // If you are undead, should not be a pleasant experience.
3041 if (item->isBlessed() && getMobType() == MOBTYPE_UNDEAD)
3042 {
3043 if (!receiveDamage(ATTACK_ACIDPOTION, 0, item, 0,
3044 ATTACKSTYLE_MISC))
3045 {
3046 // Do nothing more if we die.
3047 // TODO: This will then drop the bottle we ate???
3048 if (stackitem)
3049 delete item;
3050 return true;
3051 }
3052 }
3053 break;
3054 }
3055
3056 case ITEM_BOTTLE:
3057 doeat = true;
3058 triedeat = true;
3059 foodval = 0;
3060 formatAndReport("%U <munch> on broken glass.");
3061 if (!receiveDamage(ATTACK_GLASSSHARDS, 0, item, 0,
3062 ATTACKSTYLE_MISC))
3063 {
3064 // Do nothing more if we die.
3065 // TODO: This will then drop the bottle we ate???
3066 if (stackitem)
3067 delete item;
3068 return true;
3069 }
3070 break;
3071
3072 case ITEM_BLACKHEART:
3073 {
3074 formatAndReport("%U <consider> eating %IU, but sanity prevails.", item);
3075 doeat = false;
3076 triedeat = true;
3077 break;
3078 }
3079
3080 default:
3081 break;
3082 }
3083
3084 if (!triedeat)
3085 {
3086 // Determine if it is the sort of thing we
3087 // can digest.
3088 if (canDigest(item) && item->defn().isquest)
3089 {
3090 triedeat = true;
3091 formatAndReport("Powerful magic forces prevent %U from eating %IU.", item);
3092 }
3093 else if (canDigest(item))
3094 {
3095 MOB *corpse;
3096 const char *verb;
3097 const char *verbchoice[] =
3098 {
3099 "%U <consume> %IU.",
3100 "%U <eat> %IU.",
3101 "%U <dine> on %IU.",
3102 "%U <munch> on %IU.",
3103 0
3104 };
3105
3106 verb = rand_string(verbchoice);
3107
3108 triedeat = true;
3109 formatAndReport(verb, item);
3110
3111 // How much health do we get? Depends on the item...
3112 // Using weight seemed clever at the time, but tiny corpses
3113 // like mice have a foodval of 50!
3114 // Thus the 10 times multiplier
3115 foodval = item->getWeight() * 10;
3116 corpse = item->getCorpseMob();
3117 if (corpse)
3118 {
3119 bool isbones;
3120 INTRINSIC_NAMES intrinsic;
3121
3122 isbones = item->getDefinition() == ITEM_BONES;
3123
3124 foodval = glb_sizedefs[corpse->getSize()].foodval;
3125 if (isbones)
3126 foodval /= 2;
3127
3128 // Bonus food if you have butchery.
3129 if (hasSkill(SKILL_BUTCHERY))
3130 foodval += foodval/2;
3131
3132 if (corpse->defn().acidiccorpse)
3133 {
3134 clearIntrinsic(INTRINSIC_STONING);
3135 if (!hasIntrinsic(INTRINSIC_RESISTACID) ||
3136 hasIntrinsic(INTRINSIC_VULNACID))
3137 {
3138 if (isAvatar())
3139 {
3140 formatAndReport("%r stomach burns!");
3141 }
3142
3143 if (!receiveDamage(ATTACK_ACIDICCORPSE, 0, item, 0,
3144 ATTACKSTYLE_MISC))
3145 {
3146 // Do nothing more if we die.
3147 if (stackitem)
3148 delete item;
3149 return true;
3150 }
3151 }
3152 }
3153
3154 for (intrinsic = INTRINSIC_NONE; intrinsic < NUM_INTRINSICS;
3155 intrinsic = (INTRINSIC_NAMES) (intrinsic+1))
3156 {
3157 if (INTRINSIC::hasIntrinsic(
3158 corpse->defn().eatgrant,
3159 intrinsic))
3160 {
3161 // Don't grant poison if we can butcher
3162 // properly.
3163 if (hasSkill(SKILL_BUTCHERY))
3164 {
3165 if (glb_intrinsicdefs[intrinsic].ispoison)
3166 continue;
3167 }
3168 if (hasSkill(SKILL_BUTCHERY) || rand_chance(50))
3169 {
3170 int duration;
3171
3172 // We cap poison duration to something sensible,
3173 // most inflicted poison attacks are 5d3, so
3174 // we just go with the 5d5
3175 if (glb_intrinsicdefs[intrinsic].ispoison)
3176 duration = rand_dice(5, 5, 0);
3177 else
3178 duration = foodval + rand_choice(foodval);
3179
3180 setTimedIntrinsic(this, intrinsic, duration);
3181 }
3182 }
3183 }
3184
3185 // If we have the butchery skill, we can also
3186 // remove any poison from the surface
3187 if (hasSkill(SKILL_BUTCHERY))
3188 skippoison = true;
3189
3190 // If the corpse grants magic, add a bonus.
3191 int mpbonus;
3192
3193 mpbonus = rand_dice(corpse->defn().eatmp);
3194 if (mpbonus)
3195 {
3196 incrementMaxMP(mpbonus);
3197 formatAndReport("%U <receive> a boost.");
3198 }
3199 }
3200
3201 doeat = true;
3202 }
3203 else
3204 {
3205 formatAndReport("%U cannot digest %IU.", item);
3206 }
3207 }
3208
3209 if (doeat)
3210 {
3211 pietyEat(item, foodval);
3212 if (!skippoison)
3213 item->applyPoison(this, this, true);
3214
3215 // Remove the item from our inventory.
3216 // If it was stacked, we can just delete item.
3217 if (stackitem)
3218 {
3219 delete item;
3220 }
3221 else
3222 {
3223 drop = dropItem(ix, iy);
3224 UT_ASSERT(drop == item);
3225 delete drop;
3226 }
3227
3228 // Place the result, if any.
3229 if (newitem)
3230 {
3231 if (!acquireItem(newitem))
3232 {
3233 // We failed to acquire the item. Likely out of room.
3234 formatAndReport("%U <discard> %IU on the ground.", newitem);
3235
3236 glbCurLevel->acquireItem(newitem, getX(), getY(), this);
3237 }
3238 }
3239
3240 // Feed the mob the foodvalue.
3241 feed(foodval);
3242 }
3243 else
3244 {
3245 // Failed to eat. If we ripped this off a stack,
3246 // we need to add it back to a stack.
3247 if (stackitem)
3248 {
3249 stackitem->mergeStack(item);
3250 delete item;
3251 }
3252 }
3253
3254 return true;
3255 }
3256
3257 bool
actionRead(int ix,int iy)3258 MOB::actionRead(int ix, int iy)
3259 {
3260 ITEM *item, *drop, *newitem = 0, *itemstack = 0;
3261 BUF buf;
3262 bool doread = false;
3263 bool costturn = true;
3264
3265 item = getItem(ix, iy);
3266
3267 // Verify there is an item.
3268 if (!item)
3269 {
3270 formatAndReport("%U <read> thin air.");
3271 return false;
3272 }
3273
3274 // Check to ensure we are not blind. Blind people can't read.
3275 if (hasIntrinsic(INTRINSIC_BLIND))
3276 {
3277 formatAndReport("%U cannot read %IU, as %p <be> blind.", item);
3278 return false;
3279 }
3280
3281 // There is no objection to readding equipped scrolls.
3282 // Perhaps we should give a bonus some day?
3283
3284 if (item->getStackCount() > 1)
3285 {
3286 itemstack = item;
3287 item = itemstack->splitStack(1);
3288 }
3289
3290 // Check if its magic class is scroll..
3291 if (item->getMagicType() == MAGICTYPE_SCROLL)
3292 {
3293 int magicclass;
3294
3295 doread = true;
3296
3297 // Print the read message...
3298 formatAndReport("%U <read> %IU.", item);
3299
3300 // Apply any bonus effects due to it being an artifact.
3301 if (item->isArtifact())
3302 {
3303 const ARTIFACT *art;
3304 const char *intrinsics;
3305 int min_range, max_range;
3306
3307 if (item->isBlessed())
3308 max_range = 2000;
3309 else
3310 max_range = 500;
3311 if (item->isCursed())
3312 min_range = 2;
3313 else
3314 min_range = 300;
3315
3316 art = item->getArtifact();
3317 intrinsics = art->intrinsics;
3318 while (intrinsics && *intrinsics)
3319 {
3320 setTimedIntrinsic(this,
3321 (INTRINSIC_NAMES) *intrinsics,
3322 rand_range(min_range, max_range));
3323 intrinsics++;
3324 }
3325 }
3326
3327 magicclass = item->getMagicClass();
3328
3329 // If it autoids on reading do so.
3330 if (glb_scrolldefs[magicclass].autoid &&
3331 (isAvatar() ||
3332 (MOB::getAvatar() && MOB::getAvatar()->canSense(this))) )
3333 item->markIdentified();
3334
3335 // Apply any contact poison.
3336 item->applyPoison(this, this);
3337
3338 // Apply the effect:
3339 switch ((SCROLL_NAMES) magicclass)
3340 {
3341 case SCROLL_ID:
3342 {
3343 formatAndReport("%U <be> more aware of %r possessions!");
3344
3345 if (isAvatar())
3346 {
3347 // ID all items...
3348 ITEM *id;
3349 int x, y;
3350
3351 for (y = 0; y < MOBINV_HEIGHT; y++)
3352 for (x = 0; x < MOBINV_WIDTH; x++)
3353 {
3354 id = getItem(x, y);
3355 if (id)
3356 id->markIdentified();
3357 }
3358 }
3359 break;
3360 }
3361 case SCROLL_REMOVECURSE:
3362 {
3363 if (item->isCursed())
3364 {
3365 receiveCurse(false, false);
3366 }
3367 else
3368 {
3369 receiveUncurse(item->isBlessed());
3370 }
3371 break;
3372 }
3373 case SCROLL_FIRE:
3374 {
3375 int x, y;
3376 MOB *oldself;
3377 MOBREF myself;
3378
3379 formatAndReport("A pillar of fire engulfs %U!");
3380
3381 x = getX(); y = getY();
3382 oldself = this;
3383 myself.setMob(this);
3384 ourEffectAttack = ATTACK_FIRESCROLL;
3385 glbCurLevel->fireBall(getX(), getY(), 1, true,
3386 areaAttackCBStatic, &myself);
3387
3388 // If die, return immediately.
3389 if (oldself != glbCurLevel->getMob(x, y))
3390 {
3391 if (itemstack)
3392 delete item;
3393 return true;
3394 }
3395 break;
3396 }
3397 case SCROLL_LIGHT:
3398 {
3399 int radius = 5;
3400 bool islight = true;
3401
3402 if (item->isCursed())
3403 {
3404 radius = 7;
3405 islight = false;
3406 }
3407 if (item->isBlessed())
3408 radius = 7;
3409
3410 if (islight)
3411 {
3412 formatAndReport("A warm light spreads from %U.");
3413 }
3414 else
3415 {
3416 formatAndReport("An inky blackness spreads from %U.");
3417 }
3418
3419 // Draw a square radius light.
3420 glbCurLevel->applyFlag(SQUAREFLAG_LIT, getX(), getY(), radius,
3421 false, islight);
3422 break;
3423 }
3424 case SCROLL_MAP:
3425 {
3426 if (item->isCursed())
3427 formatAndReport("%U <be> less aware of %r surroundings.");
3428 else if (item->isBlessed())
3429 formatAndReport("%U <be> very aware of %r surroundings.");
3430 else
3431 formatAndReport("%U <be> more aware of %r surroundings.");
3432
3433 // If blessed, get entire level.
3434 // If regular, get 10 radius (almost entire level)
3435 // If cursed, lose level :>
3436 if (isAvatar())
3437 {
3438 if (item->isCursed())
3439 glbCurLevel->applyFlag(SQUAREFLAG_MAPPED,
3440 getX(), getY(), MAP_WIDTH, false,
3441 false);
3442 else if (item->isBlessed())
3443 glbCurLevel->markMapped(getX(), getY(),
3444 MAP_WIDTH, MAP_HEIGHT,
3445 false);
3446 else
3447 glbCurLevel->markMapped(getX(), getY(),
3448 10, 10,
3449 false);
3450 }
3451 break;
3452 }
3453 case SCROLL_HEAL:
3454 {
3455 formatAndReport("%R wounds are healed.");
3456 receiveHeal(15 - (item->isCursed() * 5) + (item->isBlessed() * 5), this);
3457 break;
3458 }
3459 case SCROLL_TELEPORT:
3460 {
3461 actionTeleport(!item->isCursed(), item->isBlessed());
3462 break;
3463 }
3464 case SCROLL_ENCHANTARMOUR:
3465 {
3466 int enchantment;
3467
3468 enchantment = 1;
3469 if (item->isCursed())
3470 {
3471 enchantment = -1;
3472 }
3473 if (item->isBlessed())
3474 {
3475 enchantment += rand_choice(2); // 50% chance of +2.
3476 }
3477
3478 receiveArmourEnchant((ITEMSLOT_NAMES) -1, enchantment, true);
3479
3480 break;
3481 }
3482 case SCROLL_ENCHANTWEAPON:
3483 {
3484 int enchantment;
3485
3486 enchantment = 1;
3487 if (item->isCursed())
3488 {
3489 enchantment = -1;
3490 }
3491 if (item->isBlessed())
3492 {
3493 if (rand_choice(2))
3494 {
3495 enchantment++;
3496 }
3497 }
3498
3499 receiveWeaponEnchant(enchantment, true);
3500 break;
3501 }
3502 case SCROLL_MAKEARTIFACT:
3503 {
3504 ITEM *item;
3505
3506 item = getEquippedItem(ITEMSLOT_RHAND);
3507 if (!item)
3508 {
3509 // Oops! This was wasted :>
3510 formatAndReport("%U <feel> omnipotent!");
3511 formatAndReport("The feeling passes.");
3512 // Do not consume the scroll! That is just evil!
3513 doread = false;
3514 break;
3515 }
3516
3517 // We have a valid item to make into an artifact.
3518 // If it is already an artifact, nothing happens.
3519 if (item->isArtifact())
3520 {
3521 formatAndReport("%R %IU is already as all-powerful as it can get.", item);
3522 // Do not consume the scroll! That is just evil!
3523 doread = false;
3524 break;
3525 }
3526
3527 // Transform the item into an artifact.
3528 formatAndReport("Eldritch energies from planes both higher and lower swirl through the air.");
3529 formatAndReport("They condense into a thick plasma which surrounds %R %Iu!", item);
3530 formatAndReport("%IU <I:be> now named by the gods!", item);
3531
3532 item->makeArtifact();
3533
3534 formatAndReport("%Ip <I:be> now known as %IU!", item);
3535
3536 break;
3537 }
3538 case NUM_SCROLLS:
3539 {
3540 UT_ASSERT(!"INVALID SCROLL NUMBER!");
3541 break;
3542 }
3543 }
3544 }
3545 else if (item->getMagicType() == MAGICTYPE_SPELLBOOK)
3546 {
3547 int magicclass;
3548
3549 doread = true;
3550
3551 // Print the read message...
3552 formatAndReport("%U <read> %IU.", item);
3553
3554 magicclass = item->getMagicClass();
3555
3556 // Apply any contact poison.
3557 item->applyPoison(this, this);
3558
3559 // Ask the user what to read.
3560 if (!isAvatar())
3561 {
3562 UT_ASSERT(!"Reading not supported for non-avatar!");
3563 doread = false;
3564 }
3565 else
3566 {
3567 int num_options;
3568 SKILL_NAMES skill = SKILL_NONE;
3569 SPELL_NAMES spell = SPELL_NONE;
3570
3571 num_options = strlen(glb_spellbookdefs[magicclass].spells) +
3572 strlen(glb_spellbookdefs[magicclass].skills);
3573
3574 if (!num_options)
3575 {
3576 // Nothing you can learn from this book!
3577 doread = false;
3578 }
3579 else
3580 {
3581 char **options;
3582 int *enumvalue;
3583 bool *isspell;
3584 int i;
3585
3586 // Remind the user the number of free slots.
3587 if (strlen(glb_spellbookdefs[magicclass].spells))
3588 {
3589 buf.sprintf("You have %d free %s %s.",
3590 getFreeSpellSlots(),
3591 "spell",
3592 ((getFreeSpellSlots() == 1) ? "slot" : "slots"));
3593 }
3594 else
3595 {
3596 buf.sprintf("You have %d free %s %s.",
3597 getFreeSkillSlots(),
3598 "skill",
3599 ((getFreeSkillSlots() == 1) ? "slot" : "slots"));
3600 }
3601 reportMessage(buf);
3602
3603 options = new char *[num_options+2];
3604 enumvalue = new int[num_options];
3605 isspell = new bool[num_options];
3606
3607 const u8 *stuff;
3608
3609 i = 0;
3610 for (stuff = (const u8 *)glb_spellbookdefs[magicclass].spells;
3611 *stuff;
3612 stuff++)
3613 {
3614 // Add a spell.
3615 spell = (SPELL_NAMES) *stuff;
3616
3617 options[i] = new char [strlen(glb_spelldefs[spell].name) + 5];
3618
3619 strcpy(options[i], intrinsicFromWhatLetter((INTRINSIC_NAMES) glb_spelldefs[spell].intrinsic));
3620
3621 strcat(options[i], glb_spelldefs[spell].name);
3622 isspell[i] = true;
3623 enumvalue[i] = spell;
3624
3625 i++;
3626 }
3627 for (stuff = (const u8 *)glb_spellbookdefs[magicclass].skills;
3628 *stuff;
3629 stuff++)
3630 {
3631 // Add a skill.
3632 skill = (SKILL_NAMES) *stuff;
3633
3634 options[i] = new char [strlen(glb_skilldefs[skill].name) + 5];
3635 strcpy(options[i], intrinsicFromWhatLetter((INTRINSIC_NAMES) glb_skilldefs[skill].intrinsic));
3636
3637 strcat(options[i], glb_skilldefs[skill].name);
3638 isspell[i] = false;
3639 enumvalue[i] = skill;
3640
3641 i++;
3642 }
3643
3644 UT_ASSERT(i == num_options);
3645
3646 // Add in the cancel option.
3647 options[i++] = "cancel";
3648
3649 // Null terminate
3650 options[i] = 0;
3651
3652 // And prompt for choice:
3653 spell = SPELL_NONE;
3654 skill = SKILL_NONE;
3655
3656 bool tryagain = true;
3657
3658 while (tryagain)
3659 {
3660 static int lastchoice = 0;
3661 int choice;
3662 int aorb;
3663
3664 tryagain = false;
3665
3666 choice = gfx_selectmenu(15, 3, (const char **) options, aorb, lastchoice, true);
3667 if (choice == num_options)
3668 choice = -1;
3669
3670 if (choice >= 0)
3671 lastchoice = choice;
3672
3673 if (choice >= 0 &&
3674 (aorb == BUTTON_SELECT || aorb == BUTTON_X))
3675 {
3676 // Give long description..
3677 if (isspell[choice])
3678 encyc_viewSpellDescription((SPELL_NAMES) enumvalue[choice]);
3679 else
3680 encyc_viewSkillDescription((SKILL_NAMES) enumvalue[choice]);
3681 tryagain = true;
3682 }
3683
3684 if (aorb != BUTTON_A)
3685 choice = -1;
3686
3687 if (choice >= 0)
3688 {
3689 if (isspell[choice])
3690 spell = (SPELL_NAMES) enumvalue[choice];
3691 else
3692 skill = (SKILL_NAMES) enumvalue[choice];
3693 }
3694 }
3695
3696 // Delete everything
3697 for (i = 0; i < num_options; i++)
3698 {
3699 delete [] options[i];
3700 }
3701 delete [] options;
3702 delete [] isspell;
3703 delete [] enumvalue;
3704
3705 {
3706 int y;
3707 for (y = 3; y < 19; y++)
3708 gfx_cleartextline(y);
3709 }
3710
3711 // Give long description..
3712 // Lets the user know what they are in for.
3713 const char *name = 0;
3714 if (spell != SPELL_NONE)
3715 {
3716 encyc_viewSpellDescription(spell);
3717 name = glb_spelldefs[spell].name;
3718 }
3719 else if (skill != SKILL_NONE)
3720 {
3721 encyc_viewSkillDescription(skill);
3722 name = glb_skilldefs[skill].name;
3723 }
3724
3725 if (name)
3726 {
3727
3728 BUF spelltext;
3729
3730 spelltext.sprintf("Learn %s?", name);
3731
3732 if (!gfx_yesnomenu(spelltext))
3733 {
3734 // A request to cancel
3735 spell = SPELL_NONE;
3736 skill = SKILL_NONE;
3737 }
3738
3739 }
3740
3741 // Learn the relevant spell or skill
3742 doread = false;
3743 if (spell != SPELL_NONE)
3744 {
3745 if (canLearnSpell(spell, true))
3746 {
3747 doread = true;
3748 learnSpell(spell);
3749 }
3750 }
3751 if (skill != SKILL_NONE)
3752 {
3753 if (canLearnSkill(skill, true))
3754 {
3755 doread = true;
3756 learnSkill(skill);
3757 }
3758 }
3759
3760 if (spell == SPELL_NONE && skill == SKILL_NONE)
3761 {
3762 // If the user aborts reading the spell book,
3763 // ie: just glances to see the titles,
3764 // we want to not cost a turn.
3765 costturn = false;
3766 }
3767 }
3768 }
3769
3770 // Autoid spellbooks...
3771 // We don't want to markIdentified as we don't insta-id the
3772 // charges.
3773 // Because the avatar sees the menu of choices, there is no
3774 // case in which we *don't* want to identify.
3775 if (isAvatar())
3776 item->markClassKnown();
3777
3778 if (doread)
3779 {
3780 // Use a charge from the spellbook:
3781 item->useCharge();
3782 // If no charges, fade away.
3783 if (item->getCharges())
3784 {
3785 doread = false;
3786 // Chance of figuring out the number of pages left.
3787 if (isAvatar() && !item->isKnownCharges())
3788 {
3789 if (!rand_choice(20))
3790 {
3791 item->markChargesKnown();
3792 formatAndReport("%U <determine> the number of pages in %IU.", item);
3793 }
3794 }
3795 }
3796 else
3797 {
3798 doread = true;
3799 formatAndReport("%IU <I:fade> away.", item);
3800 }
3801 }
3802 }
3803 else
3804 {
3805 // Try to read the specific object type.
3806 switch (item->getDefinition())
3807 {
3808 default:
3809 doread = false;
3810 formatAndReport("%U <look> at %IU but cannot find any writing.", item);
3811 break;
3812 }
3813 }
3814
3815 if (doread)
3816 {
3817 // Remove the item from our inventory.
3818 if (itemstack)
3819 {
3820 delete item;
3821 }
3822 else
3823 {
3824 drop = dropItem(ix, iy);
3825 UT_ASSERT(drop == item);
3826 delete drop;
3827 }
3828
3829 // Place the result, if any.
3830 if (newitem)
3831 acquireItem(newitem);
3832 }
3833 else
3834 {
3835 // Merge the scroll back if required.
3836 // Doing a mergeStack will be wrong, as spell books
3837 // would be falsely merged and fail to decrement
3838 // their usage. Instead, we reacquire the item.
3839 if (itemstack)
3840 {
3841 if (!acquireItem(item))
3842 {
3843 // Not enough room, drop on the ground.
3844 glbCurLevel->acquireItem(item, getX(), getY(), this);
3845 }
3846 }
3847 }
3848
3849 return costturn;
3850 }
3851
3852 bool
actionDip(int potionx,int potiony,int ix,int iy)3853 MOB::actionDip(int potionx, int potiony, int ix, int iy)
3854 {
3855 ITEM *potion, *item, *drop, *newpotion, *newitem;
3856 ITEM *potionstack = 0;
3857 bool consumed = false;
3858
3859 // First, get the items and ensure they exist (and are of the right
3860 // type)
3861
3862 potion = getItem(potionx, potiony);
3863 if (!potion)
3864 {
3865 formatAndReport("%U <try> to dip into thin air!");
3866 return false;
3867 }
3868
3869 item = getItem(ix, iy);
3870 if (!item)
3871 {
3872 formatAndReport("%U <dip> nothing into %IU. Suprisingly, nothing happens!", potion);
3873 return false;
3874 }
3875
3876 // If the potion is the same as the item, bad stuff will happen.
3877 // Thus we avert that with a humerous message.
3878 if (item == potion)
3879 {
3880 formatAndReport("%IU <I:be> not a klein bottle!", potion);
3881 return false;
3882 }
3883
3884 // If the potion is being wielded, we forbid the dip.
3885 // This avoids being able to dequip items by dipping stuff into them.
3886 if (!potionx)
3887 {
3888 formatAndReport("%U cannot dip anything into an equipped item.");
3889 return false;
3890 }
3891
3892 // Now, note that we can dip a stack. However, we always dip
3893 // into one potion.
3894
3895 // Remove the items from our inventory for the duration of the dip...
3896 if (potion->getStackCount() > 1)
3897 {
3898 potionstack = potion;
3899 potion = potionstack->splitStack(1);
3900 }
3901 else
3902 {
3903 drop = dropItem(potionx, potiony);
3904 UT_ASSERT(drop == potion);
3905 }
3906 drop = dropItem(ix, iy);
3907 UT_ASSERT(drop == item);
3908
3909 // Do the dip....
3910 consumed = potion->actionDip(this, item, newpotion, newitem);
3911
3912 // Acquire the new items!
3913 // If the actionDip failed to dip and the same potion was returned,
3914 // this will also implicitly merge it into our stack. In the
3915 // case it generates an empty bottle, it will have deleted our
3916 // split potion for us.
3917 if (newitem)
3918 {
3919 if (!acquireItem(newitem, ix, iy))
3920 {
3921 formatAndReport("Lacking room, %U <drop> %IU", newitem);
3922
3923 // Mark the item below grade if we are.
3924 if (hasIntrinsic(INTRINSIC_INPIT) ||
3925 hasIntrinsic(INTRINSIC_SUBMERGED))
3926 {
3927 newitem->markBelowGrade(true);
3928 }
3929
3930 glbCurLevel->acquireItem(newitem, getX(), getY(), this);
3931 }
3932 }
3933 if (newpotion)
3934 {
3935 if (!acquireItem(newpotion))
3936 {
3937 formatAndReport("Lacking room, %U <drop> %IU.", newpotion);
3938
3939 // Mark the item below grade if we are.
3940 if (hasIntrinsic(INTRINSIC_INPIT) ||
3941 hasIntrinsic(INTRINSIC_SUBMERGED))
3942 {
3943 newpotion->markBelowGrade(true);
3944 }
3945
3946 glbCurLevel->acquireItem(newpotion, getX(), getY(), this);
3947 }
3948 }
3949
3950 // If ix == 0, this is an equpped item. Dipping may thus
3951 // change what we have equipped, so we must rebuild appearance.
3952 if (ix == 0)
3953 rebuildAppearance();
3954
3955 return consumed;
3956 }
3957
3958 bool
actionThrow(int ix,int iy,int dx,int dy,int dz)3959 MOB::actionThrow(int ix, int iy, int dx, int dy, int dz)
3960 {
3961 ITEM *missile, *launcher, *stack = 0;
3962 BUF buf;
3963 const ATTACK_DEF *missileattack;
3964
3965 applyConfusion(dx, dy, dz);
3966
3967 missile = getItem(ix, iy);
3968
3969 if (!missile)
3970 {
3971 formatAndReport("%U <mime> throwing something.");
3972 return false;
3973 }
3974
3975 // Can't throw items you have equipped.
3976 if (!ix)
3977 {
3978 formatAndReport("%U cannot throw equipped items.");
3979 return false;
3980 }
3981
3982 if (!dx && !dy && !dz)
3983 {
3984 formatAndReport("%U <toss> %IU from hand to hand.", missile);
3985 return false;
3986 }
3987
3988 if (missile->getStackCount() > 1)
3989 {
3990 stack = missile;
3991 missile = stack->splitStack(1);
3992 }
3993 else
3994 {
3995 missile = dropItem(ix, iy);
3996 }
3997
3998 // Figure out what our launcher, if any, is.
3999 launcher = 0;
4000
4001 if (missile->requiresLauncher())
4002 {
4003 launcher = getEquippedItem(ITEMSLOT_RHAND);
4004 if (launcher)
4005 {
4006 if (!missile->canLaunchThis(launcher))
4007 launcher = 0;
4008 }
4009 // Try also our left hand. Either hand is allowed to have the launcher.
4010 if (!launcher)
4011 {
4012 launcher = getEquippedItem(ITEMSLOT_LHAND);
4013 if (launcher)
4014 {
4015 if (!missile->canLaunchThis(launcher))
4016 launcher = 0;
4017 }
4018 }
4019 }
4020
4021 // If we should have a launcher, but don't, we need to use misused.
4022 missileattack = missile->getThrownAttack();
4023 if (missile->requiresLauncher() && !launcher)
4024 missileattack = &glb_attackdefs[ATTACK_MISTHROWN];
4025
4026 if (!dz)
4027 {
4028 // Throwing outwards. If this is cursed, we want a chance
4029 // to fumble.
4030 if (missile->isCursed() &&
4031 (missile->getItemType() == ITEMTYPE_WEAPON ||
4032 missile->getItemType() == ITEMTYPE_POTION))
4033 {
4034 // Check we don't have misthrown, don't want to id
4035 // swords this way.
4036 if (missile->defn().thrownattack != ATTACK_MISTHROWN)
4037 {
4038 if (rand_chance(10))
4039 {
4040 formatAndReport("%U <fumble>!");
4041 // Reset throw direction to the ground
4042 dx = dy = 0;
4043 dz = -1;
4044 // If thrown by avatar, note
4045 if (isAvatar())
4046 {
4047 missile->markCursedKnown();
4048 if (stack)
4049 stack->markCursedKnown();
4050 }
4051 }
4052 }
4053 }
4054 }
4055
4056 if (dz)
4057 {
4058 if (dz < 0)
4059 {
4060 formatAndReport("%U <throw> %IU onto the ground.", missile);
4061
4062 if (missile->doBreak(10, this, 0, glbCurLevel, getX(), getY()))
4063 return true;
4064
4065 glbCurLevel->acquireItem(missile, getX(), getY(), this);
4066 return true;
4067 }
4068 else
4069 {
4070 int dropx, dropy;
4071 bool selflives;
4072
4073 // Throwing into the air, it lands on you :>
4074 formatAndReport("%U <throw> %IU into the air.", missile);
4075
4076 dropx = getX();
4077 dropy = getY();
4078
4079 formatAndReport("%IU <I:fall> on %A!", missile);
4080
4081 selflives = receiveDamage(missileattack, this, missile, launcher,
4082 ATTACKSTYLE_THROWN);
4083
4084 if (missile->doBreak(10, (selflives ? this : 0), 0, glbCurLevel, dropx, dropy))
4085 return true;
4086
4087 glbCurLevel->acquireItem(missile, dropx, dropy, this);
4088
4089 return true;
4090 }
4091 }
4092
4093 if (launcher)
4094 formatAndReport("%U <fire> %IU.", missile);
4095 else
4096 formatAndReport("%U <throw> %IU.", missile);
4097
4098 bool ricochet = false;
4099
4100 // Check for special skill...
4101 if (missile->defn().specialskill == SKILL_WEAPON_RICOCHET)
4102 {
4103 // Requires a launcher
4104 if (launcher && hasSkill(SKILL_WEAPON_RICOCHET))
4105 {
4106 ricochet = true;
4107 }
4108 }
4109
4110 MOBREF myself;
4111
4112 myself.setMob(this);
4113
4114 glbCurLevel->throwItem(getX(), getY(), dx, dy,
4115 missile, stack, launcher,
4116 this,
4117 missileattack,
4118 ricochet);
4119
4120 // If we died, there is no point worrying about floating back
4121 // on star squares.
4122 // This is also necessary if we de-poly with the splash damage.
4123 MOB *mob = myself.getMob();
4124 if (!mob || mob->hasIntrinsic(INTRINSIC_DEAD))
4125 return true;
4126
4127 // Check if you are on a star square, in which case you float
4128 // in opposite direction
4129 SQUARE_NAMES tile = glbCurLevel->getTile(mob->getX(), mob->getY());
4130 if (glb_squaredefs[tile].isstars)
4131 {
4132 mob->formatAndReport("%U <float> in the opposite direction.");
4133 mob->actionWalk(-dx, -dy, true);
4134 }
4135
4136 return true;
4137 }
4138
4139 bool
actionFire(int dx,int dy,int dz)4140 MOB::actionFire(int dx, int dy, int dz)
4141 {
4142 // Find an item in the quiver.
4143 ITEM *ammo;
4144
4145 for (ammo = myInventory; ammo; ammo = ammo->getNext())
4146 {
4147 // Ignore worn items.
4148 if (ammo->getX() == 0)
4149 continue;
4150
4151 if (ammo->isQuivered())
4152 break;
4153 }
4154
4155 if (!ammo)
4156 {
4157 formatAndReport("%U <have> nothing quivered.");
4158 return false;
4159 }
4160
4161 // We succeeded! Throw!
4162 return actionThrow(ammo->getX(), ammo->getY(), dx, dy, dz);
4163 }
4164
4165 bool
actionQuiver(int ix,int iy)4166 MOB::actionQuiver(int ix, int iy)
4167 {
4168 ITEM *ammo;
4169
4170 ammo = getItem(ix, iy);
4171
4172 if (!ammo)
4173 {
4174 formatAndReport("%U <quiver> empty air!");
4175 return false;
4176 }
4177
4178 // Toggle state.
4179 if (ammo->isQuivered())
4180 {
4181 formatAndReport("%U <dequiver> %IU.", ammo);
4182 ammo->markQuivered(false);
4183 }
4184 else
4185 {
4186 formatAndReport("%U <quiver> %IU.", ammo);
4187 ammo->markQuivered(true);
4188 }
4189 return false;
4190 }
4191
4192 bool
ableToZap(int ix,int iy,int dx,int dy,int dz,bool quiet)4193 MOB::ableToZap(int ix, int iy, int dx, int dy, int dz, bool quiet)
4194 {
4195 ITEM *wand;
4196
4197 wand = getItem(ix, iy);
4198 if (!wand)
4199 {
4200 if (!quiet)
4201 formatAndReport("%U <try> to zap nothing!");
4202
4203 return false;
4204 }
4205
4206 return true;
4207 }
4208
4209 bool
actionZap(int ix,int iy,int dx,int dy,int dz)4210 MOB::actionZap(int ix, int iy, int dx, int dy, int dz)
4211 {
4212 ITEM *wand;
4213 bool consumed = false;
4214
4215 applyConfusion(dx, dy, dz);
4216
4217 // First, get the items and ensure they exist (and are of the right
4218 // type)
4219 if (!ableToZap(ix, iy, dx, dy, dz, false))
4220 return false;
4221
4222 wand = getItem(ix, iy);
4223
4224 // If the wand was in a stack, unpeel it from the stack before zapping.
4225 if (wand->getStackCount() > 1)
4226 {
4227 int sx, sy;
4228
4229 if (findItemSlot(sx, sy))
4230 {
4231 wand = wand->splitStack();
4232 acquireItem(wand, sx, sy);
4233 }
4234 else
4235 {
4236 formatAndReport("%U <have> no free space!");
4237 return false;
4238 }
4239 }
4240
4241 // Do the zap....
4242 consumed = wand->actionZap(this, dx, dy, dz);
4243
4244 return consumed;
4245 }
4246
4247 void
cancelSpell(SPELL_NAMES spell)4248 MOB::cancelSpell(SPELL_NAMES spell)
4249 {
4250 // Un-Pay the spell cost...
4251 myHP += glb_spelldefs[spell].hpcost;
4252 myMP += glb_spelldefs[spell].mpcost;
4253 myExp += glb_spelldefs[spell].xpcost;
4254 }
4255
4256 bool
actionCast(SPELL_NAMES spell,int dx,int dy,int dz)4257 MOB::actionCast(SPELL_NAMES spell, int dx, int dy, int dz)
4258 {
4259 MOB *target;
4260 BUF buf;
4261 int i, n;
4262
4263 const char *directionflavour = 0;
4264 bool targetself = false;
4265 bool istargetlit = false;
4266
4267 applyConfusion(dx, dy, dz);
4268
4269 // Targetted square.
4270 int tx = getX() + dx;
4271 int ty = getY() + dy;
4272 MOBREF myself;
4273
4274 myself.setMob(this);
4275
4276 tx &= MAP_WIDTH-1;
4277 ty &= MAP_HEIGHT-1;
4278
4279 // Determine if our target is lit...
4280 if ((abs(dx) <= 1 && abs(dy) <= 1) ||
4281 glbCurLevel->isLit(tx, ty))
4282 {
4283 istargetlit = true;
4284 }
4285
4286 // TODO: Sanity test...
4287
4288 // Text for casting the spell...
4289 buf = formatToString("%U <cast> %B1.",
4290 this, 0, 0, 0,
4291 glb_spelldefs[spell].name);
4292 reportMessage(buf);
4293
4294 // Pay the spell cost...
4295 myHP -= glb_spelldefs[spell].hpcost;
4296 myMP -= glb_spelldefs[spell].mpcost;
4297 myExp -= glb_spelldefs[spell].xpcost;
4298
4299 target = glbCurLevel->getMob(tx, ty);
4300
4301 if (!dx && !dy && !dz)
4302 {
4303 targetself = true;
4304 directionflavour = getReflexive();
4305 }
4306 if (dz)
4307 {
4308 if (dz < 0)
4309 directionflavour = "the floor";
4310 else
4311 directionflavour = "the ceiling";
4312 }
4313
4314 // Switch on spell...
4315 switch (spell)
4316 {
4317 case SPELL_NONE:
4318 {
4319 formatAndReport("%U <gesture> in vain.");
4320 return true;
4321 }
4322 case SPELL_FLASH:
4323 // Even if no creature was targeted, the square
4324 // hit becomes lit.
4325 glbCurLevel->setFlag(tx, ty, SQUAREFLAG_LIT, true);
4326 if (!target)
4327 {
4328 formatAndReport("%U <illuminate> empty air.");
4329 return true;
4330 }
4331
4332 formatAndReport("%U <direct> a blinding flash at %MU.", target);
4333
4334 pietyCastSpell(spell);
4335 target->receiveDamage(ATTACK_SPELL_FLASH, this, 0, 0, ATTACKSTYLE_SPELL);
4336 return true;
4337
4338 case SPELL_STICKYFLAMES:
4339 if (!target)
4340 {
4341 formatAndReport("%U <cast> at thin air.");
4342 return true;
4343 }
4344
4345 formatAndReport("%U <set> %MU alight.", target);
4346
4347 target->setTimedIntrinsic(this,
4348 INTRINSIC_AFLAME, rand_dice(3, 2, 3));
4349
4350 pietyCastSpell(spell);
4351
4352 return true;
4353
4354 case SPELL_CHILL:
4355 if (!target)
4356 {
4357 formatAndReport("%U <cast> at thin air.");
4358 return true;
4359 }
4360
4361 formatAndReport("%U <touch> %MU.", target);
4362
4363 pietyCastSpell(spell);
4364 target->receiveDamage(ATTACK_SPELL_CHILL, this, 0, 0, ATTACKSTYLE_SPELL);
4365 return true;
4366
4367 case SPELL_SPARK:
4368 formatAndReport("Electrical sparks fly from %U.");
4369 pietyCastSpell(spell);
4370
4371 ourEffectAttack = ATTACK_SPELL_SPARK;
4372 glbCurLevel->fireBall(getX(), getY(), 1, false,
4373 areaAttackCBStatic, &myself);
4374 return true;
4375
4376 case SPELL_MAGICMISSILE:
4377 formatAndReport("%U <send> a magic missile from %r fingertip.");
4378
4379 ourZapSpell = spell;
4380 ourFireBallCount = getMagicDie();
4381
4382 pietyCastSpell(spell);
4383
4384 if (targetself || dz)
4385 {
4386 if (dz)
4387 reportMessage("The ray bounces.");
4388 zapCallbackStatic(getX(), getY(), false, &myself);
4389 }
4390 else
4391 {
4392 glbCurLevel->fireRay(getX(), getY(),
4393 dx, dy,
4394 5 + getMagicDie(),
4395 MOVE_STD_FLY, true,
4396 zapCallbackStatic,
4397 &myself);
4398 }
4399 return true;
4400
4401 case SPELL_ACIDSPLASH:
4402 if (!target)
4403 {
4404 formatAndReport("%U <cast> at thin air.");
4405 return true;
4406 }
4407
4408 formatAndReport("%U <spray> corrosive acid.");
4409 pietyCastSpell(spell);
4410 target->receiveDamage(ATTACK_SPELL_ACIDSPLASH, this,
4411 0, 0, ATTACKSTYLE_SPELL);
4412
4413 // Destroy a random inventory item.
4414 // If we targeted ourself, we may be dead, however.
4415 if ((myself.getMob() == this) && !hasIntrinsic(INTRINSIC_DEAD))
4416 {
4417 int ix, iy;
4418 ITEM *item;
4419
4420 ix = rand_range(0, MOBINV_WIDTH-1);
4421 iy = rand_range(0, MOBINV_HEIGHT-1);
4422 item = getItem(ix, iy);
4423
4424 if (item)
4425 {
4426 formatAndReport("The backsplash drenches %r %Iu!", item);
4427
4428 if (item->canDissolve())
4429 {
4430 // Peel off one instance if stacked, otherwise
4431 // take the whole thing.
4432 if (item->getStackCount() > 1)
4433 {
4434 item = item->splitStack(1);
4435 }
4436 else
4437 {
4438 item = dropItem(item->getX(), item->getY());
4439 }
4440 formatAndReport("%IU <I:be> dissolved.", item);
4441
4442 delete item;
4443
4444 // Rebuild our appearance.
4445 if (!ix)
4446 rebuildAppearance();
4447 }
4448 else if (item->getDefinition() == ITEM_BOTTLE)
4449 {
4450 // Fill bottle with acid!
4451 formatAndReport("Acid fills %IU.", item);
4452 item->setDefinition(ITEM::lookupMagicItem(MAGICTYPE_POTION, POTION_ACID));
4453
4454 // If we can see this happening, we now know
4455 // what acid potions are.
4456 if (MOB::getAvatar() && MOB::getAvatar()->canSense(this))
4457 item->markClassKnown();
4458 }
4459 else
4460 {
4461 formatAndReport("%IU <I:be> unaffected.", item);
4462 }
4463 }
4464 }
4465 return true;
4466
4467 case SPELL_ACIDICMIST:
4468 // Determine if we can see the coords.
4469 if (!hasIntrinsic(INTRINSIC_BLIND) &&
4470 glbCurLevel->canMove(tx, ty, MOVE_STD_FLY) &&
4471 istargetlit &&
4472 glbCurLevel->hasLOS(getX(), getY(),
4473 tx, ty))
4474 {
4475 pietyCastSpell(spell);
4476 formatAndReport("%U <condense> an acidic mist.");
4477
4478 glbCurLevel->setSmoke(tx, ty, SMOKE_ACID, this);
4479 return true;
4480 }
4481
4482 // The creature cannot see its destination - acidic mist is
4483 // illegal!
4484 formatAndReport("%U <concentrate> in vain.");
4485 cancelSpell(spell);
4486 return true;
4487
4488 case SPELL_CORROSIVEEXPLOSION:
4489 if (!hasIntrinsic(INTRINSIC_BLIND) &&
4490 glbCurLevel->canMove(tx, ty, MOVE_STD_FLY) &&
4491 istargetlit &&
4492 glbCurLevel->hasLOS(getX(), getY(),
4493 tx, ty))
4494 {
4495 ITEMSTACK itemstack;
4496 int numitems, i;
4497
4498 glbCurLevel->getItemStack(itemstack, tx, ty);
4499
4500 formatAndReport("%U <create> a highly volatile blob of acid.");
4501
4502 numitems = 0;
4503 for (i = 0; i < itemstack.entries(); i++)
4504 {
4505 if (itemstack(i)->canDissolve())
4506 {
4507 const char *flavour[] =
4508 {
4509 "devours",
4510 "consumes",
4511 "dissolves",
4512 "eats",
4513 0
4514 };
4515 BUF itemname = itemstack(i)->getName();
4516 // Stacks of arrows become very powerful :>
4517 numitems += itemstack(i)->getStackCount();
4518 buf.sprintf("The blob %s %s.",
4519 rand_string(flavour),
4520 itemname.buffer());
4521 glbCurLevel->reportMessage(buf, tx, ty);
4522 glbCurLevel->dropItem(itemstack(i));
4523 delete itemstack(i);
4524 }
4525 }
4526
4527 if (!numitems)
4528 {
4529 glbCurLevel->reportMessage("With nothing to sustain it, the blob fizzles away.", tx, ty);
4530 return true;
4531 }
4532
4533 // Successful cast!
4534 pietyCastSpell(spell);
4535
4536 // Explosion!!!
4537 int radius;
4538 int reps;
4539 radius = numitems - 1;
4540 if (radius > 10)
4541 radius = 10;
4542 reps = (numitems / 4) + 1;
4543
4544 for (i = 0; i < reps; i++)
4545 {
4546 glbCurLevel->reportMessage("The acid blob explodes violently!", tx, ty);
4547 ourEffectAttack = ATTACK_SPELL_CORROSIVEEXPLOSION;
4548 glbCurLevel->fireBall(tx, ty, radius, true,
4549 areaAttackCBStatic, &myself);
4550 // See if we are done.
4551 if (reps > 1 && (i == reps-1))
4552 glbCurLevel->reportMessage("The acid blob has dissipated.", tx, ty);
4553 }
4554
4555 return true;
4556 }
4557 // The creature cannot see its destination - corrosive explosion is
4558 // illegal!
4559 // (I had a very promising character die to a potion of blindness
4560 // on Klaskov's level when he had leap-attacked into the middle
4561 // of the orcs in preparation for a massive corrosive
4562 // explosion...)
4563 formatAndReport("%U <concentrate> in vain.");
4564 cancelSpell(spell);
4565 return true;
4566
4567 case SPELL_ACIDPOOL:
4568 if (!hasIntrinsic(INTRINSIC_BLIND) &&
4569 istargetlit &&
4570 glbCurLevel->hasLOS(getX(), getY(),
4571 tx, ty))
4572 {
4573 formatAndReport("%U <summon> a fountain of acid.");
4574 pietyCastSpell(spell);
4575 glbCurLevel->floodSquare(SQUARE_ACID, tx, ty, this);
4576 return true;
4577 }
4578
4579 // Creature can't see where to put the pool, not possible.
4580 formatAndReport("%U <concentrate> in vain.");
4581 cancelSpell(spell);
4582
4583 return true;
4584
4585 case SPELL_MINDACID:
4586 {
4587 ITEM *item;
4588
4589 // Determine if we have hands.
4590 if (!getSlotName(ITEMSLOT_RHAND))
4591 {
4592 formatAndReport("A blob of mind-acid appears beside %U and then dissipates.");
4593 return true;
4594 }
4595
4596 // Determine if we can disarm the main weapon.
4597 if (getEquippedItem(ITEMSLOT_RHAND))
4598 {
4599 actionDequip(ITEMSLOT_RHAND);
4600 item = getEquippedItem(ITEMSLOT_RHAND);
4601 if (item)
4602 {
4603 // Failed to dequip, likely due to curse or
4604 // out of inventory.
4605 formatAndReport("%R mind acid is blocked by %r %Iu.", item);
4606 return true;
4607 }
4608 }
4609
4610 pietyCastSpell(spell);
4611
4612 // The right hand is now free. Create our mind-acid
4613 // object.
4614 // We do not want artifact mind acid!
4615 item = ITEM::create(ITEM_MINDACIDHAND, false);
4616
4617 // 5 turns to dispell it.
4618 item->addCharges(5);
4619 // We don't want to have an enchanted hand, as that
4620 // may create a hand that does no damage.
4621 item->enchant(-item->getEnchantment());
4622 // Let user count down the charges.
4623 item->markChargesKnown();
4624
4625 buf.sprintf("Mind acid coats %s %s.",
4626 getPossessive(),
4627 getSlotName(ITEMSLOT_RHAND));
4628 reportMessage(buf);
4629 acquireItem(item, 0, ITEMSLOT_RHAND);
4630
4631 return true;
4632 }
4633
4634 case SPELL_DISINTEGRATE:
4635 {
4636 // Much like the hoe of destruction, does half damage to all
4637 // foes.
4638 if (!target)
4639 {
4640 reportMessage("The air boils as its contituent atoms are rent asunder.");
4641 return true;
4642 }
4643
4644 pietyCastSpell(spell);
4645 formatAndReport("%MR atoms are rent asunder by %R dread magic.", target);
4646
4647 // Build our own attack def.
4648 ATTACK_DEF attack;
4649 DICE dice;
4650 attack = glb_attackdefs[ATTACK_SPELL_DISINTEGRATE];
4651
4652 dice.myNumdie = 0;
4653 dice.mySides = 1;
4654 dice.myBonus = (target->getMaxHP() + 1) / 2;
4655 attack.damage = dice;
4656
4657 target->receiveDamage(&attack, this, 0, 0, ATTACKSTYLE_SPELL);
4658
4659 return true;
4660 }
4661
4662 case SPELL_FORCEBOLT:
4663 formatAndReport("A bolt of force slams forward.");
4664 ourZapSpell = spell;
4665 pietyCastSpell(spell);
4666
4667 if (targetself || dz)
4668 {
4669 if (dz)
4670 {
4671 buf.sprintf("The %s seems unaffected.",
4672 ((dz < 0) ? "floor" : "ceiling"));
4673 reportMessage(buf);
4674 }
4675 else
4676 {
4677 zapCallbackStatic(getX(), getY(), false, &myself);
4678 }
4679 }
4680 else
4681 {
4682 glbCurLevel->fireRay(getX(), getY(),
4683 dx, dy,
4684 1,
4685 MOVE_ALL, false,
4686 zapCallbackStatic,
4687 &myself);
4688
4689 glbCurLevel->knockbackMob(tx, ty, dx, dy, false);
4690 }
4691
4692 return true;
4693
4694 case SPELL_FORCEWALL:
4695 {
4696 formatAndReport("A wall of force builds around %U and then slams outwards.");
4697 ourZapSpell = spell;
4698
4699 pietyCastSpell(spell);
4700
4701 // No target, always run in all directions.
4702 int rad, numwalls;
4703 bool *lastwallstatus;
4704 int rdx = 0, rdy = 0, i, circle;
4705 int wdx, wdy, symbol = TILE_VOID;
4706 int cwdx = 0, cwdy = 0;
4707 int maxwalls = 25;
4708
4709 numwalls = 0;
4710
4711 lastwallstatus = new bool[11*11];
4712 memset(lastwallstatus, 0, sizeof(bool) * 11 * 11);
4713 // The current position of the avatar is marked as
4714 // having a force wall to start from.
4715 lastwallstatus[5*11+5] = true;
4716 for (rad = 1; rad < 6 && numwalls < maxwalls; rad++)
4717 {
4718 // Circle around...
4719 // 11112
4720 // 112 4 2
4721 // 4 2 -> 4 2
4722 // 433 4 2
4723 // 43333
4724 for (circle = 0; circle < 4 && numwalls < maxwalls; circle++)
4725 {
4726 for (i = 0; i < rad * 2 && numwalls < maxwalls; i++)
4727 {
4728 switch (circle)
4729 {
4730 case 0:
4731 rdy = 5 - rad;
4732 rdx = 5 - rad + i;
4733 break;
4734 case 1:
4735 rdx = 5 + rad;
4736 rdy = 5 - rad + i;
4737 break;
4738 case 2:
4739 rdx = 5 + rad - i;
4740 rdy = 5 + rad;
4741 break;
4742 case 3:
4743 rdx = 5 - rad;
4744 rdy = 5 + rad - i;
4745 break;
4746 }
4747
4748 // Disable the ray if it can't move into this square.
4749 if (!glbCurLevel->canRayMove(getX()+rdx-5,
4750 getY()+rdy-5,
4751 MOVE_STD_FLY,
4752 false))
4753 {
4754 continue;
4755 }
4756
4757 // rdx, rdy are now our entry into lastwallstatus.
4758 // We need to check if any previous direction
4759 // has a wall, and if so, what symbol we should use.
4760 // TODO: We should bias our direction based
4761 // on circle!
4762 symbol = TILE_VOID;
4763 for (wdy = -1; wdy <= 1; wdy++)
4764 {
4765 for (wdx = -1; wdx <= 1; wdx++)
4766 {
4767 if (!(wdy || wdx))
4768 continue;
4769 // Test if in bounds...
4770 if (rdx + wdx < 0 || rdx + wdx >= 11)
4771 continue;
4772 if (rdy + wdy < 0 || rdy + wdy >= 11)
4773 continue;
4774
4775 // Valid test, see if there was a wall here!
4776 if (lastwallstatus[rdx + wdx + (rdy+wdy)*11])
4777 {
4778 // Aesthetics: always replace slashes
4779 // with non slashes.
4780 if (wdx && wdy)
4781 {
4782 if (symbol != TILE_VOID)
4783 continue;
4784 if (wdx * wdy < 0)
4785 symbol = TILE_RAYSLASH;
4786 else
4787 symbol = TILE_RAYBACKSLASH;
4788 }
4789 else
4790 {
4791 if (wdx)
4792 symbol = TILE_RAYPIPE;
4793 else
4794 symbol = TILE_RAYDASH;
4795 }
4796 cwdx = wdx;
4797 cwdy = wdy;
4798 }
4799 }
4800 }
4801
4802
4803 if (symbol != TILE_VOID)
4804 {
4805 // We found a direction that is stored in
4806 // cwdx, cwdy. Note this is the *from*
4807 // direction.
4808 glbCurLevel->fireRay(getX() + rdx - 5 + cwdx,
4809 getY() + rdy - 5 + cwdy,
4810 -cwdx, -cwdy,
4811 1,
4812 MOVE_STD_FLY, false,
4813 zapCallbackStatic,
4814 &myself);
4815
4816 // Knock back mobs so they get hit again!
4817 glbCurLevel->knockbackMob(
4818 getX() + rdx - 5,
4819 getY() + rdy - 5,
4820 -cwdx, -cwdy,
4821 true);
4822
4823 // Mark this square as a valid source
4824 lastwallstatus[rdx+rdy*11] = true;
4825
4826 // Increment number of dudes
4827 numwalls++;
4828 }
4829 }
4830 }
4831 }
4832
4833 delete [] lastwallstatus;
4834
4835 return true;
4836 }
4837
4838 case SPELL_FROSTBOLT:
4839 formatAndReport("A bolt of ice speeds from %r hands.");
4840 ourZapSpell = spell;
4841
4842 pietyCastSpell(spell);
4843
4844 if (targetself || dz)
4845 {
4846 if (dz)
4847 reportMessage("The ray fizzles out.");
4848 else
4849 zapCallbackStatic(getX(), getY(), false, &myself);
4850 }
4851 else
4852 {
4853 glbCurLevel->fireRay(getX(), getY(),
4854 dx, dy,
4855 4,
4856 MOVE_STD_FLY, false,
4857 zapCallbackStatic,
4858 &myself);
4859 }
4860 return true;
4861
4862 case SPELL_LIVINGFROST:
4863 {
4864 // Determine if an frost could be there...
4865 tx = getX();
4866 ty = getY();
4867
4868 if (!glbCurLevel->findCloseTile(tx, ty, MOVE_STD_FLY))
4869 {
4870 glbCurLevel->reportMessage(
4871 "The air chills briefly.",
4872 tx, ty);
4873 return true;
4874 }
4875
4876 MOB *frost;
4877
4878 pietyCastSpell(spell);
4879
4880 frost = MOB::create(MOB_LIVINGFROST);
4881 // Summoned creatures never have inventory.
4882 frost->destroyInventory();
4883
4884 frost->move(tx, ty, true);
4885
4886 glbCurLevel->registerMob(frost);
4887
4888 frost->formatAndReport("%U <freeze> into existence!");
4889
4890 frost->makeSlaveOf(this);
4891 frost->setTimedIntrinsic(this, INTRINSIC_SUMMONED, 8);
4892
4893 return true;
4894 }
4895 case SPELL_BLIZZARD:
4896 {
4897 bool candetonate = false;
4898
4899 // Determine if the given coordinates are visable..
4900 // If we can sense the target by any means, we are allowed
4901 // to detonate it!
4902 if (target && ((target == this) || canSense(target)))
4903 candetonate = true;
4904
4905 if (!candetonate &&
4906 (!istargetlit ||
4907 !glbCurLevel->hasLOS(getX(), getY(),
4908 tx, ty)))
4909 {
4910 // Cannot see the position, illegal.
4911 formatAndReport("%U <gesture> into the darkness.");
4912 cancelSpell(spell);
4913 return true;
4914 }
4915
4916 // Check if no creature is there.
4917 if (!target)
4918 {
4919 formatAndReport("%U <try> to detonate thin air.");
4920 cancelSpell(spell);
4921 return true;
4922 }
4923
4924 // Verify the target is an undead.
4925 if (target->getDefinition() != MOB_LIVINGFROST)
4926 {
4927 formatAndReport("%MU <M:be> insufficiently cold to be a source.", target);
4928 return true;
4929 }
4930
4931 // Note we do *not* need to command the living frost!
4932 // Ice daemons are our friends :>
4933
4934 // Phew! We have a winner...
4935 formatAndReport("%U <unravel> the magics that stablize %MU.", target);
4936
4937 // Destroy the living frost.
4938 glbCurLevel->unregisterMob(target);
4939 target->triggerAsDeath(ATTACK_BLIZZARD_SOURCE, this, 0, false);
4940 delete target;
4941
4942 // Success!
4943 pietyCastSpell(spell);
4944
4945 // Create the blizzard.
4946 glbCurLevel->reportMessage("A large blizzard fills the air with sleet and snow!", tx, ty);
4947 ourEffectAttack = ATTACK_SPELL_BLIZZARD;
4948 glbCurLevel->fireBall(tx, ty, 3, true, areaAttackCBStatic, &myself);
4949
4950 return true;
4951 }
4952
4953 case SPELL_DIG:
4954 formatAndReport("%U magically <excavate> earth.");
4955 pietyCastSpell(spell);
4956
4957 if (!dz && (dx || dy) &&
4958 glbCurLevel->isDiggable(getX()+dx, getY()+dy))
4959 {
4960 MOBREF myself;
4961 myself.setMob(this);
4962
4963 ourEffectAttack = ATTACK_SPELL_SANDBLAST;
4964
4965 int angle = rand_dirtoangle(dx, dy);
4966 int rdx, rdy;
4967 int sx = getX(), sy = getY();
4968 rand_angletodir(angle+3, rdx, rdy);
4969 glbCurLevel->fireRay(sx+dx, sy+dy,
4970 rdx, rdy,
4971 1,
4972 MOVE_STD_FLY, false,
4973 areaAttackCBStatic,
4974 &myself);
4975 rand_angletodir(angle+5, rdx, rdy);
4976 glbCurLevel->fireRay(sx+dx, sy+dy,
4977 rdx, rdy,
4978 1,
4979 MOVE_STD_FLY, false,
4980 areaAttackCBStatic,
4981 &myself);
4982 if (!myself.getMob())
4983 return true;
4984 }
4985
4986 return actionDig(dx, dy, dz, 1, true);
4987
4988 case SPELL_CREATEPIT:
4989 // You can excavate anything you have LOS on, regardless
4990 // of sight rays.
4991 if (glbCurLevel->hasLOS(getX(), getY(), tx, ty))
4992 {
4993 pietyCastSpell(spell);
4994 formatAndReport("Dirt flies as %U <focus> %r will.");
4995
4996 glbCurLevel->digPit(tx, ty, this);
4997 return true;
4998 }
4999
5000 // The creature cannot see its destination, excavate
5001 // is illegal.
5002 formatAndReport("%U <concentrate> in vain.");
5003 cancelSpell(spell);
5004 return true;
5005
5006 case SPELL_SANDSTORM:
5007 {
5008 if ((!istargetlit ||
5009 !glbCurLevel->hasLOS(getX(), getY(),
5010 tx, ty)))
5011 {
5012 // Cannot see the position, illegal.
5013 formatAndReport("%U <gesture> into the darkness.");
5014 cancelSpell(spell);
5015 return true;
5016 }
5017
5018 // Determine if the given coordinates are valid..
5019 if (!glbCurLevel->isDiggable(tx, ty))
5020 {
5021 formatAndReport("There is nothing for %U to build a sandstorm from.");
5022 cancelSpell(spell);
5023 return true;
5024 }
5025
5026 // Phew! We have a winner...
5027 formatAndReport("%U <detonate> the rock and <wrap> it in a cyclone of air.");
5028
5029 // Success!
5030 pietyCastSpell(spell);
5031
5032 // Create the sandstorm.
5033 glbCurLevel->reportMessage("A sandstorm fills the air with abrasive dirt!", tx, ty);
5034 ourEffectAttack = ATTACK_SPELL_SANDSTORM;
5035 int wx, wy;
5036 glbCurLevel->getAmbientWindDirection(wx, wy);
5037 glbCurLevel->fireBall(tx+wx, ty+wy, 1, true, areaAttackCBStatic, &myself);
5038
5039 // Now dig!
5040 glbCurLevel->digSquare(tx, ty, myself.getMob());
5041
5042 return true;
5043 }
5044
5045 case SPELL_GROWFOREST:
5046 // You can grow anywhere you have LOS.
5047 if (glbCurLevel->hasLOS(getX(), getY(), tx, ty))
5048 {
5049 pietyCastSpell(spell);
5050 formatAndReport("Verdant forest springs from the ground.");
5051 glbCurLevel->growForest(tx, ty, this);
5052 return true;
5053 }
5054
5055 // The creature cannot see its destination, excavate
5056 // is illegal.
5057 formatAndReport("%U <concentrate> in vain.");
5058 cancelSpell(spell);
5059 return true;
5060
5061 case SPELL_ANIMATEFOREST:
5062 // You can grow anywhere you have LOS.
5063 if (glbCurLevel->hasLOS(getX(), getY(), tx, ty))
5064 {
5065 SQUARE_NAMES tile;
5066
5067 tile = glbCurLevel->getTile(tx, ty);
5068 if (tile == SQUARE_FORESTFIRE)
5069 {
5070 formatAndReport("The flames prevent %R magic from taking effect.");
5071 return true;
5072 }
5073 if (tile != SQUARE_FOREST)
5074 {
5075 formatAndReport("Without trees to focus on, %R animation magic fizzles.");
5076 return true;
5077 }
5078
5079 if (glbCurLevel->getMob(tx, ty))
5080 {
5081 formatAndReport("A creature's presences prevents the forest from awakening.");
5082 return true;
5083 }
5084
5085 formatAndReport("The forest comes to life!");
5086 glbCurLevel->setTile(tx, ty, SQUARE_CORRIDOR);
5087 MOB *tree;
5088
5089 // LIVINGTREE... as opposed to DEADTREE?
5090 // (This nitpick is made whilst half way between
5091 // TO and SF at some inordinate altitude)
5092 tree = MOB::create(MOB_LIVINGTREE);
5093 tree->destroyInventory();
5094 tree->move(tx, ty, true);
5095 glbCurLevel->registerMob(tree);
5096 tree->makeSlaveOf(this);
5097 tree->setTimedIntrinsic(this, INTRINSIC_SUMMONED, 30);
5098
5099 pietyCastSpell(spell);
5100 return true;
5101 }
5102
5103 // The creature cannot see its destination, excavate
5104 // is illegal.
5105 formatAndReport("%U <focus> in vain.");
5106 cancelSpell(spell);
5107 return true;
5108
5109 case SPELL_DOWNPOUR:
5110 // You can create a downpour where you have LOS.
5111 if (glbCurLevel->hasLOS(getX(), getY(), tx, ty))
5112 {
5113 pietyCastSpell(spell);
5114 formatAndReport("%U <summon> thick clouds that release a torrential downpour.");
5115 glbCurLevel->downPour(tx, ty, this);
5116 return true;
5117 }
5118
5119 // The creature cannot see its destination, excavate
5120 // is illegal.
5121 formatAndReport("%U <concentrate> in vain.");
5122 cancelSpell(spell);
5123 return true;
5124
5125 case SPELL_ROLLINGBOULDER:
5126 {
5127 SQUARE_NAMES tile;
5128 bool hadboulder = false;
5129 ITEM *boulder;
5130 MOB *victim;
5131 MOB *rollee = 0;
5132
5133 // You need to have LOS on the target wall.
5134 if (!glbCurLevel->hasLOS(getX(), getY(), tx, ty))
5135 {
5136 formatAndReport("%U <waste> %r energy.");
5137 return true;
5138 }
5139
5140 // Check to see that it is a wall...
5141 tile = glbCurLevel->getTile(tx, ty);
5142 switch (tile)
5143 {
5144 case SQUARE_EMPTY:
5145 case SQUARE_WALL:
5146 case SQUARE_SECRETDOOR:
5147 break;
5148 default:
5149 // Check to see if we have a boulder.
5150 boulder = glbCurLevel->getItem(tx, ty);
5151 if (boulder && boulder->getDefinition() == ITEM_BOULDER)
5152 {
5153 hadboulder = true;
5154 // Describe that the boulder starts to move..
5155 glbCurLevel->reportMessage("A large boulder starts to roll.", tx, ty);
5156
5157 }
5158 else
5159 {
5160 // Might be able to roll the monster
5161 rollee = glbCurLevel->getMob(tx, ty);
5162 if (!rollee)
5163 {
5164 formatAndReport("%U <try> to get rock from air.");
5165 return true;
5166 }
5167 if (rollee->getMaterial() != MATERIAL_STONE)
5168 {
5169 formatAndReport("%R magic slips off %MU.", rollee);
5170 return true;
5171 }
5172 // Pull them out of pits or water
5173 rollee->clearIntrinsic(INTRINSIC_INPIT);
5174 rollee->clearIntrinsic(INTRINSIC_SUBMERGED);
5175 }
5176 break;
5177 }
5178
5179 pietyCastSpell(spell);
5180
5181 // The wall in question turns into a boulder.
5182 // Unless there was already a boulder.
5183 if (!hadboulder && !rollee)
5184 {
5185 boulder = ITEM::create(ITEM_BOULDER, false, true);
5186 glbCurLevel->setTile(tx, ty, SQUARE_CORRIDOR);
5187
5188 // We do the message after the change so we might get
5189 // a map update...
5190 glbCurLevel->reportMessage("A large boulder is carved out of the wall.", tx, ty);
5191
5192 // In case acquiring does something odd..
5193 glbCurLevel->acquireItem(boulder, tx, ty, this);
5194 }
5195
5196 // Now, try to roll the boulder.
5197 // First, determine the likely direction.
5198 int bdx, bdy, range;
5199 bool cansee;
5200 MOBREF thismob;
5201 ITEM *blocker;
5202 int oldoverlay;
5203
5204 thismob.setMob(this);
5205
5206 bdx = getX() - tx;
5207 bdy = getY() - ty;
5208
5209 // If one is bigger than the other, zero the other.
5210 // (ie, usually do horizontal moves)
5211 if (abs(bdx) > abs(bdy))
5212 bdy = 0;
5213 else if (abs(bdx) < abs(bdy))
5214 bdx = 0;
5215
5216 bdx = SIGN(bdx);
5217 bdy = SIGN(bdy);
5218
5219 // If we are casting on ourself (perhaps to escape
5220 // entombment?) point randomly.
5221 if (!bdx && !bdy)
5222 rand_direction(bdx, bdy);
5223
5224 oldoverlay = -1;
5225
5226 for (range = 0; range < 5; range++)
5227 {
5228 // Let the user see the boulder here...
5229 cansee = glbCurLevel->getFlag(tx, ty, SQUAREFLAG_FOV);
5230
5231 // Retrieve the boulder...
5232 if (rollee)
5233 {
5234 MOB *nmob = glbCurLevel->getMob(tx, ty);
5235 // Check if died or fell in a hole
5236 if (nmob != rollee)
5237 break;
5238 // If they are in a pit or submerged stop them
5239 if (rollee->hasIntrinsic(INTRINSIC_INPIT) ||
5240 rollee->hasIntrinsic(INTRINSIC_SUBMERGED))
5241 break;
5242 }
5243 else
5244 {
5245 boulder = glbCurLevel->getItem(tx, ty);
5246 // The boulder may have falling into a pit or what not...
5247 if (!boulder || boulder->getDefinition() != ITEM_BOULDER)
5248 break;
5249 }
5250
5251 victim = glbCurLevel->getMob(tx+bdx, ty+bdy);
5252 blocker = glbCurLevel->getItem(tx+bdx, ty+bdy);
5253
5254 // Verify the boulder can move...
5255 // Huge creatures just have boulder hit their feet
5256 // for no damage!
5257 if (!glbCurLevel->canMove(tx+bdx, ty+bdy, MOVE_STD_FLY) ||
5258 (victim && victim->getSize() >= SIZE_GARGANTUAN) ||
5259 (blocker && !blocker->isPassable()))
5260 {
5261 if (rollee)
5262 rollee->formatAndReport("%U <shudder> to a stop.");
5263 else
5264 glbCurLevel->reportMessage("The boulder shudders to a stop.", tx, ty);
5265 break;
5266 }
5267
5268 // Erase old overlay...
5269 if (oldoverlay >= 0)
5270 {
5271 gfx_setoverlay(tx, ty, oldoverlay);
5272 }
5273
5274 // Move the boulder...
5275 if (rollee)
5276 {
5277 tx += bdx;
5278 ty += bdy;
5279
5280 // And put the item in as the new overlay.
5281 if (cansee)
5282 {
5283 oldoverlay = gfx_getoverlay(tx, ty);
5284 gfx_setoverlay(tx, ty, rollee->getTile());
5285 gfx_sleep(6);
5286 }
5287 else
5288 oldoverlay = -1;
5289
5290 // Crush the poor person!
5291 victim = glbCurLevel->getMob(tx, ty);
5292 if (victim)
5293 {
5294 rollee->actionAttack(bdx, bdy, 1);
5295 break;
5296 }
5297 if (!rollee->move(tx, ty))
5298 break;
5299 }
5300 else
5301 {
5302 glbCurLevel->dropItem(boulder);
5303 tx += bdx;
5304 ty += bdy;
5305
5306 // And put the item in as the new overlay.
5307 if (cansee)
5308 {
5309 oldoverlay = gfx_getoverlay(tx, ty);
5310 gfx_setoverlay(tx, ty, TILE_BOULDER);
5311 gfx_sleep(6);
5312 }
5313 else
5314 oldoverlay = -1;
5315
5316 // Crush the poor person!
5317 victim = glbCurLevel->getMob(tx, ty);
5318 if (victim)
5319 {
5320 victim->receiveAttack(ATTACK_ROLLINGBOULDER,
5321 thismob.getMob(), boulder, 0,
5322 ATTACKSTYLE_MISC);
5323 }
5324
5325 glbCurLevel->acquireItem(boulder, tx, ty, thismob.getMob());
5326 }
5327 }
5328 // Clean up...
5329 if (oldoverlay >= 0)
5330 {
5331 gfx_setoverlay(tx, ty, oldoverlay);
5332 }
5333
5334 return true;
5335 }
5336
5337 case SPELL_ENTOMB:
5338 {
5339 int tile;
5340 bool anyeffect = false;
5341
5342 for (dy = -1; dy <= 1; dy++)
5343 {
5344 if (ty+dy < 0 || ty+dy >= MAP_HEIGHT)
5345 continue;
5346 for (dx = -1; dx <= 1; dx++)
5347 {
5348 if (tx+dx < 0 || tx+dx >= MAP_HEIGHT)
5349 continue;
5350
5351 if (!dx && !dy)
5352 continue;
5353
5354 // Change this tile into solid rock, if possible.
5355
5356 // Can't entomb critters.
5357 if (glbCurLevel->getMob(tx + dx, ty + dy))
5358 continue;
5359
5360 tile = glbCurLevel->getTile(tx+dx, ty+dy);
5361
5362 if (glb_squaredefs[tile].invulnerable)
5363 continue;
5364
5365 // Set the tile.
5366 if (tile != SQUARE_EMPTY)
5367 anyeffect = true;
5368 glbCurLevel->setTile(tx+dx, ty+dy, SQUARE_EMPTY);
5369 }
5370 }
5371
5372 if (!anyeffect)
5373 {
5374 formatAndReport("%U <feel> the ground shudder. Blocked, the rock summoned from the plane of earth returs whence it came.");
5375 }
5376 else
5377 {
5378 formatAndReport("Solid rock rises from the ground, entombing %U.");
5379 pietyCastSpell(spell);
5380 }
5381
5382 return true;
5383 }
5384
5385
5386 case SPELL_FIREBALL:
5387 ourZapSpell = spell;
5388 ourFireBallCount = 0;
5389
5390 pietyCastSpell(spell);
5391
5392 if (targetself || dz)
5393 {
5394 if (dz)
5395 {
5396 buf.sprintf("The ray hits the %s.", directionflavour);
5397 reportMessage(buf);
5398 }
5399 zapCallbackStatic(getX(), getY(), true, &myself);
5400 }
5401 else
5402 {
5403 // After much soul searching, the bouncing fireballs
5404 // have been axed during a flight over the atlantic, this
5405 // time on return form IRDC'09 which was held at CERN.
5406 // For reasons of economy, however, my own departure is a few
5407 // weeks later and via Paris.
5408 // Due to this airbus 320 lacking any power outlet, I am
5409 // at the mercy of the p1610's 5 hours of battery life.
5410 // Since I also plan on playing some Civ 4, this session
5411 // must be truncated to fit withing the 8 hour flight.
5412 glbCurLevel->fireRay(getX(), getY(),
5413 dx, dy,
5414 6,
5415 MOVE_STD_FLY, false,
5416 zapCallbackStatic,
5417 &myself);
5418 }
5419 return true;
5420
5421 case SPELL_LIGHTNINGBOLT:
5422 formatAndReport("Lightning flies from %r hand.");
5423
5424 pietyCastSpell(spell);
5425
5426 ourZapSpell = spell;
5427 if (targetself || dz)
5428 {
5429 if (dz)
5430 {
5431 reportMessage("The ray bounces.");
5432 }
5433 zapCallbackStatic(getX(), getY(), false, &myself);
5434 }
5435 else
5436 {
5437 glbCurLevel->fireRay(getX(), getY(),
5438 dx, dy,
5439 6,
5440 MOVE_STD_FLY, true,
5441 zapCallbackStatic,
5442 &myself);
5443 }
5444 return true;
5445
5446 case SPELL_CHAINLIGHTNING:
5447 pietyCastSpell(spell);
5448
5449 ourZapSpell = spell;
5450 ourFireBallCount = 0;
5451 if (targetself || dz)
5452 {
5453 if (dz)
5454 {
5455 reportMessage("The ray bounces.");
5456 }
5457 zapCallbackStatic(getX(), getY(), false, &myself);
5458 }
5459 else
5460 {
5461 glbCurLevel->fireRay(getX(), getY(),
5462 dx, dy,
5463 6,
5464 MOVE_STD_FLY, true,
5465 zapCallbackStatic,
5466 &myself);
5467 }
5468 return true;
5469
5470 case SPELL_SUNFIRE:
5471 {
5472 int delay;
5473
5474 formatAndReport("%U <talk> to the sun...");
5475
5476 if (hasIntrinsic(INTRINSIC_TIRED))
5477 {
5478 formatAndReport("The sun ignores %r prayers!");
5479 return true;
5480 }
5481
5482 pietyCastSpell(spell);
5483
5484 // Report that the sun answers...
5485 formatAndReport("The sun answers %r prayers!");
5486 reportMessage("Darkness falls on the surface world as the sun's "
5487 "energies are focussed.");
5488
5489 reportMessage("A blinding blast of light falls from the roof.");
5490
5491 // First we blast everyone in the area with the sunfire
5492 // blast.
5493 ourEffectAttack = ATTACK_SUNFIREBLAST;
5494 glbCurLevel->fireBall(getX(), getY(), 3, false,
5495 areaAttackCBStatic, &myself);
5496
5497 // Next, melt the rocks.
5498 reportMessage("The very rocks melt!");
5499 glbCurLevel->fireBall(getX(), getY(), 3, false,
5500 meltRocksCBStatic, &myself);
5501
5502 delay = rand_dice(20, 20, 20);
5503 setTimedIntrinsic(this, INTRINSIC_TIRED, delay);
5504 return true;
5505 }
5506
5507 case SPELL_REGENERATE:
5508 if (!target || ((target != this) && !canSense(target)))
5509 {
5510 formatAndReport("%U <cast> at thin air.");
5511 cancelSpell(spell);
5512 return true;
5513 }
5514
5515 // You get 30 + 3d10 turns of REGENERATION.
5516 // You get 5 + 1d10 turns of MAGICDRAIN.
5517 target->setTimedIntrinsic(this, INTRINSIC_REGENERATION,
5518 rand_dice(3, 10, 30));
5519 target->setTimedIntrinsic(this, INTRINSIC_MAGICDRAIN,
5520 rand_dice(1, 10, 5));
5521
5522 pietyCastSpell(spell);
5523
5524 target->formatAndReport("%U <be> surrounded by a healthy aura.");
5525 return true;
5526
5527 case SPELL_HEAL:
5528 if (!target || ((target != this) && !canSense(target)))
5529 {
5530 formatAndReport("%U <cast> at thin air.");
5531 cancelSpell(spell);
5532 return true;
5533 }
5534 if (target->receiveHeal(10, this))
5535 {
5536 target->formatAndReport("%R wounds close.");
5537
5538 pietyCastSpell(spell);
5539 }
5540 else
5541 {
5542 target->formatAndReport("%U <look> no better.");
5543 }
5544 return true;
5545
5546 case SPELL_SLOWPOISON:
5547 if (!target || ((target != this) && !canSense(target)))
5548 {
5549 formatAndReport("%U <cast> at thin air.");
5550 cancelSpell(spell);
5551 return true;
5552 }
5553 #if 0
5554 if (target->receiveSlowPoison())
5555 {
5556 target->formatAndReport("The poison slows in %R veins.");
5557
5558 pietyCastSpell(spell);
5559 }
5560 else
5561 {
5562 target->formatAndReport("%U <be> not poisoned.");
5563 }
5564 #else
5565 // We grant poison resistance for a 5 + 1d5 turns.
5566 target->formatAndReport("%R metabolism stabilizes.");
5567 target->setTimedIntrinsic(this, INTRINSIC_RESISTPOISON,
5568 rand_dice(1, 5, 6));
5569 #endif
5570 return true;
5571
5572 case SPELL_MAJORHEAL:
5573 if (!target || ((target != this) && !canSense(target)))
5574 {
5575 formatAndReport("%U <cast> at thin air.");
5576 cancelSpell(spell);
5577 return true;
5578 }
5579 if (target->receiveHeal(30, this))
5580 {
5581 target->formatAndReport("%R wounds close.");
5582
5583 pietyCastSpell(spell);
5584 }
5585 else
5586 {
5587 target->formatAndReport("%U <look> no better.");
5588 }
5589 return true;
5590
5591 case SPELL_CUREPOISON:
5592 if (!target || ((target != this) && !canSense(target)))
5593 {
5594 formatAndReport("%U <cast> at thin air.");
5595 cancelSpell(spell);
5596 return true;
5597 }
5598 if (target->receiveCure())
5599 {
5600 formatAndReport("The poison is expunged from %MR veins.", target);
5601 pietyCastSpell(spell);
5602 }
5603 else
5604 {
5605 formatAndReport("%M <M:be> not poisoned.", target);
5606 }
5607 return true;
5608
5609 case SPELL_RESURRECT:
5610 // Determine the corpse that is pointed at...
5611 if (target)
5612 {
5613 target->formatAndReport("%U <be> not in need of aid.");
5614 cancelSpell(spell);
5615 return true;
5616 }
5617
5618 // Resurrect all corpses at the target square...
5619 if (!glbCurLevel->resurrectCorpses(tx, ty, this))
5620 {
5621 formatAndReport("The stones fail to come to life.");
5622 return true;
5623 }
5624 else
5625 pietyCastSpell(spell);
5626
5627 return true;
5628
5629 case SPELL_SUMMON_FAMILIAR:
5630 {
5631 // Find a location for the familiar
5632 tx = getX();
5633 ty = getY();
5634
5635 if (!glbCurLevel->findCloseTile(tx, ty, MOVE_WALK))
5636 {
5637 glbCurLevel->reportMessage(
5638 "A famliar briefly appears before thinking better of it.",
5639 tx, ty);
5640 return true;
5641 }
5642
5643 MOB *familiar;
5644
5645 familiar = MOB::create(MOB_BAT);
5646
5647 // Bats always start with their full possible health.
5648 // No reason to be evil with 1 HP bats :>
5649 familiar->forceHP(10);
5650
5651 // Likewise, they get some magic points to play with
5652 familiar->incrementMaxMP(10 - familiar->getMaxMP());
5653
5654 // Summoned creatures never have inventory.
5655 familiar->destroyInventory();
5656
5657 familiar->move(tx, ty, true);
5658
5659 glbCurLevel->registerMob(familiar);
5660
5661 familiar->formatAndReport("%U <come> to %MR call!", this);
5662
5663 familiar->makeSlaveOf(this);
5664 familiar->setIntrinsic(INTRINSIC_FAMILIAR);
5665
5666 pietyCastSpell(spell);
5667
5668 return true;
5669 }
5670
5671 case SPELL_TRANSFER_KNOWLEDGE:
5672 {
5673 if (!target || !canSense(target))
5674 {
5675 // Unable to transfer to destination.
5676 formatAndReport("%U watch helplessly as %R hard earned experience evaporates.");
5677 return true;
5678 }
5679 target->formatAndReport("%U <be> wiser.");
5680 // Thanks to the pyramid scheme, you will get half the experience
5681 // back. This might thus be too powerful.
5682 target->receiveExp(250);
5683
5684 pietyCastSpell(spell);
5685 return true;
5686 }
5687
5688 case SPELL_FETCH:
5689 {
5690 bool somethinghappened = false;
5691 int i;
5692 ITEM *item;
5693
5694 // You don't need visible coordinates.
5695 // You do need to either recall the item at the coordinates
5696 // or sense the creature at the coordinates.
5697 if (target && canSense(target))
5698 {
5699 int fx, fy;
5700
5701 // Summon the creature to your presence!
5702 formatAndReport("%U <summon> %MU.", target);
5703
5704 // We also want the summoned creature to know what happened.
5705 target->formatAndReport("%U <be> summoned to %MR presence.", this);
5706
5707 // Find the closest valid square to us.
5708 fx = getX();
5709 fy = getY();
5710 glbCurLevel->findCloseTile(fx, fy, target->getMoveType());
5711
5712 target->actionTeleport(false, true, fx, fy);
5713 somethinghappened = true;
5714 }
5715
5716 ITEMSTACK stack;
5717
5718 // Return a list of all mapped items.
5719 glbCurLevel->getItemStack(stack, tx, ty, -1, true);
5720 for (i = 0; i < stack.entries(); i++)
5721 {
5722 item = stack(i);
5723 if (item->hasIntrinsic(INTRINSIC_TELEFIXED))
5724 {
5725 item->formatAndReport("%U <shudder>.");
5726 continue;
5727 }
5728 formatAndReport("%U <fetch> %IU.", item);
5729 glbCurLevel->dropItem(item);
5730 glbCurLevel->acquireItem(item, getX(), getY(), this);
5731 somethinghappened = true;
5732 }
5733
5734 if (!somethinghappened)
5735 {
5736 formatAndReport("%R summons go unheeded.");
5737 }
5738 else
5739 {
5740 pietyCastSpell(spell);
5741 }
5742 return true;
5743 }
5744
5745
5746 case SPELL_BLINK:
5747 // Determine if the given coordinates are visable..
5748 if (!hasIntrinsic(INTRINSIC_BLIND) &&
5749 istargetlit &&
5750 glbCurLevel->hasLOS(getX(), getY(),
5751 tx, ty))
5752 {
5753 if (target && target != this)
5754 {
5755 formatAndReport("%U <stare> at %MU.", target);
5756 return true;
5757 }
5758 else
5759 {
5760 pietyCastSpell(spell);
5761 return actionTeleport(false, true,
5762 tx, ty);
5763 }
5764 }
5765
5766 // The creature cannot see its destination - blink is
5767 // illegal!
5768 formatAndReport("%U <stare> into darkness.");
5769 cancelSpell(spell);
5770 return true;
5771
5772 case SPELL_FLAMESTRIKE:
5773 if (target && ((target == this) || canSense(target)))
5774 {
5775 formatAndReport("%U <call> on %r god's power!");
5776
5777 pietyCastSpell(spell);
5778 if (isAvatar())
5779 {
5780 // Compute our piety...
5781 ATTACK_DEF attack;
5782 memcpy(&attack, &glb_attackdefs[ATTACK_FLAMESTRIKE], sizeof(ATTACK_DEF));
5783 // Every doubling of piety adds another die.
5784 // Zero or less piety gets no dice
5785 // We know we are capped at 10k, or 2^13 for an awesome
5786 // 13d6 attack.
5787 int piety = piety_chosengodspiety();
5788 attack.damage.myNumdie = 0;
5789 while (piety > 1)
5790 {
5791 attack.damage.myNumdie++;
5792 piety >>= 1;
5793 }
5794 target->receiveAttack(&attack, this, 0, 0,
5795 ATTACKSTYLE_SPELL);
5796 }
5797 else
5798 target->receiveAttack(ATTACK_FLAMESTRIKE, this, 0, 0,
5799 ATTACKSTYLE_SPELL);
5800 }
5801 else
5802 {
5803 formatAndReport("%U foolishly <ask> %r god to strike thin air.");
5804 cancelSpell(spell);
5805 }
5806 return true;
5807
5808 case SPELL_TELEPORT:
5809 pietyCastSpell(spell);
5810 return actionTeleport();
5811
5812 case SPELL_TELEWITHCONTROL:
5813 pietyCastSpell(spell);
5814 return actionTeleport(true, true);
5815
5816 case SPELL_DETECTCURSE:
5817 {
5818 // All items in our inventory have their status marked.
5819 // Only get credit if this changed something.
5820 if (!isAvatar())
5821 return true;
5822 ITEM *item;
5823 bool didid = false;
5824
5825 formatAndReport("%U <become> attuned to %r possessions...");
5826
5827 for (item = myInventory; item; item = item->getNext())
5828 {
5829 if (!item->isKnownCursed())
5830 {
5831 const char *colour = "grey";
5832 if (item->isBlessed())
5833 colour = "white";
5834 else if (item->isCursed())
5835 colour = "black";
5836
5837 formatAndReport("%R %Iu <I:glow> %B1.", item, colour);
5838 item->markCursedKnown();
5839 didid = true;
5840 }
5841 }
5842 if (didid)
5843 {
5844 pietyCastSpell(spell);
5845 }
5846 else
5847 {
5848 formatAndReport("%U <sense> nothing new.");
5849 }
5850 return true;
5851 }
5852
5853 case SPELL_IDENTIFY:
5854 {
5855 // This had better be the player.
5856 UT_ASSERT(isAvatar());
5857 if (!isAvatar())
5858 return true;
5859
5860 // No UI during stress test.
5861 if (glbStressTest)
5862 return true;
5863
5864 // Select item to identify.
5865 int selectx, selecty;
5866 ITEM *item = 0;
5867
5868 gfx_showinventory(this);
5869 gfx_getinvcursor(selectx, selecty);
5870 if (gfx_selectinventory(selectx, selecty))
5871 {
5872 item = getItem(selectx, selecty);
5873 }
5874
5875 gfx_hideinventory();
5876 writeGlobalActionBar(true);
5877
5878 // We perform the identify after hiding the inventory
5879 // so we don't lose the final line of the identification
5880 // to the inventory clear.
5881 if (item)
5882 {
5883 item->markIdentified();
5884 formatAndReport("%U <identify> %IU.", item);
5885 pietyCastSpell(spell);
5886 }
5887 else
5888 {
5889 // Undo cost...
5890 cancelSpell(spell);
5891 return false;
5892 }
5893
5894 return true;
5895 }
5896
5897 case SPELL_LIGHT:
5898 formatAndReport("Light spreads out from %U.");
5899
5900 pietyCastSpell(spell);
5901 glbCurLevel->applyFlag(SQUAREFLAG_LIT,
5902 getX() + dx,
5903 getY() + dy,
5904 5, false, true);
5905 return true;
5906
5907 case SPELL_DARKNESS:
5908 formatAndReport("Darkness spreads out from %U.");
5909
5910 pietyCastSpell(spell);
5911 glbCurLevel->applyFlag(SQUAREFLAG_LIT,
5912 getX() + dx,
5913 getY() + dy,
5914 5, false, false);
5915 return true;
5916
5917 case SPELL_MAGICMAP:
5918 // Only applicable to avatar.
5919 if (!isAvatar())
5920 return true;
5921
5922 formatAndReport("A map forms in %r mind");
5923 pietyCastSpell(spell);
5924 glbCurLevel->markMapped(getX() + dx, getY() + dy,
5925 10, 10,
5926 false);
5927 return true;
5928
5929 case SPELL_KNOCK:
5930 if (!hasIntrinsic(INTRINSIC_BLIND) &&
5931 istargetlit &&
5932 glbCurLevel->hasLOS(getX(), getY(),
5933 tx, ty))
5934 {
5935 SQUARE_NAMES tile;
5936
5937 // Determine if there is a door or secret door
5938 // in the targetted square...
5939 tile = glbCurLevel->getTile(tx, ty);
5940
5941 switch (tile)
5942 {
5943 case SQUARE_OPENDOOR:
5944 {
5945 // Find a dx/dy.
5946 int dx, dy;
5947 MOB *mob;
5948 ITEM *item;
5949
5950 // Knock is always away from caster
5951 dx = SIGN(tx - getX());
5952 dy = SIGN(ty - getY());
5953
5954 if (glbCurLevel->canMove(tx+dx,ty,MOVE_STD_FLY,false))
5955 {
5956 dy = 0;
5957 }
5958 else if (glbCurLevel->canMove(tx,ty+dy,MOVE_STD_FLY,false))
5959 {
5960 dx = 0;
5961 }
5962 else
5963 {
5964 dy = dx = 0;
5965 }
5966
5967 if (dx || dy)
5968 {
5969 // Check to see if a creature or item
5970 // blocks the square.
5971 mob = glbCurLevel->getMob(tx, ty);
5972 if (mob)
5973 {
5974 if (glbCurLevel->knockbackMob(tx, ty, dx, dy, true))
5975 {
5976 mob = glbCurLevel->getMob(tx+dx, ty+dy);
5977 if (mob)
5978 mob->receiveDamage(
5979 ATTACK_DOORSLAM,
5980 this,
5981 0, 0,
5982 ATTACKSTYLE_SPELL);
5983 }
5984 }
5985 }
5986
5987 // Check for any blockers.
5988 mob = glbCurLevel->getMob(tx, ty);
5989 if (mob)
5990 {
5991 mob->formatAndReport("%U <hold> the door open!");
5992 return true;
5993 }
5994
5995 // Check to see if there is an item.
5996 item = glbCurLevel->getItem(tx, ty);
5997 if (item)
5998 {
5999 formatAndReport("The door is blocked by %IU.", item);
6000 return true;
6001 }
6002
6003 reportMessage("The door closes.");
6004 glbCurLevel->setTile(tx, ty, SQUARE_DOOR);
6005 pietyCastSpell(spell);
6006 break;
6007 }
6008 case SQUARE_DOOR:
6009 case SQUARE_BLOCKEDDOOR:
6010 reportMessage("The door opens.");
6011 glbCurLevel->setTile(tx, ty, SQUARE_OPENDOOR);
6012 pietyCastSpell(spell);
6013 break;
6014 case SQUARE_MAGICDOOR:
6015 reportMessage("A voice booms: \"My test is not so easily bypassed!\"");
6016 break;
6017 case SQUARE_SECRETDOOR:
6018 reportMessage("A secret door opens.");
6019 glbCurLevel->setTile(tx, ty, SQUARE_OPENDOOR);
6020 pietyCastSpell(spell);
6021 break;
6022
6023 default:
6024 reportMessage("Nothing happens.");
6025 break;
6026 }
6027 return true;
6028 }
6029 formatAndReport("%U <hear> a distant knock.");
6030 return true;
6031
6032 case SPELL_TRACK:
6033 // Determine if there is a creature there that
6034 // we can sense.
6035 if (target && ((target == this) || canSense(target)))
6036 {
6037 formatAndReport("%U <build> a standing wave of magic.");
6038 target->formatAndReport("%U <be> revealed to all.");
6039
6040 target->setTimedIntrinsic(this,
6041 INTRINSIC_POSITIONREVEALED,
6042 100);
6043
6044 pietyCastSpell(spell);
6045 }
6046 else
6047 {
6048 formatAndReport("%U <try> to track the wind.");
6049 cancelSpell(spell);
6050 }
6051 return true;
6052
6053 case SPELL_DIAGNOSE:
6054 // Determine if there is a creature there that
6055 // we can sense.
6056 if (target && ((target == this) || canSense(target)))
6057 {
6058 formatAndReport("%U <peer> into %MU with your magic.", target);
6059
6060 target->characterDump(false, false, false);
6061
6062 pietyCastSpell(spell);
6063 }
6064 else
6065 {
6066 formatAndReport("%U <find> the empty air in surprisingly good condition.");
6067 cancelSpell(spell);
6068 }
6069 return true;
6070
6071 case SPELL_POSSESS:
6072 {
6073 if (target)
6074 {
6075 // Control target
6076 // Attempt a mental roll.
6077 int check = smartsCheck(target->getSmarts());
6078
6079 switch (check)
6080 {
6081 case -1:
6082 // Force failure.
6083 formatAndReport("%MU easily <M:rebuff> %R attempt at control.", target);
6084 break;
6085 case 0:
6086 // Failure, but could work.
6087 formatAndReport("%MU <M:rebuff> %R attempt at control.", target);
6088 break;
6089 case 1:
6090 // Success, but could fail.
6091 formatAndReport("%U <overpower> %MR mental defences.", target);
6092 break;
6093 case 2:
6094 // Success always
6095 formatAndReport("%U easily <overpower> %MR mental defences.", target);
6096 break;
6097 default:
6098 UT_ASSERT(!"Invalid smarts check");
6099 break;
6100 }
6101 if (check >= 1)
6102 {
6103 actionPossess(target, rand_range(50, 100));
6104 target->pietyCastSpell(spell);
6105 }
6106 else
6107 {
6108 // Still get noticed by the gods, as this is equivalent
6109 // to missing.
6110 pietyCastSpell(spell);
6111 }
6112 }
6113 else
6114 {
6115 formatAndReport("%U <find> nothing to possess.");
6116 cancelSpell(spell);
6117 }
6118 break;
6119 }
6120
6121 case SPELL_PRESERVE:
6122 {
6123 ITEMSTACK stack;
6124 bool anythinghappen = false;
6125
6126 formatAndReport("%U <form> a stasis field.");
6127
6128 if (target)
6129 {
6130 // Preserve the target.
6131 target->formatAndReport("%U <be> encased in a purple glow.");
6132
6133 // Designed to be long enough to prevent stoning.
6134 target->setTimedIntrinsic(this,
6135 INTRINSIC_UNCHANGING,
6136 6);
6137
6138 anythinghappen = true;
6139 }
6140
6141 glbCurLevel->getItemStack(stack, tx, ty);
6142 n = stack.entries();
6143 for (i = 0; i < n; i++)
6144 {
6145 // If this is a corpse, set its charges to 255
6146 // to preserve it.
6147 if (stack(i)->getDefinition() == ITEM_BONES ||
6148 stack(i)->getDefinition() == ITEM_CORPSE)
6149 {
6150 stack(i)->formatAndReport("%U <be> encased in a purple glow.");
6151 stack(i)->setAsPreserved();
6152 anythinghappen = true;
6153 }
6154 }
6155
6156 if (!anythinghappen)
6157 reportMessage("Nothing happens.");
6158 else
6159 pietyCastSpell(spell);
6160 return true;
6161 }
6162
6163 case SPELL_PETRIFY:
6164 {
6165 bool anythinghappen = false;
6166 ITEMSTACK stack;
6167
6168 if (target)
6169 {
6170 // Petrify the target.
6171 anythinghappen = true;
6172 formatAndReport("A layer of dust covers %MU.", target);
6173 if (target->hasIntrinsic(INTRINSIC_RESISTSTONING))
6174 {
6175 formatAndReport("The dust falls to the floor.");
6176 }
6177 else
6178 target->setTimedIntrinsic(this,
6179 INTRINSIC_STONING,
6180 3);
6181 }
6182
6183 glbCurLevel->getItemStack(stack, tx, ty);
6184 n = stack.entries();
6185 for (i = 0; i < n; i++)
6186 anythinghappen |= stack(i)->petrify(glbCurLevel, this);
6187
6188 if (!anythinghappen)
6189 reportMessage("Nothing happens.");
6190 else pietyCastSpell(spell);
6191
6192 return true;
6193 }
6194
6195 case SPELL_STONETOFLESH:
6196 {
6197 bool anythinghappen = false;
6198 ITEMSTACK stack;
6199 SQUARE_NAMES tile;
6200
6201 if (target)
6202 {
6203 anythinghappen = true;
6204 formatAndReport("A layer of dandruff covers %MU.", target);
6205
6206 // Grant temporary stoning resistance.
6207 target->setTimedIntrinsic(this, INTRINSIC_RESISTSTONING,
6208 rand_dice(1, 5, 6));
6209
6210 // UnPetrify the target. This is bad news
6211 // for earth elementals :>
6212 target->actionUnPetrify();
6213 }
6214
6215 glbCurLevel->getItemStack(stack, tx, ty);
6216 n = stack.entries();
6217 for (i = 0; i < n; i++)
6218 anythinghappen |= stack(i)->unpetrify(glbCurLevel, this);
6219
6220 // Check to see if the tile is stone, in which case
6221 // we turn it to a mound of flesh.
6222 tile = glbCurLevel->getTile(tx, ty);
6223
6224 switch (tile)
6225 {
6226 case SQUARE_WALL:
6227 case SQUARE_EMPTY:
6228 case SQUARE_SECRETDOOR:
6229 {
6230 ITEM *flesh;
6231
6232 anythinghappen = true;
6233 glbCurLevel->reportMessage("The wall slumps into a large mound of flesh.", tx, ty);
6234 glbCurLevel->setTile(tx, ty, SQUARE_CORRIDOR);
6235 flesh = ITEM::create(ITEM_MOUNDFLESH, false, true);
6236 glbCurLevel->acquireItem(flesh, tx, ty, this);
6237
6238 break;
6239 }
6240
6241 default:
6242 break;
6243 }
6244
6245 if (!anythinghappen)
6246 reportMessage("Nothing happens.");
6247 else pietyCastSpell(spell);
6248
6249 return true;
6250 }
6251
6252 case SPELL_DIRECTWIND:
6253 {
6254 // I have never understood wind names.
6255 const char *direction[9] =
6256 { "southeast", "south", "southwest",
6257 "east", "invalid", "west",
6258 "northeast", "north", "northwest" };
6259 const char *windname;
6260 int windidx;
6261
6262 windidx = (dx + 1) + (dy + 1) * 3;
6263 windname = direction[windidx];
6264
6265 if (dx || dy)
6266 {
6267 buf.sprintf("A strong wind blows from the %s. ",
6268 windname);
6269 }
6270 else
6271 {
6272 buf.reference("The air stills to an unnatural calm. ");
6273 }
6274 // Global message.
6275 msg_report(buf);
6276 glbCurLevel->setWindDirection(dx, dy, 20 + rand_choice(20));
6277
6278 pietyCastSpell(spell);
6279 break;
6280 }
6281
6282 case SPELL_RAISE_UNDEAD:
6283 {
6284 // Determine if the given coordinates are visable..
6285 if (hasIntrinsic(INTRINSIC_BLIND) ||
6286 !istargetlit ||
6287 !glbCurLevel->hasLOS(getX(), getY(),
6288 tx, ty))
6289 {
6290 // Cannot see the position, illegal.
6291 formatAndReport("%U <gesture> into the darkness.");
6292 cancelSpell(spell);
6293 return true;
6294 }
6295
6296 // Check to see if a living creature is there.
6297 if (target)
6298 {
6299 formatAndReport("%MU <M:be> still twitching.", target);
6300 return true;
6301 }
6302
6303 // Try to raise a zombie first.
6304 if (!glbCurLevel->raiseZombie(tx, ty, this))
6305 {
6306 // If that fails, raise a skeleton.
6307 if (!glbCurLevel->raiseSkeleton(tx, ty, this))
6308 {
6309 // All failed.
6310 formatAndReport("Without any corpses or bones, the magic is wasted.");
6311 return true;
6312 }
6313 }
6314
6315 // Success!
6316 pietyCastSpell(spell);
6317 return true;
6318 }
6319
6320 case SPELL_RECLAIM_SOUL:
6321 {
6322 // Determine if the given coordinates are visable..
6323 if (hasIntrinsic(INTRINSIC_BLIND) ||
6324 !istargetlit ||
6325 !glbCurLevel->hasLOS(getX(), getY(),
6326 tx, ty))
6327 {
6328 // Cannot see the position, illegal.
6329 formatAndReport("%U <gesture> into the darkness.");
6330 cancelSpell(spell);
6331 return true;
6332 }
6333
6334 // Check if no creature is there.
6335 if (!target)
6336 {
6337 formatAndReport("%U <try> to extract a soul from empty air.");
6338 return true;
6339 }
6340
6341 // Verify the target is an undead.
6342 if (target->defn().mobtype != MOBTYPE_UNDEAD)
6343 {
6344 formatAndReport("The soul of %MU is still bound too tightly.", target);
6345 return true;
6346 }
6347
6348 // Verify we are not oneself.
6349 if (target == this)
6350 {
6351 formatAndReport("The laws of thermodynamics forbid %U from using %MU as a power source.", target);
6352 return true;
6353 }
6354
6355 // Verify we command the undead.
6356 if (!target->isSlave(this))
6357 {
6358 formatAndReport("%MU is not under the control of %U.", target);
6359 return true;
6360 }
6361
6362 // Phew! We have a winner...
6363 formatAndReport("%U <send> vile tendrils that envelope %MU and yank free the last vestiges of unlife.", target);
6364
6365 // Increase our health.
6366 int extrahp, maxhp;
6367
6368 // Maximum heal is the target's strength
6369 extrahp = target->getHP();
6370
6371 // We cap again according to the undead type.
6372 switch (target->getDefinition())
6373 {
6374 case MOB_ZOMBIE:
6375 maxhp = 10;
6376 break;
6377 case MOB_SKELETON:
6378 maxhp = 5;
6379 break;
6380 case MOB_GHAST:
6381 maxhp = 20;
6382 break;
6383 default:
6384 // I don't think any other type can fall through to here,
6385 // but let us prepare for inevitable programmer error.
6386 maxhp = 30;
6387 break;
6388 }
6389
6390 if (extrahp > maxhp)
6391 extrahp = maxhp;
6392
6393 // Heal ourself.
6394 receiveHeal(extrahp, this, true);
6395
6396 // Destroy the target.
6397 glbCurLevel->unregisterMob(target);
6398 target->triggerAsDeath(ATTACK_RECLAIM_SOUL, this, 0, false);
6399 delete target;
6400
6401 // Success!
6402 pietyCastSpell(spell);
6403 return true;
6404 }
6405
6406 case SPELL_DARK_RITUAL:
6407 {
6408 int dx, dy;
6409 ATTACK_NAMES datk[4] = { ATTACK_SPELL_DARK_RITUAL_EAST,
6410 ATTACK_SPELL_DARK_RITUAL_SOUTH,
6411 ATTACK_SPELL_DARK_RITUAL_WEST,
6412 ATTACK_SPELL_DARK_RITUAL_NORTH };
6413 int dir;
6414 int numkilled = 0;
6415 MOBREF myself;
6416
6417 formatAndReport("%U <scatter> blood in a rough pentagram and start the unspeakable ritual.");
6418
6419 myself.setMob(this);
6420
6421 for (dir = 0; dir < 4; dir++)
6422 {
6423 getDirection(dir, dx, dy);
6424
6425 // Determine if their is an owned undead in this direction.
6426 target = glbCurLevel->getMob(getX() + dx, getY() + dy);
6427 if (!target)
6428 continue;
6429
6430 // See if undead & owned.
6431 if (target->defn().mobtype != MOBTYPE_UNDEAD)
6432 continue;
6433
6434 if (!target->isSlave(this))
6435 continue;
6436
6437 numkilled++;
6438
6439 // Transform the undead & empower the attack.
6440 formatAndReport("%U <power> a deadly attack by consuming %MU.", target);
6441
6442 // Destroy the target.
6443 glbCurLevel->unregisterMob(target);
6444 target->triggerAsDeath(ATTACK_DARK_RITUAL_CONSUME, this, 0, false);
6445 delete target;
6446
6447 // Apply the attack.
6448 ourEffectAttack = datk[dir];
6449 // Todo, maybe cone attack instead?
6450 glbCurLevel->fireBall(getX() + dx * 2,
6451 getY() + dy * 2,
6452 1, true,
6453 areaAttackCBStatic, &myself);
6454
6455 // Check if we died...
6456 if (!myself.getMob() ||
6457 myself.getMob() != this ||
6458 myself.getMob()->hasIntrinsic(INTRINSIC_DEAD))
6459 {
6460 return true;
6461 }
6462 }
6463 if (numkilled)
6464 {
6465 pietyCastSpell(spell);
6466 if (numkilled == 4)
6467 {
6468 // Charge us up!
6469 setTimedIntrinsic(0, INTRINSIC_LICHFORM, 2);
6470 }
6471 }
6472 else
6473 {
6474 // No visual effect.
6475 formatAndReport("With no willing victims, %R ritual has no effect.", target);
6476 return true;
6477 }
6478 return true;
6479 }
6480
6481 case SPELL_GHASTIFY:
6482 // You must be targetting a creature.
6483 if (!target)
6484 {
6485 formatAndReport("Cruel magics envelope thin air and dissipate.");
6486 cancelSpell(spell);
6487 return true;
6488 }
6489
6490 // The target must have some life force.
6491 if (glb_mobdefs[target->getDefinition()].mobtype == MOBTYPE_UNDEAD)
6492 {
6493 formatAndReport("%MU <M:have> no life force to convert.", target);
6494 return true;
6495 }
6496
6497 // The target must be at death's gate.
6498 if ((target->getHP() >= target->getMaxHP()) ||
6499 (target->getHP() != 1))
6500 {
6501 formatAndReport("%MU, not being at death's gate, <M:resist>.", target);
6502 return true;
6503 }
6504
6505 if (target->hasIntrinsic(INTRINSIC_UNCHANGING))
6506 {
6507 target->formatAndReport("%U <shudder> as malevolent magics dissipate.");
6508 return true;
6509 }
6510
6511 // GHASTIFY!
6512 formatAndReport("Malevolent magics surround %MU, twisting %MA into a ghast!", target);
6513
6514 pietyCastSpell(spell);
6515
6516 // Yes, they keep all of their powers. Perhaps this is too evil
6517 // We'll see...
6518 target->myDefinition = MOB_GHAST;
6519
6520 // All of their health is instantly returned.
6521 // This allows this to be used as a "save self" spell.
6522 target->setMaximalHP();
6523
6524 target->makeSlaveOf(this);
6525
6526 return true;
6527
6528 case SPELL_BINDSOUL:
6529 {
6530 ITEMSTACK stack;
6531 ITEM *item, *corpse = 0;
6532 int i, n;
6533
6534 glbCurLevel->getItemStack(stack, tx, ty);
6535
6536 n = stack.entries();
6537 for (i = 0; i < n; i++)
6538 {
6539 item = stack(i);
6540 if (item->getDefinition() == ITEM_BONES ||
6541 item->getDefinition() == ITEM_CORPSE)
6542 {
6543 corpse = item;
6544 }
6545 }
6546
6547 if (!corpse)
6548 {
6549 formatAndReport("With no corpse, %R energies dissipate.");
6550 return true;
6551 }
6552
6553 if (!corpse->getCorpseMob())
6554 {
6555 reportMessage("The soul has left the corpse.");
6556 return true;
6557 }
6558
6559 // If there is target, we want to poly it into
6560 // the given corpse tyoe
6561 if (target)
6562 {
6563 bool forcetame = false;
6564 MOBREF newpet;
6565
6566 formatAndReport("Dark energies swirl around %MU.", target);
6567 pietyCastSpell(spell);
6568
6569 if (target != this)
6570 forcetame = true;
6571
6572 newpet.setMob(target);
6573
6574 target->actionPolymorph(false, false,
6575 corpse->getCorpseMob()->getDefinition());
6576
6577 // WARNING this is now invalid if we self-polymorphed!
6578
6579 // Only enslave if poly success.
6580 if (target == newpet.getMob())
6581 forcetame = false;
6582
6583 target = newpet.getMob();
6584 if (forcetame)
6585 {
6586 target->makeSlaveOf(this);
6587 }
6588
6589 // Destroy the corpse.
6590 glbCurLevel->dropItem(corpse);
6591 delete corpse;
6592
6593 return true;
6594 }
6595
6596 // Check to see if there is a mound of flesh. Then
6597 // we can make a Flesh Golem.
6598 // In case of a boulder, we get a stone golem.
6599 {
6600 ITEM *mound = 0;
6601 bool isboulder = false;
6602
6603 n = stack.entries();
6604 for (i = 0; i < n; i++)
6605 {
6606 item = stack(i);
6607 if (item->getDefinition() == ITEM_MOUNDFLESH)
6608 {
6609 mound = item;
6610 isboulder = false;
6611 }
6612 if (item->getDefinition() == ITEM_BOULDER)
6613 {
6614 mound = item;
6615 isboulder = true;
6616 }
6617 }
6618
6619 if (mound)
6620 {
6621 BUF corpsename = corpse->getName();
6622 BUF moundname = mound->getName();
6623 buf.sprintf("Shadowy tendrils yank the soul from %s and infuse it in %s.",
6624 corpsename.buffer(), moundname.buffer());
6625
6626 reportMessage(buf);
6627
6628 pietyCastSpell(spell);
6629
6630 // Destroy the corpse.
6631 glbCurLevel->dropItem(corpse);
6632 delete corpse;
6633
6634 // Destory the mound of flesh.
6635 glbCurLevel->dropItem(mound);
6636 delete mound;
6637
6638 // Create the appropriate golem.
6639 MOB *golem;
6640
6641 if (isboulder)
6642 golem = MOB::create(MOB_STONEGOLEM);
6643 else
6644 golem = MOB::create(MOB_FLESHGOLEM);
6645
6646 // Make tame and summoned.
6647 golem->makeSlaveOf(this);
6648 golem->setTimedIntrinsic(this, INTRINSIC_SUMMONED,
6649 isboulder ? 500 : 2000);
6650
6651 // Add to the map.
6652 golem->move(tx, ty, true);
6653 glbCurLevel->registerMob(golem);
6654
6655 return true;
6656 }
6657 }
6658
6659 // Check if we can create a stone golem out of
6660 // solid rock. These are permament.
6661 SQUARE_NAMES tile;
6662
6663 tile = glbCurLevel->getTile(tx, ty);
6664 if (tile == SQUARE_WALL || tile == SQUARE_EMPTY)
6665 {
6666 BUF corpsename = corpse->getName();
6667 buf.sprintf("The wall collapses, reforming itself with the soul of %s.",
6668 corpsename.buffer());
6669 reportMessage(buf);
6670
6671 pietyCastSpell(spell);
6672
6673 glbCurLevel->setTile(tx, ty, SQUARE_CORRIDOR);
6674
6675 // Destroy the corpse.
6676 glbCurLevel->dropItem(corpse);
6677 delete corpse;
6678
6679 // Create the permament golem.
6680 MOB *golem;
6681
6682 golem = MOB::create(MOB_STONEGOLEM);
6683
6684 // Make tame
6685 golem->makeSlaveOf(this);
6686
6687 // Add to the map.
6688 golem->move(tx, ty, true);
6689 glbCurLevel->registerMob(golem);
6690
6691 return true;
6692 }
6693
6694 if ((tile == SQUARE_CORRIDOR || tile == SQUARE_FLOOR) &&
6695 corpse->isBelowGrade())
6696 {
6697 // Dig a pit for the golem.
6698 formatAndReport("The ground churns as %IU <I:infuse> it with power.", corpse);
6699
6700 pietyCastSpell(spell);
6701
6702 // Destroy the corpse.
6703 glbCurLevel->dropItem(corpse);
6704 delete corpse;
6705
6706 glbCurLevel->digPit(tx, ty, 0);
6707
6708 // Create the permament golem.
6709 MOB *golem;
6710
6711 golem = MOB::create(MOB_STONEGOLEM);
6712
6713 // Make tame
6714 golem->makeSlaveOf(this);
6715
6716 // Add to the map.
6717 golem->move(tx, ty, true);
6718 glbCurLevel->registerMob(golem);
6719
6720 return true;
6721 }
6722
6723 formatAndReport("%IU <I:twitch>.", corpse);
6724 return true;
6725 }
6726
6727 case SPELL_SOULSUCK:
6728 {
6729 if (target && ((target == this) || canSense(target)))
6730 {
6731 target->formatAndReport("A black cloud surrounds %U.");
6732 formatAndReport("Oily smoke, inky black, streams to %U.");
6733
6734 // Check if target already amnesiac
6735 if (target->hasIntrinsic(INTRINSIC_AMNESIA))
6736 {
6737 formatAndReport("The smoke has no substance.");
6738 return true;
6739 }
6740
6741 // Steal all of the target's spells for the duration
6742 int duration;
6743 duration = rand_range(100, 250);
6744
6745 if (target != this)
6746 {
6747 SPELL_NAMES spell;
6748 SKILL_NAMES skill;
6749
6750 FOREACH_SPELL(spell)
6751 {
6752 if (target->hasSpell(spell, false))
6753 {
6754 learnSpell(spell, duration);
6755 }
6756 }
6757 FOREACH_SKILL(skill)
6758 {
6759 if (target->hasSkill(skill, false))
6760 {
6761 learnSkill(skill, duration);
6762 }
6763 }
6764 }
6765
6766 // Apply amnesia
6767 target->setTimedIntrinsic(this, INTRINSIC_AMNESIA, duration);
6768 target->setTimedIntrinsic(this, INTRINSIC_CONFUSED, 5);
6769
6770 pietyCastSpell(spell);
6771 }
6772 else
6773 {
6774 formatAndReport("%U <fail> in %r attempt to extract a soul from thin air.");
6775 cancelSpell(spell);
6776 }
6777 return true;
6778 }
6779
6780 case SPELL_WIZARDSEYE:
6781 {
6782 // Determine if an imp could be there...
6783 tx = getX();
6784 ty = getY();
6785
6786 if (!glbCurLevel->findCloseTile(tx, ty, MOVE_STD_FLY))
6787 {
6788 glbCurLevel->reportMessage(
6789 "A floating eye briefly appears before thinking better of it.",
6790 tx, ty);
6791 return true;
6792 }
6793
6794 MOB *eye;
6795
6796 eye = MOB::create(MOB_WIZARDSEYE);
6797 // Summoned creatures never have inventory.
6798 eye->destroyInventory();
6799
6800 eye->move(tx, ty, true);
6801
6802 glbCurLevel->registerMob(eye);
6803
6804 eye->formatAndReport("%U <appear> in a puff of smoke!");
6805
6806 // Possess the eye for a few turns
6807 eye->setTimedIntrinsic(this, INTRINSIC_SUMMONED, 30);
6808 // Also make it tame.
6809 eye->makeSlaveOf(this);
6810 actionPossess(eye, 29);
6811
6812 eye->pietyCastSpell(spell);
6813
6814 return true;
6815 }
6816 case SPELL_SUMMON_IMP:
6817 {
6818 // Determine if an imp could be there...
6819 tx = getX();
6820 ty = getY();
6821
6822 if (!glbCurLevel->findCloseTile(tx, ty, MOVE_WALK))
6823 {
6824 glbCurLevel->reportMessage(
6825 "An imp briefly appears before thinking better of it.",
6826 tx, ty);
6827 return true;
6828 }
6829
6830 MOB *imp;
6831
6832 imp = MOB::create(MOB_IMP);
6833 // Summoned creatures never have inventory.
6834 imp->destroyInventory();
6835
6836 imp->move(tx, ty, true);
6837
6838 glbCurLevel->registerMob(imp);
6839
6840 imp->formatAndReport("%U <appear> in a puff of smoke!");
6841
6842 imp->makeSlaveOf(this);
6843 imp->setTimedIntrinsic(this, INTRINSIC_SUMMONED, 30);
6844
6845 pietyCastSpell(spell);
6846
6847 return true;
6848 }
6849
6850 case SPELL_SUMMON_DEMON:
6851 {
6852 // Determine if an demon could be there...
6853 tx = getX();
6854 ty = getY();
6855
6856 if (!glbCurLevel->findCloseTile(tx, ty, MOVE_STD_FLY))
6857 {
6858 glbCurLevel->reportMessage(
6859 "A daemon briefly appears before thinking better of it.",
6860 tx, ty);
6861 return true;
6862 }
6863
6864 MOB *imp;
6865
6866 imp = MOB::create(MOB_DAEMON);
6867 // Summoned creatures never have inventory.
6868 imp->destroyInventory();
6869
6870 imp->move(tx, ty, true);
6871
6872 glbCurLevel->registerMob(imp);
6873
6874 imp->formatAndReport("%U <appear> in a puff of smoke!");
6875
6876 imp->makeSlaveOf(this);
6877 imp->setTimedIntrinsic(this, INTRINSIC_SUMMONED, 100);
6878
6879 pietyCastSpell(spell);
6880
6881 return true;
6882 }
6883
6884 case SPELL_POISONITEM:
6885 {
6886 ITEM *item;
6887
6888 item = getEquippedItem(ITEMSLOT_RHAND);
6889
6890 if (!item)
6891 {
6892 // No item to poison.
6893 buf = formatToString("A greenish glow appears around %R %B1.",
6894 this, 0, 0, 0,
6895 (getSlotName(ITEMSLOT_RHAND) ?
6896 getSlotName(ITEMSLOT_RHAND) :
6897 "vicinity"),
6898 0);
6899 reportMessage(buf);
6900 return true;
6901 }
6902
6903 // If the item is an empty bottle, make a poison potion.
6904 if (item->getDefinition() == ITEM_BOTTLE)
6905 {
6906 formatAndReport("A thick slime fills %IU.", item);
6907 pietyCastSpell(spell);
6908 item->setDefinition(ITEM::lookupMagicItem(MAGICTYPE_POTION, POTION_POISON));
6909
6910 // It is safe to assume the user knows what casting
6911 // poison item on a bottle would fill the bottle with.
6912 if (MOB::getAvatar() && MOB::getAvatar()->canSense(this))
6913 item->markClassKnown();
6914
6915 return true;
6916 }
6917
6918 // Poison the item.
6919 formatAndReport("A thick slime coats %R %Iu.", item);
6920 pietyCastSpell(spell);
6921 item->makePoisoned(POISON_NORMAL, 5);
6922 if (MOB::getAvatar() && MOB::getAvatar()->canSense(this))
6923 item->markPoisonKnown();
6924
6925 return true;
6926 }
6927
6928 case SPELL_POISONBOLT:
6929 formatAndReport("A poison bolt shoots from %r fingertip.");
6930
6931 pietyCastSpell(spell);
6932
6933 ourZapSpell = spell;
6934
6935 if (targetself || dz)
6936 {
6937 if (dz)
6938 reportMessage("The ray fizzles.");
6939 else
6940 zapCallbackStatic(getX(), getY(), false, &myself);
6941 }
6942 else
6943 {
6944 glbCurLevel->fireRay(getX(), getY(),
6945 dx, dy,
6946 6,
6947 MOVE_STD_FLY, false,
6948 zapCallbackStatic,
6949 &myself);
6950 }
6951 return true;
6952
6953 case SPELL_CLOUDKILL:
6954 formatAndReport("%U <spit>.");
6955
6956 pietyCastSpell(spell);
6957 ourZapSpell = spell;
6958
6959 if (targetself || dz)
6960 {
6961 zapCallbackStatic(getX(), getY(), true, &myself);
6962 }
6963 else
6964 {
6965 glbCurLevel->fireRay(getX(), getY(),
6966 dx, dy,
6967 6,
6968 MOVE_STD_FLY, false,
6969 zapCallbackStatic,
6970 &myself);
6971 }
6972 return true;
6973
6974 case SPELL_FINGEROFDEATH:
6975 formatAndReport("A jet black ray flies from %r eyes.");
6976
6977 pietyCastSpell(spell);
6978
6979 ourZapSpell = spell;
6980
6981 if (targetself || dz)
6982 {
6983 if (dz)
6984 reportMessage("The ray bounces.");
6985 zapCallbackStatic(getX(), getY(), false, &myself);
6986 }
6987 else
6988 {
6989 glbCurLevel->fireRay(getX(), getY(),
6990 dx, dy,
6991 6,
6992 MOVE_STD_FLY, true,
6993 zapCallbackStatic,
6994 &myself);
6995 }
6996 return true;
6997
6998 case NUM_SPELLS:
6999 UT_ASSERT(!"UNHANDLED SPELL!");
7000 return false;
7001 }
7002
7003 return false;
7004 }
7005
7006 bool
actionDig(int dx,int dy,int dz,int range,bool magical)7007 MOB::actionDig(int dx, int dy, int dz, int range, bool magical)
7008 {
7009 applyConfusion(dx, dy, dz);
7010
7011 // TODO: Handle mundane digging.
7012 if (!magical)
7013 {
7014 // Special messages for special equipment...
7015 ITEM *item = getSourceOfIntrinsic(INTRINSIC_DIG);
7016
7017 if (item)
7018 {
7019 formatAndReport("%U <dig> with %r %Iu.", item);
7020 }
7021 else
7022 {
7023 formatAndReport("%U <dig>.");
7024 }
7025 }
7026
7027 // Do actual excavation:
7028
7029 // Dig yourself.
7030 if (!dx && !dy && !dz)
7031 {
7032 formatAndReport("%r stomach feels empty.");
7033 starve(500);
7034 return true;
7035 }
7036
7037 // Dig upwards.
7038 if (dz > 0)
7039 {
7040 bool fancymove = false;
7041
7042 // If you are submerged, digging upwards will
7043 // clear a path. If you are buried alive,
7044 // it will turn your square into a pit.
7045 // If you are under ice, it will break the ice
7046 // and raise you up.
7047 if (hasIntrinsic(INTRINSIC_SUBMERGED))
7048 {
7049 SQUARE_NAMES tile, newtile;
7050 bool setinpit = false;
7051
7052 tile = glbCurLevel->getTile(getX(), getY());
7053 newtile = tile;
7054
7055 // We let the creature escape first. This way you see the report
7056 // message even if they were invisible when entombed.
7057 clearIntrinsic(INTRINSIC_SUBMERGED);
7058
7059 switch (tile)
7060 {
7061 case SQUARE_WATER:
7062 formatAndReport("The water parts above %U.");
7063 fancymove = true;
7064 break;
7065 case SQUARE_ACID:
7066 formatAndReport("The acid parts above %U.");
7067 fancymove = true;
7068 break;
7069 case SQUARE_LAVA:
7070 formatAndReport("The lava parts above %U.");
7071 fancymove = true;
7072 break;
7073 case SQUARE_ICE:
7074 formatAndReport("The ice breaks above %U.");
7075 newtile = SQUARE_WATER;
7076 fancymove = true;
7077 break;
7078 default:
7079 formatAndReport("The rocks shatter above %U.");
7080 setinpit = true;
7081 fancymove = true;
7082 break;
7083 }
7084
7085 if (newtile != tile)
7086 {
7087 glbCurLevel->setTile(getX(),
7088 getY(),
7089 newtile);
7090 glbCurLevel->dropItems(getX(),
7091 getY(),
7092 this);
7093 }
7094 if (setinpit)
7095 {
7096 // Don't identify us as zapper as
7097 // not normal type.
7098 glbCurLevel->digPit(getX(), getY(), 0);
7099 setIntrinsic(INTRINSIC_INPIT);
7100 }
7101 }
7102
7103 if (!fancymove)
7104 {
7105 if (glbCurLevel->branchName() == BRANCH_TRIDUDE)
7106 {
7107 formatAndReport("The metal roof is unaffected.");
7108 }
7109 else if (glbCurLevel->getDepth() == 0)
7110 {
7111 formatAndReport("The clear blue sky remains unimpressed.");
7112 }
7113 else
7114 {
7115 // Zap at the ceiling, rocks fall down.
7116 formatAndReport("Rocks fall from the ceiling onto %r head.");
7117
7118 ITEM *rocks = ITEM::create(ITEM_ROCK, false);
7119 glbCurLevel->acquireItem(rocks, getX(), getY(), this);
7120 // TODO: We would like a nicer message?
7121 receiveDamage(ATTACK_CEILINGROCKS, this,
7122 0, 0,
7123 ATTACKSTYLE_MISC);
7124 }
7125 }
7126
7127 return true;
7128 }
7129
7130 if (dz < 0)
7131 {
7132 // If we are digging into water or lava, we have a no-op.
7133 // If we are digging into ice, we break the ice and end up
7134 // in the drink. (Unless we are submerged.)
7135 switch (glbCurLevel->getTile(getX(), getY()))
7136 {
7137 case SQUARE_WATER:
7138 formatAndReport("The water briefly parts below %U..");
7139 // Another chance to drop due to the air being revealed.
7140 glbCurLevel->dropMobs(getX(), getY(), false, this);
7141 return true;
7142 case SQUARE_ACID:
7143 formatAndReport("The acid briefly parts below %U.");
7144 // Another chance to drop due to the air being revealed.
7145 glbCurLevel->dropMobs(getX(), getY(), false, this);
7146 return true;
7147 case SQUARE_LAVA:
7148 formatAndReport("The lava briefly parts below %U.");
7149 // Another chance to drop due to the air being revealed.
7150 glbCurLevel->dropMobs(getX(), getY(), false, this);
7151 return true;
7152 case SQUARE_ICE:
7153 if (!hasIntrinsic(INTRINSIC_SUBMERGED))
7154 {
7155 formatAndReport("The ice breaks below %U.");
7156 glbCurLevel->setTile(getX(),
7157 getY(),
7158 SQUARE_WATER);
7159 glbCurLevel->dropMobs(getX(), getY(), false, this);
7160 }
7161 else
7162 {
7163 formatAndReport("The water briefly parts below %U..");
7164 }
7165 return true;
7166 default:
7167 break;
7168 }
7169
7170 // Zap at the floor. We make a pit, and fall down it.
7171 // The map handles falling through it.
7172 // The exception is if we are digging a pit, ie: short range.
7173 // The exception with that is if you are already in a pit.
7174 if (range > 1 || hasIntrinsic(INTRINSIC_INPIT))
7175 glbCurLevel->digHole(getX(), getY(), this);
7176 else
7177 glbCurLevel->digPit(getX(), getY(), this);
7178
7179 return true;
7180 }
7181
7182 // Now, the theoritically easy part...
7183 ourZapSpell = SPELL_DIG;
7184
7185 MOBREF myself;
7186 myself.setMob(this);
7187
7188 glbCurLevel->fireRay(getX(), getY(),
7189 dx, dy,
7190 range,
7191 MOVE_ALL, false,
7192 zapCallbackStatic,
7193 &myself);
7194
7195 return true;
7196 }
7197
7198 bool
actionTeleport(bool allowcontrol,bool givecontrol,int xloc,int yloc)7199 MOB::actionTeleport(bool allowcontrol, bool givecontrol, int xloc, int yloc)
7200 {
7201 int tx, ty;
7202 BUF buf;
7203
7204 if (hasIntrinsic(INTRINSIC_TELEFIXED))
7205 {
7206 formatAndReport("%U <shake>.");
7207 return true;
7208 }
7209
7210 // Check for failure due to being on a level that prohibits
7211 // teleportation
7212 if (!glbCurLevel->allowTeleportation())
7213 {
7214 // We allow people to blink as it isn't quite so bad a form
7215 // of cheating... This may change :>
7216 if (xloc >= 0 && yloc >= 0)
7217 {
7218 // Blink, allow.
7219 }
7220 else
7221 {
7222 formatAndReport("The dungeon briefly distorts around %U.");
7223 return true;
7224 }
7225 }
7226
7227 if (allowcontrol &&
7228 (givecontrol || hasIntrinsic(INTRINSIC_TELEPORTCONTROL)))
7229 {
7230 if (!glbStressTest && isAvatar())
7231 {
7232 buf.sprintf("Where do you want to teleport?");
7233 reportMessage(buf);
7234 tx = getX();
7235 ty = getY();
7236 if (gfx_selecttile(tx, ty))
7237 {
7238 // Check to see if that is a legal location.
7239 if (canMove(tx, ty, true) && !glbCurLevel->getMob(tx, ty)
7240 && !glbCurLevel->getFlag(tx, ty, SQUAREFLAG_NOMOB))
7241 {
7242 // A legal move!
7243 formatAndReport("%U <teleport>.");
7244 }
7245 else
7246 {
7247 // If you decide to stay put, don't report that you
7248 // are blocked (since you will, of course, fail to
7249 // move into your own square!)
7250 if (tx == getX() && ty == getY())
7251 {
7252 formatAndReport("%U <decide> to stay put.");
7253 }
7254 else
7255 {
7256 formatAndReport("%U <be> blocked!");
7257 tx = getX();
7258 ty = getY();
7259 }
7260 }
7261 }
7262 else
7263 {
7264 tx = getX();
7265 ty = getY();
7266 formatAndReport("%U <decide> to stay put.");
7267 }
7268 }
7269 else
7270 {
7271 // Use AI. Try to teleport to the stairs.
7272 if (glbCurLevel->findTile(SQUARE_LADDERUP, tx, ty) &&
7273 glbCurLevel->findCloseTile(tx, ty, getMoveType()))
7274 {
7275 formatAndReport("%U <teleport> away.");
7276 }
7277 else
7278 {
7279 // Go somewhere random instead of the stairs as they
7280 // are blocked.
7281 if (glbCurLevel->findRandomLoc(tx, ty, getMoveType(),
7282 false, getSize() >= SIZE_GARGANTUAN,
7283 false, true, true, true))
7284 {
7285 formatAndReport("%U <teleport> away.");
7286 }
7287 else
7288 {
7289 // Teleport to where we are.
7290 formatAndReport("%U <blink> out of sight briefly.");
7291 tx = getX();
7292 ty = getY();
7293 }
7294 }
7295 }
7296 }
7297 else
7298 {
7299 // Determine a random destination...
7300 if (xloc >= 0 && yloc >= 0)
7301 {
7302 formatAndReport("%U <blink>.");
7303 tx = xloc;
7304 ty = yloc;
7305 }
7306 else if (glbCurLevel->findRandomLoc(tx, ty, getMoveType(),
7307 false, getSize() >= SIZE_GARGANTUAN,
7308 false, false, false, true))
7309 {
7310 formatAndReport("%U <teleport>.");
7311 }
7312 else
7313 {
7314 // Failed to teleport, you shudder...
7315 tx = getX();
7316 ty = getY();
7317 formatAndReport("%U <shudder> violently.");
7318 }
7319 }
7320
7321 bool didmove = false;
7322
7323 if (tx != getX() || ty != getY())
7324 didmove = true;
7325
7326 // Move to the given destination.
7327 if (!move(tx, ty, !didmove))
7328 return true;
7329
7330 // Report what we stepped on...
7331 if (didmove && isAvatar())
7332 {
7333 // Need to rebuild our screen! Refreshing will be handled
7334 // by the message wait code.
7335 gfx_scrollcenter(getX(), getY());
7336 glbCurLevel->buildFOV(getX(), getY(), 7, 5);
7337
7338 glbCurLevel->describeSquare(getX(), getY(),
7339 hasIntrinsic(INTRINSIC_BLIND),
7340 false);
7341 }
7342
7343 return true;
7344 }
7345
7346 bool
actionUnPolymorph(bool silent,bool systemshock)7347 MOB::actionUnPolymorph(bool silent, bool systemshock)
7348 {
7349 MOB *origmob;
7350
7351 if (hasIntrinsic(INTRINSIC_UNCHANGING))
7352 {
7353 // Explicitly prevent polymorphing...
7354 if (!silent)
7355 formatAndReport("%U <shudder>.");
7356 return true;
7357 }
7358
7359 origmob = myBaseType.getMob();
7360
7361 if (!origmob)
7362 {
7363 // Nothing to do!
7364 return true;
7365 }
7366
7367 // Be paranoid and trigger a mobref check.
7368 #ifdef STRESS_TEST
7369 if (glbCurLevel)
7370 {
7371 if (!glbCurLevel->verifyMob())
7372 {
7373 msg_report("MOBREF corruption: pre-unpoly. ");
7374 }
7375 }
7376 #endif
7377
7378 if (!silent)
7379 formatAndReport("%U <return> to %r original shape!");
7380
7381 bool shouldwallcrush = false;
7382
7383 if (origmob->getSize() >= SIZE_GARGANTUAN &&
7384 getSize() < SIZE_GARGANTUAN)
7385 {
7386 shouldwallcrush = true;
7387 }
7388
7389 // Transfer the name of the mob to the base level. This accounts
7390 // for cases where the player has named the mob.
7391 origmob->myName = myName;
7392
7393 // Transfer all the items.
7394 // We could just steal the inventory pointer, but that would be
7395 // evil if items ever get back references. There is also no
7396 // guarantee that the intrinsics/rebuild will work correctly.
7397 ITEM *item, *next, *drop;
7398 bool anydrop = false;
7399 for (item = myInventory; item; item = next)
7400 {
7401 next = item->getNext();
7402
7403 drop = dropItem(item->getX(), item->getY());
7404 UT_ASSERT(drop == item);
7405
7406 if (!origmob->acquireItem(item, item->getX(), item->getY()))
7407 {
7408 // We have to report the dropping before doing it
7409 // as dropping may merge stacks and delete item.
7410 // Note that we don't want to use newitem as that
7411 // may report an unusual new stack count.
7412 formatAndReport("%IU <I:drop> on the ground!", item);
7413 // If we fail to acquire, we have to drop on the ground.
7414 glbCurLevel->acquireItem(item, getX(), getY(), this);
7415 anydrop = true;
7416 }
7417 }
7418 // Make sure stuff ends up in the water, etc.
7419 if (anydrop)
7420 glbCurLevel->dropItems(getX(), getY(), 0);
7421
7422 UT_ASSERT(!myInventory);
7423
7424 // Transfer the raw mob pointers...
7425 MOBREF tmpref;
7426 INTRINSIC_NAMES intrinsic;
7427
7428 tmpref = myOwnRef;
7429 myOwnRef = origmob->myOwnRef;
7430 origmob->myOwnRef = tmpref;
7431
7432 // Fix up the underlying pointers to match the global level.
7433 if (!myOwnRef.transferMOB(this))
7434 {
7435 UT_ASSERT(!"UnPoly Fail Transfer To");
7436 }
7437 if (!origmob->myOwnRef.transferMOB(origmob))
7438 {
7439 UT_ASSERT(!"UnPoly Fail Transfer From");
7440 }
7441
7442 origmob->move(getX(), getY(), true);
7443
7444 // Clear out our base type. (It points to ourself now,
7445 // so would be rather disasterous to delete it as is!)
7446 myBaseType.setMob(0);
7447
7448 glbCurLevel->unregisterMob(this);
7449 glbCurLevel->registerMob(origmob);
7450
7451 // We don't need to transfer back experience as it has been
7452 // accumulating for us.
7453
7454 // Transfer food level.
7455 origmob->myFoodLevel = myFoodLevel;
7456
7457 // Transfer all the intrinsics that should survive.
7458 for (intrinsic = INTRINSIC_NONE; intrinsic < NUM_INTRINSICS;
7459 intrinsic = (INTRINSIC_NAMES) (intrinsic + 1))
7460 {
7461 if (glb_intrinsicdefs[intrinsic].surviveunpoly &&
7462 hasIntrinsic(intrinsic))
7463 {
7464 INTRINSIC_COUNTER *counter;
7465
7466 // We may already have the intrinsic, so don't want
7467 // to double count things.
7468 origmob->clearIntrinsic(intrinsic);
7469
7470 counter = getCounter(intrinsic);
7471 if (counter)
7472 {
7473 origmob->setTimedIntrinsic(counter->myInflictor.getMob(),
7474 intrinsic,
7475 counter->myTurns);
7476 }
7477 else
7478 origmob->setIntrinsic(intrinsic);
7479 }
7480
7481 // If the intrinsic shouldn't survive, remove ourselves.
7482 if (glb_intrinsicdefs[intrinsic].clearonpoly)
7483 clearIntrinsic(intrinsic);
7484 }
7485
7486 // Transfer the ai target.
7487 origmob->myAITarget.setMob(getAITarget());
7488
7489 delete this;
7490
7491 origmob->rebuildAppearance();
7492 origmob->rebuildWornIntrinsic();
7493
7494 // Put the system shock to the end so the message makes sense.
7495 // Apply system shock: The base types max hit points are damaged
7496 // as a result of the pain.
7497 if (systemshock)
7498 {
7499 origmob->systemshock();
7500 }
7501
7502 // Crush surrounding walls if we return to gargantuan size
7503 if (shouldwallcrush)
7504 {
7505 glbCurLevel->wallCrush(origmob);
7506 }
7507
7508 #ifdef STRESS_TEST
7509 // Be paranoid and trigger a mobref check.
7510 if (glbCurLevel)
7511 {
7512 if (!glbCurLevel->verifyMob())
7513 {
7514 msg_report("MOBREF corruption: unpoly. ");
7515 }
7516 }
7517 #endif
7518
7519 // All done! No one should be the wiser!
7520 return true;
7521 }
7522
7523 bool
actionPolymorph(bool allowcontrol,bool forcecontrol,MOB_NAMES newdef)7524 MOB::actionPolymorph(bool allowcontrol, bool forcecontrol, MOB_NAMES newdef)
7525 {
7526 MOB *newmob;
7527 BUF buf;
7528
7529 if (hasIntrinsic(INTRINSIC_UNCHANGING))
7530 {
7531 // Explicitly prevent polymorphing...
7532 formatAndReport("%U <shudder>.");
7533 return true;
7534 }
7535
7536 #ifdef STRESS_TEST
7537 // Be paranoid and trigger a mobref check.
7538 if (glbCurLevel)
7539 {
7540 if (!glbCurLevel->verifyMob())
7541 {
7542 msg_report("MOBREF corruption: pre-poly. ");
7543 }
7544 }
7545 #endif
7546
7547 // If we are already polyed, we first return to our original state
7548 // before going on to polymorph...
7549 if (myBaseType.getMob())
7550 {
7551 newmob = myBaseType.getMob();
7552
7553 actionUnPolymorph(true);
7554
7555 return newmob->actionPolymorph(allowcontrol, forcecontrol, newdef);
7556 }
7557
7558 // newdef = MOB_CRETAN_MINOTAUR;
7559
7560 if (allowcontrol && (forcecontrol || hasIntrinsic(INTRINSIC_POLYCONTROL)))
7561 {
7562 formatAndReport("%U <take> control of the transformation!");
7563 if (!glbStressTest && isAvatar())
7564 {
7565 // Make a list of all killed critters and prompt.
7566 MOB_NAMES mobtypes[NUM_MOBS];
7567 const char *moblist[NUM_MOBS+2]; // room for null, nochange
7568 int i, numtype, aorb, selection;
7569
7570 victory_buildSortedKillList(mobtypes, &numtype);
7571
7572 // Option to remain unchanged...
7573 moblist[0] = "yourself";
7574
7575 // Create names
7576 for (i = 0; i < numtype; i++)
7577 {
7578 moblist[i+1] = glb_mobdefs[mobtypes[i]].name;
7579 }
7580 // Null terminate
7581 moblist[numtype+1] = 0;
7582
7583 while (1)
7584 {
7585 int y;
7586
7587 gfx_printtext(0, 3, "Transform into what?");
7588 selection = gfx_selectmenu(5, 4, (const char **) moblist, aorb);
7589 for (y = 3; y < 19; y++)
7590 gfx_cleartextline(y);
7591
7592 // You are not allowed to cancel.
7593 if (!aorb)
7594 break;
7595 }
7596 if (!selection)
7597 {
7598 // Person chose to resist.
7599 // No shudder message as this might involve turning
7600 // *back* to your original form.
7601 return true;
7602 }
7603
7604 selection--;
7605 newdef = mobtypes[selection];
7606 }
7607 else
7608 {
7609 // Find the end of our advancment and turn into it.
7610 newdef = getDefinition();
7611 while (glb_mobdefs[newdef].evolvetarget != MOB_NONE)
7612 {
7613 newdef = (MOB_NAMES) glb_mobdefs[newdef].evolvetarget;
7614 }
7615
7616 // If I have no evolve target, go for random poly unless
7617 // we are in good health.
7618 if (newdef == getDefinition())
7619 {
7620 if (getHP() >= (getMaxHP() >> 3) + 1)
7621 {
7622 // We are happy with what we are.
7623 return true;
7624 }
7625 else
7626 {
7627 // We may be seeking escape.
7628 newdef = MOB_NONE;
7629 }
7630 }
7631 }
7632 }
7633
7634 // If newdef is not MOB_NONE, that is the forced value.
7635 // We manually add an upper bound as one may have locked
7636 // the NPC to only be of a certain type, that will then infinite
7637 // loop
7638 int maxtries = 100;
7639 while (newdef == MOB_NONE)
7640 {
7641 newdef = MOB::chooseNPC(100);
7642
7643 // Reject self-polymorph as it is embarrassing.
7644 if (newdef == getDefinition() && (maxtries --> 0))
7645 newdef = MOB_NONE;
7646 }
7647
7648 newmob = MOB::create(newdef);
7649
7650 // This bit of logic was cut and paste on the Narita Express whilst
7651 // somewhat drunk on beer and sake. Not enough beer and sake, obviously,
7652 // since I'm still able to cut and paste. Not much to see outside at
7653 // 8:15, however, it is already very dark. Clearly I'm preparing
7654 // for my more northerly summers and expectations of the sun setting
7655 // some time after dusk.
7656 bool shouldwallcrush = false;
7657
7658 if (newmob->getSize() >= SIZE_GARGANTUAN &&
7659 getSize() < SIZE_GARGANTUAN)
7660 {
7661 shouldwallcrush = true;
7662 }
7663
7664 // The new mob doesn't get to keep any cool possessions!
7665 newmob->destroyInventory();
7666
7667 // Transfer all the items.
7668 // We could just steal the inventory pointer, but that would be
7669 // evil if items ever get back references. There is also no
7670 // guarantee that the intrinsics/rebuild will work correctly.
7671 ITEM *item, *next, *drop;
7672 bool anydrop = false;
7673
7674 for (item = myInventory; item; item = next)
7675 {
7676 next = item->getNext();
7677
7678 drop = dropItem(item->getX(), item->getY());
7679 UT_ASSERT(drop == item);
7680
7681 if (!newmob->acquireItem(item, item->getX(), item->getY()))
7682 {
7683 // We have to report the dropping before doing it
7684 // as dropping may merge stacks and delete item.
7685 // Note that we don't want to use newitem as that
7686 // may report an unusual new stack count.
7687 // If we fail to acquire, we have to drop on the ground.
7688 formatAndReport("%IU <I:drop> on the ground!", item);
7689 glbCurLevel->acquireItem(item, getX(), getY(), this);
7690 anydrop = true;
7691 }
7692 }
7693 // Make sure stuff ends up in the water, etc.
7694 if (anydrop)
7695 glbCurLevel->dropItems(getX(), getY(), 0);
7696
7697 UT_ASSERT(!myInventory);
7698
7699 // Move the getX()/getY() so we don't get "transforms into it".
7700 newmob->move(getX(), getY(), true);
7701 // Set the mob's dlevel so we don't get transforms into it.
7702 newmob->setDLevel(getDLevel());
7703 // Set the mob's name to match our own name.
7704 newmob->myName = myName;
7705
7706 // Spam the effect!
7707 // If we are the avatar, we force the new form to be visible. Otherwise,
7708 // polying whilst invisible and no see invisible would be
7709 // "you turn into it"
7710 // Except.... That is a feature. After all, you have no
7711 // idea of knowing what your new form is?
7712 // Bah, disable for now until we properly use "something".
7713 // We now use soemething, so it is a feature again.
7714 formatAndReport("%U <transform> into %B1!",
7715 newmob->getName(true, false, false /*isAvatar()*/));
7716
7717 // We want all mob references to go to newmob rather than this:
7718 MOBREF tmpref;
7719 INTRINSIC_NAMES intrinsic;
7720
7721 tmpref = myOwnRef;
7722 myOwnRef = newmob->myOwnRef;
7723 newmob->myOwnRef = tmpref;
7724
7725 // Fix up the underlying pointers to match the global level.
7726 if (!myOwnRef.transferMOB(this))
7727 {
7728 UT_ASSERT(!"Poly transfer to");
7729 }
7730 if (!newmob->myOwnRef.transferMOB(newmob))
7731 {
7732 UT_ASSERT(!"Poly transfer from");
7733 }
7734
7735 // Important to do this AFTER the transfer!
7736 newmob->myBaseType.setMob(this);
7737
7738 glbCurLevel->unregisterMob(this);
7739 glbCurLevel->registerMob(newmob);
7740
7741 // Transfer any experience to the new mob.
7742 newmob->myExp = myExp;
7743
7744 // Transfer food level.
7745 newmob->myFoodLevel = myFoodLevel;
7746
7747 // Transfer all the intrinsics that should survive.
7748 for (intrinsic = INTRINSIC_NONE; intrinsic < NUM_INTRINSICS;
7749 intrinsic = (INTRINSIC_NAMES) (intrinsic + 1))
7750 {
7751 if (glb_intrinsicdefs[intrinsic].survivepoly &&
7752 hasIntrinsic(intrinsic))
7753 {
7754 INTRINSIC_COUNTER *counter;
7755
7756 counter = getCounter(intrinsic);
7757 if (counter)
7758 {
7759 newmob->setTimedIntrinsic(counter->myInflictor.getMob(),
7760 intrinsic,
7761 counter->myTurns);
7762 }
7763 else
7764 newmob->setIntrinsic(intrinsic);
7765 }
7766
7767 // If the intrinsic shouldn't survive, remove ourselves.
7768 if (glb_intrinsicdefs[intrinsic].clearonpoly)
7769 clearIntrinsic(intrinsic);
7770 }
7771
7772 // Transfer the ai target.
7773 newmob->myAITarget.setMob(getAITarget());
7774
7775 newmob->rebuildAppearance();
7776 newmob->rebuildWornIntrinsic();
7777
7778 // Crush surrounding walls if we return to gargantuan size
7779 if (shouldwallcrush)
7780 {
7781 glbCurLevel->wallCrush(newmob);
7782 }
7783
7784 // All done! No one should be the wiser!
7785
7786 #ifdef STRESS_TEST
7787 // Be paranoid and trigger a mobref check.
7788 if (glbCurLevel)
7789 {
7790 if (!glbCurLevel->verifyMob())
7791 {
7792 msg_report("MOBREF corruption: poly. ");
7793 }
7794 }
7795 #endif
7796
7797 return true;
7798 }
7799
7800 bool
actionPetrify()7801 MOB::actionPetrify()
7802 {
7803 // Ensure we are made of flesh. Otherwise, not much to do.
7804 if (getMaterial() != MATERIAL_FLESH || hasIntrinsic(INTRINSIC_UNCHANGING))
7805 {
7806 formatAndReport("%U <feel> momentarily stony.");
7807 return false;
7808 }
7809
7810 // Special case: Flesh golems become stone golems.
7811 if (getDefinition() == MOB_FLESHGOLEM)
7812 {
7813 formatAndReport("%U <transform> into a stone golem!");
7814 myDefinition = MOB_STONEGOLEM;
7815 return true;
7816 }
7817
7818 // Special case: Trolls become cave trolls.
7819 if (getDefinition() == MOB_TROLL)
7820 {
7821 formatAndReport("%U <transform> into a cave troll!");
7822 myDefinition = MOB_CAVETROLL;
7823 return true;
7824 }
7825
7826 // Determine if the user can save their life.
7827 if (attemptLifeSaving("%U <harden> into a statue!"))
7828 {
7829 return true;
7830 }
7831
7832 #ifdef STRESS_TEST
7833 if (glbCurLevel)
7834 {
7835 if (!glbCurLevel->verifyMob())
7836 {
7837 msg_report("MOBREF corruption: pre-petrify. ");
7838 }
7839 }
7840 #endif
7841
7842 formatAndReport("%U <harden> into a statue!");
7843
7844 // Release any possession
7845 if (hasIntrinsic(INTRINSIC_POSSESSED))
7846 actionReleasePossession(true);
7847
7848 // Create the new statue.
7849 ITEM *statue;
7850
7851 glbCurLevel->unregisterMob(this);
7852 statue = ITEM::createStatue(this);
7853 glbCurLevel->acquireItem(statue, getX(), getY(), this);
7854
7855 if (isAvatar())
7856 {
7857 // Note we want to auto-map our own statue so the user
7858 // gets to see it.
7859 statue->markMapped();
7860 }
7861 triggerAsDeath(ATTACK_TURNED_TO_STONE, 0, 0, true);
7862
7863 // Clear their death intrinsics so if they are ressed, they
7864 // don't keep burning...
7865 clearDeathIntrinsics(true);
7866
7867 // One may be better categorized as in stasis, but this does
7868 // mean that people should stop following you.
7869 setIntrinsic(INTRINSIC_DEAD);
7870
7871 #ifdef STRESS_TEST
7872 // Be paranoid and trigger a mobref check.
7873 if (glbCurLevel)
7874 {
7875 if (!glbCurLevel->verifyMob())
7876 {
7877 msg_report("MOBREF corruption: petrify. ");
7878 }
7879 }
7880 #endif
7881
7882 return true;
7883 }
7884
7885 bool
actionUnPetrify()7886 MOB::actionUnPetrify()
7887 {
7888 // Clear out the stoning intrinsic
7889 clearIntrinsic(INTRINSIC_STONING);
7890
7891 // Ensure we are made of stone. Otherwise, not much to do.
7892 if (getMaterial() != MATERIAL_STONE || hasIntrinsic(INTRINSIC_UNCHANGING))
7893 {
7894 formatAndReport("%U <feel> momentarily fleshy.");
7895 return false;
7896 }
7897
7898 // Special case: Flesh golems become stone golems.
7899 if (getDefinition() == MOB_STONEGOLEM)
7900 {
7901 formatAndReport("%U <transform> into a flesh golem!");
7902 myDefinition = MOB_FLESHGOLEM;
7903 return true;
7904 }
7905
7906 // Special case: Cave trolls become trolls.
7907 if (getDefinition() == MOB_CAVETROLL)
7908 {
7909 formatAndReport("%U <soften> into a troll!");
7910 myDefinition = MOB_TROLL;
7911 return true;
7912 }
7913
7914 // Check if we can save our life...
7915 if (attemptLifeSaving("%U <sag> into a mound of featureless flesh!"))
7916 {
7917 return true;
7918 }
7919
7920 // Ew! Icky!
7921 formatAndReport("%U <collapse> into a mound of flesh!");
7922
7923 // Create the new flesh mound.
7924 ITEM *flesh;
7925
7926 glbCurLevel->unregisterMob(this);
7927
7928 // Drop our inventory on the ground.
7929 {
7930 ITEM *cur;
7931
7932 // Drop our inventory on this square...
7933 while ((cur = myInventory))
7934 {
7935 // We manually drop the item for efficiency.
7936 // (Creature is dead, so no need for efficiency)
7937 myInventory = cur->getNext();
7938 cur->setNext(0);
7939 glbCurLevel->acquireItem(cur, getX(), getY(), this);
7940 }
7941 }
7942
7943 flesh = ITEM::create(ITEM_MOUNDFLESH, false, true);
7944 glbCurLevel->acquireItem(flesh, getX(), getY(), this);
7945
7946 if (isAvatar())
7947 {
7948 // Note we want to auto-map our own mound of flesh so the user
7949 // gets to see it.
7950 flesh->markMapped();
7951 }
7952 // WHile you do become an item, there is no restoration possibility...
7953 triggerAsDeath(ATTACK_TURNED_TO_FLESH, 0, 0, false);
7954
7955 // Self is most definitely dead.
7956 delete this;
7957
7958 return true;
7959 }
7960
7961 bool
ableToEquip(int ix,int iy,ITEMSLOT_NAMES slot,bool quiet)7962 MOB::ableToEquip(int ix, int iy, ITEMSLOT_NAMES slot, bool quiet)
7963 {
7964 // First, ensure there is an item...
7965 ITEM *item, *olditem, *otheritem;
7966 BUF buf;
7967 bool fit;
7968 bool dualdequip = false;
7969
7970 // Determine if we have the given slot...
7971 if (!hasSlot(slot))
7972 {
7973 if (!quiet)
7974 {
7975 formatAndReport("%U <have> no %B1!",
7976 glb_itemslotdefs[slot].bodypart);
7977 }
7978 return false;
7979 }
7980
7981 item = getItem(ix, iy);
7982 if (!item)
7983 {
7984 if (!quiet)
7985 formatAndReport("%U <try> to equip nothing!");
7986 return false;
7987 }
7988
7989 // Check to see if it can fit. Note anything will fit in the right
7990 // hand, as anything can be used as a weapon.
7991 fit = false;
7992 switch (slot)
7993 {
7994 case ITEMSLOT_HEAD:
7995 fit = item->isHelmet();
7996 break;
7997 case ITEMSLOT_RHAND:
7998 fit = true;
7999
8000 // We need two hands to equip large items.
8001 if (item->getSize() > getSize())
8002 {
8003 // Check to see if the right hand is free. Specifically,
8004 // if the left hand has an item, we can't fit a large weapon.
8005 otheritem = getEquippedItem(ITEMSLOT_LHAND);
8006 if ((otheritem && otheritem->getDefinition() != ITEM_BUCKLER)
8007 || !hasSlot(ITEMSLOT_LHAND))
8008 {
8009 // We demand a double dequip
8010 dualdequip = true;
8011 }
8012 }
8013 break;
8014 case ITEMSLOT_LHAND:
8015 // Anything fits in the right hand.
8016 // However, we still prohibit stacking (as that is only
8017 // allowed for doing enchantments)
8018 // We only idac if it is a shield.
8019 fit = true;
8020 // Check to see if the left hand is free. Specifically,
8021 // if the right hand has a large weapon in it, we can't
8022 // equip in the left hand.
8023 // Note: If you have a left hand, you always have a right hand.
8024 // Note: Bucklers are special because you strap them to
8025 // your forearm.
8026 otheritem = getEquippedItem(ITEMSLOT_RHAND);
8027 if (otheritem && (otheritem->getSize() > getSize()) &&
8028 (item->getDefinition() != ITEM_BUCKLER))
8029 {
8030 if (!quiet)
8031 {
8032 formatAndReport("%U <need> two hands to wield %IU!", otheritem);
8033 }
8034 return false;
8035 }
8036 break;
8037 case ITEMSLOT_BODY:
8038 fit = item->isJacket();
8039 break;
8040 case ITEMSLOT_FEET:
8041 fit = item->isBoots();
8042 break;
8043 case ITEMSLOT_AMULET:
8044 fit = item->isAmulet();
8045 break;
8046 case ITEMSLOT_RRING:
8047 case ITEMSLOT_LRING:
8048 {
8049 // If we have the missing finger intrinsic, this will
8050 // only work if we are replacing the ring or have no
8051 // rings, ie, the other slot is free.
8052 if (hasIntrinsic(INTRINSIC_MISSINGFINGER))
8053 {
8054 if (getEquippedItem(
8055 (slot==ITEMSLOT_RRING)
8056 ? ITEMSLOT_LRING
8057 : ITEMSLOT_RRING))
8058 {
8059 if (!quiet)
8060 {
8061 formatAndReport("%U <be> surprised to find no %B1!",
8062 getSlotName(slot));
8063 }
8064 // Yes, you get to find out for free...
8065 return false;
8066 }
8067 }
8068 fit = item->isRing();
8069 break;
8070 }
8071 default:
8072 {
8073 UT_ASSERT(!"Unhandled ITEMSLOT");
8074 fit = false;
8075 break;
8076 }
8077 }
8078
8079 if (!fit)
8080 {
8081 if (!quiet)
8082 {
8083 buf = formatToString("%U <fail> to put %IU %B1 %r %B2.",
8084 this, 0, 0, item,
8085 glb_itemslotdefs[slot].preposition,
8086 getSlotName(slot));
8087 reportMessage(buf);
8088 }
8089 return false;
8090 }
8091
8092 // Verify we are able to dequip the old slot.
8093 olditem = getEquippedItem(slot);
8094 if (olditem)
8095 {
8096 if (!ableToDequip(slot, quiet))
8097 return false;
8098 }
8099 if (dualdequip)
8100 {
8101 if (!ableToDequip(ITEMSLOT_LHAND, quiet))
8102 return false;
8103 }
8104
8105 return true;
8106 }
8107
8108 bool
actionEquip(int ix,int iy,ITEMSLOT_NAMES slot,bool quiet)8109 MOB::actionEquip(int ix, int iy, ITEMSLOT_NAMES slot, bool quiet)
8110 {
8111 // First, ensure there is an item...
8112 ITEM *item, *olditem, *offhand;
8113 BUF buf;
8114 bool idac = false; // Do we now know the ac?
8115 bool allowstack = false;
8116
8117 // Verify this is a legal operation.
8118 if (!ableToEquip(ix, iy, slot, quiet))
8119 return false;
8120
8121 item = getItem(ix, iy);
8122
8123 switch (slot)
8124 {
8125 case ITEMSLOT_HEAD:
8126 idac = true;
8127 break;
8128 case ITEMSLOT_RHAND:
8129 allowstack = item->defn().equipstack;
8130 break;
8131 case ITEMSLOT_LHAND:
8132 idac = item->isShield();
8133 break;
8134 case ITEMSLOT_BODY:
8135 idac = true;
8136 break;
8137 case ITEMSLOT_FEET:
8138 idac = true;
8139 break;
8140 case ITEMSLOT_AMULET:
8141 break;
8142 case ITEMSLOT_RRING:
8143 case ITEMSLOT_LRING:
8144 break;
8145 default:
8146 {
8147 UT_ASSERT(!"Unhandled ITEMSLOT");
8148 break;
8149 }
8150 }
8151
8152 // It fits. First, we remove whatever was there...
8153 olditem = getEquippedItem(slot);
8154 if (olditem)
8155 {
8156 actionDequip(slot);
8157 }
8158 // It is possible dequiping failed. In that case, we abort early.
8159 // (We do check that we are ableToDequip, but let's still be paranoid)
8160 if (getEquippedItem(slot))
8161 return true;
8162
8163 // Check to see if we need secondary dequip.
8164 if (slot == ITEMSLOT_RHAND &&
8165 item->getSize() > getSize())
8166 {
8167 offhand = getEquippedItem(ITEMSLOT_LHAND);
8168
8169 // See if we need to dequip the left hand or not.
8170 // Bucklers are special.
8171 // Only do so if an item is present to avoid spam.
8172 // (Written on GO train, less prestigous than first class...)
8173 if (offhand && offhand->getDefinition() != ITEM_BUCKLER)
8174 {
8175 // Force double dequip.
8176 actionDequip(ITEMSLOT_LHAND, quiet);
8177 // Check for failure, possibly full inventory?
8178 // (Cursed should be handled in ableToDequip...)
8179 if (getEquippedItem(ITEMSLOT_LHAND))
8180 return true;
8181 }
8182 }
8183
8184 // Strip off the top item of a stack.
8185 ITEM *itemstack = 0;
8186 if (!allowstack)
8187 {
8188 if (item->getStackCount() > 1)
8189 {
8190 itemstack = item;
8191 item = itemstack->splitStack(1);
8192 }
8193 }
8194
8195 // Move the old item into the slot which we equipped from.
8196 // Note that we require a spare slot to dequip into, but we
8197 // will ensure the cursor is over the dequipped item, so one
8198 // can easily swap weapons.
8199 // Note that if we equipped from a stack, the stack will
8200 // remain in the old pos, so thus we don't want to do this.
8201 if (olditem && !itemstack)
8202 {
8203 // If we are equipping from a slot, we have to verify
8204 // it is legal to put the old item into that slot.
8205 // Or you can end up wearing a sword.
8206 if (ix != 0 || ableToEquip(olditem->getX(), olditem->getY(), (ITEMSLOT_NAMES) iy, true))
8207 olditem->setPos(item->getX(), item->getY());
8208 }
8209
8210 // Move this item into the slot...
8211 if (!itemstack)
8212 item->setPos(0, slot);
8213 else
8214 {
8215 // As we have verified above that the slot is empty,
8216 // this will not fail nor result in item being deleted.
8217 acquireItem(item, 0, slot);
8218 }
8219
8220 if (!quiet)
8221 {
8222 buf = formatToString("%U <put> %IU %B1 %r %B2.",
8223 this, 0, 0, item,
8224 glb_itemslotdefs[slot].preposition,
8225 getSlotName(slot));
8226 reportMessage(buf);
8227 }
8228
8229 // Poison ourselves if the item is poisoned:
8230 item->applyPoison(this, this);
8231
8232 // If we now can figure out the ac, we mark it as known.
8233 if (idac && isAvatar())
8234 item->markEnchantKnown();
8235
8236 // Auto identify the item if it is cursed and we didn't know
8237 // the status.
8238 if (!item->isKnownCursed() && item->isCursed() && isAvatar())
8239 {
8240 if (!quiet)
8241 {
8242 formatAndReport("%IU <I:chill> %r %B1!", item,
8243 getSlotName(slot));
8244 }
8245 item->markCursedKnown();
8246 }
8247
8248 // If the avatar equips something that doesn't curse them,
8249 // the avatar can note it is now not cursed.
8250 if (!item->isKnownNotCursed() && !item->isCursed() && isAvatar())
8251 {
8252 // No spam for this.
8253 item->markNotCursedKnown();
8254 }
8255
8256 // If the new item grants missing finger, we should ensure we have
8257 // a free finger...
8258 if (item->hasIntrinsic(INTRINSIC_MISSINGFINGER))
8259 {
8260 dropOneRing();
8261 }
8262
8263 rebuildAppearance();
8264 rebuildWornIntrinsic();
8265
8266 return true;
8267 }
8268
8269 bool
ableToDequip(ITEMSLOT_NAMES slot,bool quiet)8270 MOB::ableToDequip(ITEMSLOT_NAMES slot, bool quiet)
8271 {
8272 ITEM *item;
8273 BUF buf;
8274 int ix, iy;
8275
8276 item = getEquippedItem(slot);
8277
8278 if (!item)
8279 {
8280 // Trying to dequip something you don't have on.
8281 if (!hasSlot(slot))
8282 {
8283 buf = formatToString("%U <have> no %B1!",
8284 this, 0, 0, 0,
8285 glb_itemslotdefs[slot].bodypart);
8286 }
8287 else
8288 {
8289 buf = formatToString("%U <have> nothing %B1 %r %B2!",
8290 this, 0, 0, 0,
8291 glb_itemslotdefs[slot].preposition,
8292 getSlotName(slot));
8293 }
8294 if (!quiet)
8295 reportMessage(buf);
8296 return false;
8297 }
8298
8299 // Check if the item is cursed. Can't remove cursed items.
8300 if (item->isCursed())
8301 {
8302 if (!quiet)
8303 {
8304 formatAndReport("%U <try> to remove %IU, but %Ip <I:be> evil!", item);
8305 }
8306 if (isAvatar())
8307 item->markCursedKnown();
8308
8309 return false;
8310 }
8311
8312 // Find a slot for it to go to.
8313 if (!findItemSlot(ix, iy))
8314 {
8315 if (!quiet)
8316 {
8317 formatAndReport("%U <have> no room to hold %IU.", item);
8318 }
8319
8320 return false;
8321 }
8322
8323 return true;
8324 }
8325
8326 bool
actionDequip(ITEMSLOT_NAMES slot,bool quiet)8327 MOB::actionDequip(ITEMSLOT_NAMES slot, bool quiet)
8328 {
8329 ITEM *item;
8330 BUF buf;
8331 int ix, iy;
8332
8333 if (!ableToDequip(slot, quiet))
8334 return false;
8335
8336 item = getEquippedItem(slot);
8337
8338 // Find a slot for it to go to.
8339 findItemSlot(ix, iy);
8340
8341 // Take the item off...
8342 item->setPos(ix, iy);
8343 if (!quiet)
8344 formatAndReport("%U <remove> %IU from %r %B1.",
8345 item, getSlotName(slot));
8346
8347 rebuildAppearance();
8348 rebuildWornIntrinsic();
8349
8350 return true;
8351 }
8352
8353 bool
actionPray()8354 MOB::actionPray()
8355 {
8356 // Non avatars have it simple.
8357 if (!isAvatar())
8358 {
8359 formatAndReport("%U <utter> a quick prayer.");
8360 return true;
8361 }
8362
8363 formatAndReport("%U <query> the gods...");
8364
8365 pietyReport();
8366
8367 return true;
8368 }
8369
8370 bool
actionPossess(MOB * target,int duration)8371 MOB::actionPossess(MOB *target, int duration)
8372 {
8373 if (target == this)
8374 {
8375 formatAndReport("%U already <control> %MU.", target);
8376 return true;
8377 }
8378 target->setTimedIntrinsic(this, INTRINSIC_POSSESSED, duration);
8379 formatAndReport("%U <gain> control of %MU.", target);
8380 // Transfer avatarhood.
8381 if (isAvatar())
8382 setAvatar(target);
8383 // The source creature is marked as braindead.
8384 setIntrinsic(INTRINSIC_BRAINDEAD);
8385 return true;
8386 }
8387
8388 bool
actionReleasePossession(bool systemshock)8389 MOB::actionReleasePossession(bool systemshock)
8390 {
8391 if (!hasIntrinsic(INTRINSIC_POSSESSED))
8392 {
8393 formatAndReport("%U <be> already of one mind.");
8394 return true;
8395 }
8396
8397 MOB *orig;
8398
8399 orig = getInflictorOfIntrinsic(INTRINSIC_POSSESSED);
8400 if (!orig || orig->hasIntrinsic(INTRINSIC_DEAD))
8401 {
8402 formatAndReport("%U <realize> %r original body is no more!");
8403 // Clear status to return original controller
8404 clearIntrinsic(INTRINSIC_POSSESSED);
8405 // This counts as another death - which handles the avatar
8406 // suiciding here.
8407 // Hmm... If our orig exists we technically should be becameitem
8408 // since we are now a corpse... Though, on further thought,
8409 // tha twas handled *when* it became a corpse and this is
8410 // about the victory screen. If we allow the user to wait
8411 // for their body to decay, this needs to be tightned
8412 // up anyways.
8413 triggerAsDeath(ATTACK_LOSTBODY, 0, 0, false);
8414 return true;
8415 }
8416
8417 // Remove any braindead status
8418 orig->clearIntrinsic(INTRINSIC_BRAINDEAD);
8419
8420 // If we are the avatar, restore avatar pointer
8421 if (isAvatar())
8422 {
8423 MAP *avatarmap;
8424
8425 avatarmap = glbCurLevel->findMapWithMob(orig);
8426 setAvatar(orig);
8427 if (avatarmap && avatarmap != glbCurLevel)
8428 {
8429 // Technically, we want to change levels right away
8430 // However, that is a very dangerous thing to do inside of
8431 // actionReleasePossession as people like receiveDamage
8432 // naively assume that glbCurLevel remains constant. You
8433 // can't just fix those guys either, as they are called
8434 // multiple times - say if an area attack fireball kills
8435 // the possessed critter.
8436 // MAP::changeCurrentLevel(avatarmap);
8437 MAP::delayedLevelChange(avatarmap);
8438 }
8439 }
8440
8441 // Clear status to return original controller
8442 orig->formatAndReport("%U <release> control of %MU.", this);
8443 clearIntrinsic(INTRINSIC_POSSESSED);
8444
8445 // Apply system shock now.
8446 if (systemshock)
8447 orig->systemshock();
8448
8449 return true;
8450 }
8451
8452 bool
actionForget(SPELL_NAMES spell,SKILL_NAMES skill)8453 MOB::actionForget(SPELL_NAMES spell, SKILL_NAMES skill)
8454 {
8455 bool canforget = false;
8456 BUF buf;
8457 ITEM *item;
8458 int i;
8459 const char *prereq;
8460
8461 if (spell == SPELL_NONE && skill == SKILL_NONE)
8462 {
8463 // Nothing to foget.
8464 formatAndReport("%U <suffer> from deja vu.");
8465 return true;
8466 }
8467
8468 // Each in turn.
8469 if (spell != SPELL_NONE)
8470 {
8471 if (!hasSpell(spell))
8472 {
8473 formatAndReport("%U <forget> %B1 before knowing it. Impressive.",
8474 glb_spelldefs[spell].name);
8475 }
8476 else if ((item = getSourceOfIntrinsic((INTRINSIC_NAMES) glb_spelldefs[spell].intrinsic)))
8477 {
8478 formatAndReport("%U cannot forget %B1 while using %IU.",
8479 item, glb_spelldefs[spell].name);
8480 }
8481 else
8482 {
8483 canforget = true;
8484
8485 // Check that nothing we know prevents us from forgetting it.
8486 for (i = 0; i < NUM_SPELLS; i++)
8487 {
8488 if (i == spell)
8489 continue;
8490
8491 if (!hasSpell((SPELL_NAMES) i))
8492 continue;
8493
8494 prereq = glb_spelldefs[i].prereq;
8495 while (*prereq)
8496 {
8497 if (*((u8 *) prereq) == spell)
8498 {
8499 canforget = false;
8500 buf.sprintf("Knowing %s prevents %s from being forgotten.",
8501 glb_spelldefs[i].name,
8502 glb_spelldefs[spell].name);
8503 reportMessage(buf);
8504 }
8505 prereq++;
8506 }
8507 }
8508
8509 if (canforget)
8510 {
8511 formatAndReport("%U <try> to forget %B1.",
8512 glb_spelldefs[spell].name);
8513 clearIntrinsic((INTRINSIC_NAMES) glb_spelldefs[spell].intrinsic);
8514 // Remove from base type, if present.
8515 MOB *base;
8516 base = getBaseType();
8517 if (base)
8518 base->clearIntrinsic((INTRINSIC_NAMES) glb_spelldefs[spell].intrinsic);
8519
8520 // Report new number of slots.
8521 formatAndReport("%U <have> %B1.",
8522 gram_createcount("free spell slot",
8523 getFreeSpellSlots(),
8524 true));
8525 }
8526 }
8527 }
8528
8529 if (skill != SKILL_NONE)
8530 {
8531 if (!hasSkill(skill))
8532 {
8533 formatAndReport("%U <forget> %B1 before knowing it. Impressive.",
8534 glb_skilldefs[skill].name);
8535 }
8536 else if ((item = getSourceOfIntrinsic((INTRINSIC_NAMES) glb_skilldefs[skill].intrinsic)))
8537 {
8538 formatAndReport("%U cannot forget %B1 while using %IU.",
8539 item,
8540 glb_skilldefs[skill].name);
8541 }
8542 else
8543 {
8544 canforget = true;
8545
8546 // Check that nothing we know prevents us from forgetting it.
8547 for (i = 0; i < NUM_SKILLS; i++)
8548 {
8549 if (i == skill)
8550 continue;
8551
8552 if (!hasSkill((SKILL_NAMES) i))
8553 continue;
8554
8555 prereq = glb_skilldefs[i].prereq;
8556 while (*prereq)
8557 {
8558 if (*((u8 *) prereq) == spell)
8559 {
8560 canforget = false;
8561 buf.sprintf("Knowing %s prevents %s from being forgotten.",
8562 glb_skilldefs[i].name,
8563 glb_skilldefs[skill].name);
8564 reportMessage(buf);
8565 }
8566 prereq++;
8567 }
8568 }
8569
8570 if (canforget)
8571 {
8572 formatAndReport("%U <try> to forget %B1.",
8573 glb_skilldefs[skill].name);
8574 clearIntrinsic((INTRINSIC_NAMES) glb_skilldefs[skill].intrinsic);
8575 // Remove from base type, if present.
8576 MOB *base;
8577 base = getBaseType();
8578 if (base)
8579 base->clearIntrinsic((INTRINSIC_NAMES) glb_skilldefs[spell].intrinsic);
8580 // Report new number of slots.
8581 // Report new number of slots.
8582 formatAndReport("%U <have> %B1.",
8583 gram_createcount("free skill slot",
8584 getFreeSkillSlots(),
8585 true));
8586 }
8587 }
8588 }
8589
8590 return canforget;
8591 }
8592
8593 // We store what our action bars are bound to in this packed
8594 // structure. This both reduces the size required and ensures
8595 // we can store both spells and actions.
8596 //
8597 // It also secretly clamps our number of spells at 127 and number
8598 // of action commands to 127. The latter is likely not a problem,
8599 // but I look forward to some day running into the former limit
8600 // and cursing the fool that left it here.
8601
8602 u8
action_packStripButton(ACTION_NAMES action)8603 action_packStripButton(ACTION_NAMES action)
8604 {
8605 return action;
8606 }
8607
8608 u8
action_packStripButton(SPELL_NAMES spell)8609 action_packStripButton(SPELL_NAMES spell)
8610 {
8611 return spell | 128;
8612 }
8613
8614 void
action_unpackStripButton(u8 value,ACTION_NAMES & action,SPELL_NAMES & spell)8615 action_unpackStripButton(u8 value, ACTION_NAMES &action, SPELL_NAMES &spell)
8616 {
8617 spell = SPELL_NONE;
8618 action = ACTION_NONE;
8619 if (value & 128)
8620 spell = (SPELL_NAMES) (value & 127);
8621 else
8622 action = (ACTION_NAMES) value;
8623 }
8624
8625 SPRITE_NAMES
action_spriteFromStripButton(u8 value,int * grey)8626 action_spriteFromStripButton(u8 value, int *grey)
8627 {
8628 SPELL_NAMES spell;
8629 ACTION_NAMES action;
8630
8631 action_unpackStripButton(value, action, spell);
8632
8633 if (grey)
8634 *grey = 257;
8635
8636 if (spell != SPELL_NONE)
8637 {
8638 if (grey)
8639 {
8640 MOB *avatar = MOB::getAvatar();
8641 if (avatar)
8642 {
8643 *grey = avatar->spellCastability(spell);
8644 }
8645 else
8646 {
8647 // Dead people have no spells!
8648 *grey = 0;
8649 }
8650 }
8651 return (SPRITE_NAMES) glb_spelldefs[spell].tile;
8652 }
8653 else
8654 return (SPRITE_NAMES) glb_actiondefs[action].tile;
8655 }
8656
8657 void
action_indexToOverlayPos(int index,int & x,int & y)8658 action_indexToOverlayPos(int index, int &x, int &y)
8659 {
8660 if (index < 15)
8661 {
8662 x = index * 2;
8663 y = -2;
8664 }
8665 else if (index < 30)
8666 {
8667 x = index - 15;
8668 x *= 2;
8669 y = 20;
8670 }
8671 else if (index < 40)
8672 {
8673 x = -1;
8674 y = (index - 30) * 2;
8675 }
8676 else if (index < 50)
8677 {
8678 x = 29;
8679 y = (index - 40) * 2;
8680 }
8681 else
8682 {
8683 UT_ASSERT(!"Invalid buttons strip");
8684 x = 7 * 2;
8685 y = 5 * 2;
8686 }
8687 }
8688
8689