1 /* SCCS Id: @(#)steed.c 3.4 2003/01/10 */
2 /* Copyright (c) Kevin Hugo, 1998-1999. */
3 /* NetHack may be freely redistributed. See license for details. */
4
5 #include "hack.h"
6
7
8 #ifdef STEED
9
10 /* Monsters that might be ridden */
11 static NEARDATA const char steeds[] = {
12 S_QUADRUPED, S_UNICORN, S_ANGEL, S_CENTAUR, S_DRAGON, S_JABBERWOCK, '\0'
13 };
14
15 STATIC_DCL boolean FDECL(landing_spot, (coord *, int, int));
16
17 /* caller has decided that hero can't reach something while mounted */
18 void
rider_cant_reach()19 rider_cant_reach()
20 {
21 You("aren't skilled enough to reach from %s.", y_monnam(u.usteed));
22 }
23
24 /*** Putting the saddle on ***/
25
26 /* Can this monster wear a saddle? */
27 boolean
can_saddle(mtmp)28 can_saddle(mtmp)
29 struct monst *mtmp;
30 {
31 struct permonst *ptr = mtmp->data;
32
33 return (index(steeds, ptr->mlet) && (ptr->msize >= MZ_MEDIUM) &&
34 (!humanoid(ptr) || ptr->mlet == S_CENTAUR) &&
35 !amorphous(ptr) && !noncorporeal(ptr) &&
36 !is_whirly(ptr) && !unsolid(ptr));
37 }
38
39
40 int
use_saddle(otmp)41 use_saddle(otmp)
42 struct obj *otmp;
43 {
44 struct monst *mtmp;
45 struct permonst *ptr;
46 int chance;
47 const char *s;
48
49
50 /* Can you use it? */
51 if (nohands(youmonst.data)) {
52 You("have no hands!"); /* not `body_part(HAND)' */
53 return 0;
54 } else if (!freehand()) {
55 You("have no free %s.", body_part(HAND));
56 return 0;
57 }
58
59 /* Select an animal */
60 if (u.uswallow || Underwater || !getdir((char *)0)) {
61 pline(Never_mind);
62 return 0;
63 }
64 if (!u.dx && !u.dy) {
65 pline("Saddle yourself? Very funny...");
66 return 0;
67 }
68 if (!isok(u.ux+u.dx, u.uy+u.dy) ||
69 !(mtmp = m_at(u.ux+u.dx, u.uy+u.dy)) ||
70 !canspotmon(mtmp)) {
71 pline("I see nobody there.");
72 return 1;
73 }
74
75 /* Is this a valid monster? */
76 if (mtmp->misc_worn_check & W_SADDLE ||
77 which_armor(mtmp, W_SADDLE)) {
78 pline("%s doesn't need another one.", Monnam(mtmp));
79 return 1;
80 }
81 ptr = mtmp->data;
82 if (touch_petrifies(ptr) && !uarmg && !Stone_resistance) {
83 char kbuf[BUFSZ];
84
85 You("touch %s.", mon_nam(mtmp));
86 if (!(poly_when_stoned(youmonst.data) && polymon(PM_STONE_GOLEM))) {
87 Sprintf(kbuf, "attempting to saddle %s", an(mtmp->data->mname));
88 instapetrify(kbuf);
89 }
90 }
91 if (ptr == &mons[PM_INCUBUS] || ptr == &mons[PM_SUCCUBUS]) {
92 pline("Shame on you!");
93 exercise(A_WIS, FALSE);
94 return 1;
95 }
96 if (mtmp->isminion || mtmp->isshk || mtmp->ispriest ||
97 mtmp->isgd || mtmp->iswiz) {
98 pline("I think %s would mind.", mon_nam(mtmp));
99 return 1;
100 }
101 if (!can_saddle(mtmp)) {
102 You_cant("saddle such a creature.");
103 return 1;
104 }
105
106 /* Calculate your chance */
107 chance = ACURR(A_DEX) + ACURR(A_CHA)/2 + 2*mtmp->mtame;
108 chance += u.ulevel * (mtmp->mtame ? 20 : 5);
109 if (!mtmp->mtame) chance -= 10*mtmp->m_lev;
110 if (Role_if(PM_KNIGHT))
111 chance += 20;
112 switch (P_SKILL(P_RIDING)) {
113 case P_ISRESTRICTED:
114 case P_UNSKILLED:
115 default:
116 chance -= 20; break;
117 case P_BASIC:
118 break;
119 case P_SKILLED:
120 chance += 15; break;
121 case P_EXPERT:
122 chance += 30; break;
123 }
124 if (Confusion || Fumbling || Glib)
125 chance -= 20;
126 else if (uarmg &&
127 (s = OBJ_DESCR(objects[uarmg->otyp])) != (char *)0 &&
128 !strncmp(s, "riding ", 7))
129 /* Bonus for wearing "riding" (but not fumbling) gloves */
130 chance += 10;
131 else if (uarmf &&
132 (s = OBJ_DESCR(objects[uarmf->otyp])) != (char *)0 &&
133 !strncmp(s, "riding ", 7))
134 /* ... or for "riding boots" */
135 chance += 10;
136 if (otmp->cursed)
137 chance -= 50;
138
139 /* Make the attempt */
140 if (rn2(100) < chance) {
141 You("put the saddle on %s.", mon_nam(mtmp));
142 if (otmp->owornmask) remove_worn_item(otmp, FALSE);
143 freeinv(otmp);
144 /* mpickobj may free otmp it if merges, but we have already
145 checked for a saddle above, so no merger should happen */
146 (void) mpickobj(mtmp, otmp);
147 mtmp->misc_worn_check |= W_SADDLE;
148 otmp->owornmask = W_SADDLE;
149 otmp->leashmon = mtmp->m_id;
150 update_mon_intrinsics(mtmp, otmp, TRUE, FALSE);
151 } else
152 pline("%s resists!", Monnam(mtmp));
153 return 1;
154 }
155
156
157 /*** Riding the monster ***/
158
159 /* Can we ride this monster? Caller should also check can_saddle() */
160 boolean
can_ride(mtmp)161 can_ride(mtmp)
162 struct monst *mtmp;
163 {
164 return (mtmp->mtame && humanoid(youmonst.data) &&
165 !verysmall(youmonst.data) && !bigmonst(youmonst.data) &&
166 (!Underwater || is_swimmer(mtmp->data)));
167 }
168
169
170 int
doride()171 doride()
172 {
173 boolean forcemount = FALSE;
174
175 if (u.usteed)
176 dismount_steed(DISMOUNT_BYCHOICE);
177 else if (getdir((char *)0) && isok(u.ux+u.dx, u.uy+u.dy)) {
178 #ifdef WIZARD
179 if (wizard && yn("Force the mount to succeed?") == 'y')
180 forcemount = TRUE;
181 #endif
182 return (mount_steed(m_at(u.ux+u.dx, u.uy+u.dy), forcemount));
183 } else
184 return 0;
185 return 1;
186 }
187
188
189 /* Start riding, with the given monster */
190 boolean
mount_steed(mtmp,force)191 mount_steed(mtmp, force)
192 struct monst *mtmp; /* The animal */
193 boolean force; /* Quietly force this animal */
194 {
195 struct obj *otmp;
196 char buf[BUFSZ];
197 struct permonst *ptr;
198
199 /* Sanity checks */
200 if (u.usteed) {
201 You("are already riding %s.", mon_nam(u.usteed));
202 return (FALSE);
203 }
204
205 /* Is the player in the right form? */
206 if (Hallucination && !force) {
207 pline("Maybe you should find a designated driver.");
208 return (FALSE);
209 }
210 /* While riding Wounded_legs refers to the steed's,
211 * not the hero's legs.
212 * That opens up a potential abuse where the player
213 * can mount a steed, then dismount immediately to
214 * heal leg damage, because leg damage is always
215 * healed upon dismount (Wounded_legs context switch).
216 * By preventing a hero with Wounded_legs from
217 * mounting a steed, the potential for abuse is
218 * minimized, if not eliminated altogether.
219 */
220 if (Wounded_legs) {
221 Your("%s are in no shape for riding.", makeplural(body_part(LEG)));
222 #ifdef WIZARD
223 if (force && wizard && yn("Heal your legs?") == 'y')
224 HWounded_legs = EWounded_legs = 0;
225 else
226 #endif
227 return (FALSE);
228 }
229
230 if (Upolyd && (!humanoid(youmonst.data) || verysmall(youmonst.data) ||
231 bigmonst(youmonst.data) || slithy(youmonst.data))) {
232 You("won't fit on a saddle.");
233 return (FALSE);
234 }
235 if(!force && (near_capacity() > SLT_ENCUMBER)) {
236 You_cant("do that while carrying so much stuff.");
237 return (FALSE);
238 }
239
240 /* Can the player reach and see the monster? */
241 if (!mtmp || (!force && ((Blind && !Blind_telepat) ||
242 mtmp->mundetected ||
243 mtmp->m_ap_type == M_AP_FURNITURE ||
244 mtmp->m_ap_type == M_AP_OBJECT))) {
245 pline("I see nobody there.");
246 return (FALSE);
247 }
248 if (u.uswallow || u.ustuck || u.utrap || Punished ||
249 !test_move(u.ux, u.uy, mtmp->mx-u.ux, mtmp->my-u.uy, TEST_MOVE)) {
250 if (Punished || !(u.uswallow || u.ustuck || u.utrap))
251 You("are unable to swing your %s over.", body_part(LEG));
252 else
253 You("are stuck here for now.");
254 return (FALSE);
255 }
256
257 /* Is this a valid monster? */
258 otmp = which_armor(mtmp, W_SADDLE);
259 if (!otmp) {
260 pline("%s is not saddled.", Monnam(mtmp));
261 return (FALSE);
262 }
263 ptr = mtmp->data;
264 if (touch_petrifies(ptr) && !Stone_resistance) {
265 char kbuf[BUFSZ];
266
267 You("touch %s.", mon_nam(mtmp));
268 Sprintf(kbuf, "attempting to ride %s", an(mtmp->data->mname));
269 instapetrify(kbuf);
270 }
271 if (!mtmp->mtame || mtmp->isminion) {
272 pline("I think %s would mind.", mon_nam(mtmp));
273 return (FALSE);
274 }
275 if (mtmp->mtrapped) {
276 struct trap *t = t_at(mtmp->mx, mtmp->my);
277
278 You_cant("mount %s while %s's trapped in %s.",
279 mon_nam(mtmp), mhe(mtmp),
280 an(defsyms[trap_to_defsym(t->ttyp)].explanation));
281 return (FALSE);
282 }
283
284 if (!force && !Role_if(PM_KNIGHT) && !(--mtmp->mtame)) {
285 /* no longer tame */
286 newsym(mtmp->mx, mtmp->my);
287 pline("%s resists%s!", Monnam(mtmp),
288 mtmp->mleashed ? " and its leash comes off" : "");
289 if (mtmp->mleashed) m_unleash(mtmp, FALSE);
290 return (FALSE);
291 }
292 if (!force && Underwater && !is_swimmer(ptr)) {
293 You_cant("ride that creature while under water.");
294 return (FALSE);
295 }
296 if (!can_saddle(mtmp) || !can_ride(mtmp)) {
297 You_cant("ride such a creature.");
298 return (0);
299 }
300
301 /* Is the player impaired? */
302 if (!force && !is_floater(ptr) && !is_flyer(ptr) &&
303 Levitation && !Lev_at_will) {
304 You("cannot reach %s.", mon_nam(mtmp));
305 return (FALSE);
306 }
307 if (!force && uarm && is_metallic(uarm) &&
308 greatest_erosion(uarm)) {
309 Your("%s armor is too stiff to be able to mount %s.",
310 uarm->oeroded ? "rusty" : "corroded",
311 mon_nam(mtmp));
312 return (FALSE);
313 }
314 if (!force && (Confusion || Fumbling || Glib || Wounded_legs ||
315 otmp->cursed || (u.ulevel+mtmp->mtame < rnd(MAXULEV/2+5)))) {
316 if (Levitation) {
317 pline("%s slips away from you.", Monnam(mtmp));
318 return FALSE;
319 }
320 You("slip while trying to get on %s.", mon_nam(mtmp));
321
322 Sprintf(buf, "slipped while mounting %s",
323 /* "a saddled mumak" or "a saddled pony called Dobbin" */
324 x_monnam(mtmp, ARTICLE_A, (char *)0,
325 SUPPRESS_IT|SUPPRESS_INVISIBLE|SUPPRESS_HALLUCINATION,
326 TRUE));
327 losehp(rn1(5,10), buf, NO_KILLER_PREFIX);
328 return (FALSE);
329 }
330
331 /* Success */
332 if (!force) {
333 if (Levitation && !is_floater(ptr) && !is_flyer(ptr))
334 /* Must have Lev_at_will at this point */
335 pline("%s magically floats up!", Monnam(mtmp));
336 You("mount %s.", mon_nam(mtmp));
337 }
338 /* setuwep handles polearms differently when you're mounted */
339 if (uwep && is_pole(uwep)) unweapon = FALSE;
340 u.usteed = mtmp;
341 remove_monster(mtmp->mx, mtmp->my);
342 teleds(mtmp->mx, mtmp->my, TRUE);
343 return (TRUE);
344 }
345
346
347 /* You and your steed have moved */
348 void
exercise_steed()349 exercise_steed()
350 {
351 if (!u.usteed)
352 return;
353
354 /* It takes many turns of riding to exercise skill */
355 if (u.urideturns++ >= 100) {
356 u.urideturns = 0;
357 use_skill(P_RIDING, 1);
358 }
359 return;
360 }
361
362
363 /* The player kicks or whips the steed */
364 void
kick_steed()365 kick_steed()
366 {
367 char He[4];
368 if (!u.usteed)
369 return;
370
371 /* [ALI] Various effects of kicking sleeping/paralyzed steeds */
372 if (u.usteed->msleeping || !u.usteed->mcanmove) {
373 /* We assume a message has just been output of the form
374 * "You kick <steed>."
375 */
376 Strcpy(He, mhe(u.usteed));
377 *He = highc(*He);
378 if ((u.usteed->mcanmove || u.usteed->mfrozen) && !rn2(2)) {
379 if (u.usteed->mcanmove)
380 u.usteed->msleeping = 0;
381 else if (u.usteed->mfrozen > 2)
382 u.usteed->mfrozen -= 2;
383 else {
384 u.usteed->mfrozen = 0;
385 u.usteed->mcanmove = 1;
386 }
387 if (u.usteed->msleeping || !u.usteed->mcanmove)
388 pline("%s stirs.", He);
389 else
390 pline("%s rouses %sself!", He, mhim(u.usteed));
391 } else
392 pline("%s does not respond.", He);
393 return;
394 }
395
396 /* Make the steed less tame and check if it resists */
397 if (u.usteed->mtame) u.usteed->mtame--;
398 if (!u.usteed->mtame && u.usteed->mleashed) m_unleash(u.usteed, TRUE);
399 if (!u.usteed->mtame || (u.ulevel+u.usteed->mtame < rnd(MAXULEV/2+5))) {
400 newsym(u.usteed->mx, u.usteed->my);
401 dismount_steed(DISMOUNT_THROWN);
402 return;
403 }
404
405 pline("%s gallops!", Monnam(u.usteed));
406 u.ugallop += rn1(20, 30);
407 return;
408 }
409
410 /*
411 * Try to find a dismount point adjacent to the steed's location.
412 * If all else fails, try enexto(). Use enexto() as a last resort because
413 * enexto() chooses its point randomly, possibly even outside the
414 * room's walls, which is not what we want.
415 * Adapted from mail daemon code.
416 */
417 STATIC_OVL boolean
landing_spot(spot,reason,forceit)418 landing_spot(spot, reason, forceit)
419 coord *spot; /* landing position (we fill it in) */
420 int reason;
421 int forceit;
422 {
423 int i = 0, x, y, distance, min_distance = -1;
424 boolean found = FALSE;
425 struct trap *t;
426
427 /* avoid known traps (i == 0) and boulders, but allow them as a backup */
428 if (reason != DISMOUNT_BYCHOICE || Stunned || Confusion || Fumbling) i = 1;
429 for (; !found && i < 2; ++i) {
430 for (x = u.ux-1; x <= u.ux+1; x++)
431 for (y = u.uy-1; y <= u.uy+1; y++) {
432 if (!isok(x, y) || (x == u.ux && y == u.uy)) continue;
433
434 if (ACCESSIBLE(levl[x][y].typ) &&
435 !MON_AT(x,y) && !closed_door(x,y)) {
436 distance = distu(x,y);
437 if (min_distance < 0 || distance < min_distance ||
438 (distance == min_distance && rn2(2))) {
439 if (i > 0 || (((t = t_at(x, y)) == 0 || !t->tseen) &&
440 (!sobj_at(BOULDER, x, y) ||
441 throws_rocks(youmonst.data)))) {
442 spot->x = x;
443 spot->y = y;
444 min_distance = distance;
445 found = TRUE;
446 }
447 }
448 }
449 }
450 }
451
452 /* If we didn't find a good spot and forceit is on, try enexto(). */
453 if (forceit && min_distance < 0 &&
454 !enexto(spot, u.ux, u.uy, youmonst.data))
455 return FALSE;
456
457 return found;
458 }
459
460 /* Stop riding the current steed */
461 void
dismount_steed(reason)462 dismount_steed(reason)
463 int reason; /* Player was thrown off etc. */
464 {
465 struct monst *mtmp;
466 struct obj *otmp;
467 coord cc;
468 const char *verb = "fall";
469 boolean repair_leg_damage = TRUE;
470 unsigned save_utrap = u.utrap;
471 boolean have_spot = landing_spot(&cc,reason,0);
472
473 mtmp = u.usteed; /* make a copy of steed pointer */
474 /* Sanity check */
475 if (!mtmp) /* Just return silently */
476 return;
477
478 /* Check the reason for dismounting */
479 otmp = which_armor(mtmp, W_SADDLE);
480 switch (reason) {
481 case DISMOUNT_THROWN:
482 verb = "are thrown";
483 case DISMOUNT_FELL:
484 You("%s off of %s!", verb, mon_nam(mtmp));
485 if (!have_spot) have_spot = landing_spot(&cc,reason,1);
486 losehp(rn1(10,10), "riding accident", KILLED_BY_AN);
487 set_wounded_legs(BOTH_SIDES, (int)HWounded_legs + rn1(5,5));
488 repair_leg_damage = FALSE;
489 break;
490 case DISMOUNT_POLY:
491 You("can no longer ride %s.", mon_nam(u.usteed));
492 if (!have_spot) have_spot = landing_spot(&cc,reason,1);
493 break;
494 case DISMOUNT_ENGULFED:
495 /* caller displays message */
496 break;
497 case DISMOUNT_BONES:
498 /* hero has just died... */
499 break;
500 case DISMOUNT_GENERIC:
501 /* no messages, just make it so */
502 break;
503 case DISMOUNT_BYCHOICE:
504 default:
505 if (otmp && otmp->cursed) {
506 You("can't. The saddle %s cursed.",
507 otmp->bknown ? "is" : "seems to be");
508 otmp->bknown = TRUE;
509 return;
510 }
511 if (!have_spot) {
512 You("can't. There isn't anywhere for you to stand.");
513 return;
514 }
515 if (!mtmp->mnamelth) {
516 pline("You've been through the dungeon on %s with no name.",
517 an(mtmp->data->mname));
518 if (Hallucination)
519 pline("It felt good to get out of the rain.");
520 } else
521 You("dismount %s.", mon_nam(mtmp));
522 }
523 /* While riding these refer to the steed's legs
524 * so after dismounting they refer to the player's
525 * legs once again.
526 */
527 if (repair_leg_damage) HWounded_legs = EWounded_legs = 0;
528
529 /* Release the steed and saddle */
530 u.usteed = 0;
531 u.ugallop = 0L;
532
533 /* Set player and steed's position. Try moving the player first
534 unless we're in the midst of creating a bones file. */
535 if (reason == DISMOUNT_BONES) {
536 /* move the steed to an adjacent square */
537 if (enexto(&cc, u.ux, u.uy, mtmp->data))
538 rloc_to(mtmp, cc.x, cc.y);
539 else /* evidently no room nearby; move steed elsewhere */
540 (void) rloc(mtmp, FALSE);
541 return;
542 }
543 if (!DEADMONSTER(mtmp)) {
544 place_monster(mtmp, u.ux, u.uy);
545 if (!u.uswallow && !u.ustuck && have_spot) {
546 struct permonst *mdat = mtmp->data;
547
548 /* The steed may drop into water/lava */
549 if (!is_flyer(mdat) && !is_floater(mdat) && !is_clinger(mdat)) {
550 if (is_pool(u.ux, u.uy)) {
551 if (!Underwater)
552 pline("%s falls into the %s!", Monnam(mtmp),
553 surface(u.ux, u.uy));
554 if (!is_swimmer(mdat) && !amphibious(mdat)) {
555 killed(mtmp);
556 adjalign(-1);
557 }
558 } else if (is_lava(u.ux, u.uy)) {
559 pline("%s is pulled into the lava!", Monnam(mtmp));
560 if (!likes_lava(mdat)) {
561 killed(mtmp);
562 adjalign(-1);
563 }
564 }
565 }
566 /* Steed dismounting consists of two steps: being moved to another
567 * square, and descending to the floor. We have functions to do
568 * each of these activities, but they're normally called
569 * individually and include an attempt to look at or pick up the
570 * objects on the floor:
571 * teleds() --> spoteffects() --> pickup()
572 * float_down() --> pickup()
573 * We use this kludge to make sure there is only one such attempt.
574 *
575 * Clearly this is not the best way to do it. A full fix would
576 * involve having these functions not call pickup() at all, instead
577 * calling them first and calling pickup() afterwards. But it
578 * would take a lot of work to keep this change from having any
579 * unforseen side effects (for instance, you would no longer be
580 * able to walk onto a square with a hole, and autopickup before
581 * falling into the hole).
582 */
583 /* [ALI] No need to move the player if the steed died. */
584 if (!DEADMONSTER(mtmp)) {
585 /* Keep steed here, move the player to cc;
586 * teleds() clears u.utrap
587 */
588 in_steed_dismounting = TRUE;
589 teleds(cc.x, cc.y, TRUE);
590 in_steed_dismounting = FALSE;
591
592 /* Put your steed in your trap */
593 if (save_utrap)
594 (void) mintrap(mtmp);
595 }
596 /* Couldn't... try placing the steed */
597 } else if (enexto(&cc, u.ux, u.uy, mtmp->data)) {
598 /* Keep player here, move the steed to cc */
599 rloc_to(mtmp, cc.x, cc.y);
600 /* Player stays put */
601 /* Otherwise, kill the steed */
602 } else {
603 killed(mtmp);
604 adjalign(-1);
605 }
606 }
607
608 /* Return the player to the floor */
609 if (reason != DISMOUNT_ENGULFED) {
610 in_steed_dismounting = TRUE;
611 (void) float_down(0L, W_SADDLE);
612 in_steed_dismounting = FALSE;
613 flags.botl = 1;
614 (void)encumber_msg();
615 vision_full_recalc = 1;
616 } else
617 flags.botl = 1;
618 /* polearms behave differently when not mounted */
619 if (uwep && is_pole(uwep)) unweapon = TRUE;
620 return;
621 }
622
623 void
place_monster(mon,x,y)624 place_monster(mon, x, y)
625 struct monst *mon;
626 int x, y;
627 {
628 if (mon == u.usteed ||
629 /* special case is for convoluted vault guard handling */
630 (DEADMONSTER(mon) && !(mon->isgd && x == 0 && y == 0))) {
631 impossible("placing %s onto map?",
632 (mon == u.usteed) ? "steed" : "defunct monster");
633 return;
634 }
635 mon->mx = x, mon->my = y;
636 level.monsters[x][y] = mon;
637 }
638
639 #endif /* STEED */
640
641 /*steed.c*/
642