1 | /* SCCS Id: @(#)steed.c 3.3 2000/07/29 */
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 |
16 | /*** Putting the saddle on ***/
17 |
18 | /* Can this monster wear a saddle? */
19 | boolean
20 | can_saddle(mtmp)
21 | struct monst *mtmp;
22 | {
23 | struct permonst *ptr = mtmp->data;
24 |
25 | return (index(steeds, ptr->mlet) && (ptr->msize >= MZ_MEDIUM) &&
26 | (!humanoid(ptr) || ptr->mlet == S_CENTAUR) &&
27 | !amorphous(ptr) && !noncorporeal(ptr) &&
28 | !is_whirly(ptr) && !unsolid(ptr));
29 | }
30 |
31 |
32 | int
33 | use_saddle(otmp)
34 | struct obj *otmp;
35 | {
36 | struct monst *mtmp;
37 | struct permonst *ptr;
38 | int chance;
39 | const char *s;
40 |
41 |
42 | /* Can you use it? */
43 | if (nohands(youmonst.data)) {
44 | You("have no hands!"); /* not `body_part(HAND)' */
45 | return 0;
46 | } else if (!freehand()) {
47 | You("have no free %s.", body_part(HAND));
48 | return 0;
49 | }
50 |
51 | /* Select an animal */
52 | if (u.uswallow || Underwater || !getdir((char *)0)) {
53 | pline(Never_mind);
54 | return 0;
55 | }
56 | if (!u.dx && !u.dy) {
57 | pline("Saddle yourself? Very funny...");
58 | return 0;
59 | }
60 | if (!isok(u.ux+u.dx, u.uy+u.dy) ||
61 | !(mtmp = m_at(u.ux+u.dx, u.uy+u.dy)) ||
62 | !canspotmon(mtmp)) {
63 | pline("I see nobody there.");
64 | return 1;
65 | }
66 |
67 | /* Is this a valid monster? */
68 | if (mtmp->misc_worn_check & W_SADDLE ||
69 | which_armor(mtmp, W_SADDLE)) {
70 | pline("%s doesn't need another one.", Monnam(mtmp));
71 | return 1;
72 | }
73 | ptr = mtmp->data;
74 | if (touch_petrifies(ptr) && !Stone_resistance) {
75 | char kbuf[BUFSZ];
76 |
77 | You("touch %s.", mon_nam(mtmp));
78 | if (!(poly_when_stoned(youmonst.data) && polymon(PM_STONE_GOLEM))) {
79 | Sprintf(kbuf, "attempting to saddle %s", a_monnam(mtmp));
80 | instapetrify(kbuf);
81 | }
82 | }
83 | if (ptr == &mons[PM_INCUBUS] || ptr == &mons[PM_SUCCUBUS]) {
84 | pline("Shame on you!");
85 | exercise(A_WIS, FALSE);
86 | return 1;
87 | }
88 | if (mtmp->isminion || mtmp->isshk || mtmp->ispriest ||
89 | mtmp->isgd || mtmp->iswiz) {
90 | pline("I think %s would mind.", mon_nam(mtmp));
91 | return 1;
92 | }
93 | if (!can_saddle(mtmp)) {
94 | You_cant("saddle such a creature.");
95 | return 1;
96 | }
97 |
98 | /* Calculate your chance */
99 | chance = ACURR(A_DEX) + ACURR(A_CHA)/2 + 2*mtmp->mtame;
100 | chance += u.ulevel * (mtmp->mtame ? 20 : 5);
101 | if (!mtmp->mtame) chance -= 10*mtmp->m_lev;
102 | if (Role_if(PM_KNIGHT))
103 | chance += 20;
104 | switch (P_SKILL(P_RIDING)) {
105 | case P_ISRESTRICTED:
106 | case P_UNSKILLED:
107 | default:
108 | chance -= 20; break;
109 | case P_BASIC:
110 | break;
111 | case P_SKILLED:
112 | chance += 15; break;
113 | case P_EXPERT:
114 | chance += 30; break;
115 | }
116 | if (Confusion || Fumbling || Glib)
117 | chance -= 20;
118 | else if (uarmg &&
119 | (s = OBJ_DESCR(objects[uarmg->otyp])) != (char *)0 &&
120 | !strncmp(s, "riding ", 7))
121 | /* Bonus for wearing "riding" (but not fumbling) gloves */
122 | chance += 10;
123 | else if (uarmf &&
124 | (s = OBJ_DESCR(objects[uarmf->otyp])) != (char *)0 &&
125 | !strncmp(s, "riding ", 7))
126 | /* ... or for "riding boots" */
127 | chance += 10;
128 | if (otmp->cursed)
129 | chance -= 50;
130 |
131 | /* Make the attempt */
132 | if (rn2(100) < chance) {
133 | You("put the saddle on %s.", mon_nam(mtmp));
134 | freeinv(otmp);
135 | /* mpickobj may free otmp it if merges, but we have already
136 | checked for a saddle above, so no merger should happen */
137 | (void) mpickobj(mtmp, otmp);
138 | mtmp->misc_worn_check |= W_SADDLE;
139 | otmp->owornmask = W_SADDLE;
140 | otmp->leashmon = mtmp->m_id;
141 | update_mon_intrinsics(mtmp, otmp, TRUE);
142 | } else
143 | pline("%s resists!", Monnam(mtmp));
144 | return 1;
145 | }
146 |
147 |
148 | /*** Riding the monster ***/
149 |
150 | /* Can we ride this monster? Caller should also check can_saddle() */
151 | boolean
152 | can_ride(mtmp)
153 | struct monst *mtmp;
154 | {
155 | return (mtmp->mtame && humanoid(youmonst.data) &&
156 | !verysmall(youmonst.data) && !bigmonst(youmonst.data) &&
157 | (!Underwater || is_swimmer(mtmp->data)));
158 | }
159 |
160 |
161 | int
162 | doride()
163 | {
164 | boolean forcemount = FALSE;
165 |
166 | if (u.usteed)
167 | dismount_steed(DISMOUNT_BYCHOICE);
168 | else if (getdir((char *)0) && isok(u.ux+u.dx, u.uy+u.dy)) {
169 | #ifdef WIZARD
170 | if (wizard && yn("Force the mount to succeed?") == 'y')
171 | forcemount = TRUE;
172 | #endif
173 | return (mount_steed(m_at(u.ux+u.dx, u.uy+u.dy), forcemount));
174 | } else
175 | return 0;
176 | return 1;
177 | }
178 |
179 |
180 | /* Start riding, with the given monster */
181 | boolean
182 | mount_steed(mtmp, force)
183 | struct monst *mtmp; /* The animal */
184 | boolean force; /* Quietly force this animal */
185 | {
186 | struct obj *otmp;
187 | char buf[BUFSZ];
188 | struct permonst *ptr;
189 |
190 |
191 | /* Sanity checks */
192 | if (u.usteed) {
193 | if (!force)
194 | You("are already riding %s.", mon_nam(u.usteed));
195 | return (FALSE);
196 | }
197 |
198 | /* Is the player in the right form? */
199 | if (Hallucination && !force) {
200 | pline("Maybe you should find a designated driver.");
201 | return (FALSE);
202 | }
203 | if (Upolyd && (!humanoid(youmonst.data) || verysmall(youmonst.data) ||
204 | bigmonst(youmonst.data))) {
205 | if (!force)
206 | You("won't fit on a saddle.");
207 | return (FALSE);
208 | }
209 | if(!force && (near_capacity() > SLT_ENCUMBER)) {
210 | You_cant("do that while carrying so much stuff.");
211 | return (FALSE);
212 | }
213 |
214 | /* Can the player reach and see the monster? */
215 | if (u.uswallow || u.ustuck || u.utrap || Punished) {
216 | if (!force) {
217 | if (Punished)
218 | You("are unable to swing your %s over.",
219 | body_part(LEG));
220 | else
221 | You("are stuck here for now.");
222 | }
223 | return (FALSE);
224 | }
225 | if (!mtmp || (!force && ((Blind && !Blind_telepat) ||
226 | mtmp->mundetected ||
227 | mtmp->m_ap_type == M_AP_FURNITURE ||
228 | mtmp->m_ap_type == M_AP_OBJECT))) {
229 | if (!force)
230 | pline("I see nobody there.");
231 | return (FALSE);
232 | }
233 |
234 | /* Is this a valid monster? */
235 | otmp = which_armor(mtmp, W_SADDLE);
236 | if (!otmp) {
237 | pline("%s is not saddled.", Monnam(mtmp));
238 | return (FALSE);
239 | }
240 | ptr = mtmp->data;
241 | if (touch_petrifies(ptr) && !Stone_resistance) {
242 | char kbuf[BUFSZ];
243 |
244 | You("touch %s.", mon_nam(mtmp));
245 | Sprintf(kbuf, "attempting to ride %s", an(mtmp->data->mname));
246 | instapetrify(kbuf);
247 | }
248 | if (!mtmp->mtame || mtmp->isminion) {
249 | if (!force)
250 | pline("I think %s would mind.", mon_nam(mtmp));
251 | return (FALSE);
252 | }
253 | if (!force && !Role_if(PM_KNIGHT) && !(--mtmp->mtame)) {
254 | pline("%s resists!", Monnam(mtmp));
255 | return (FALSE);
256 | }
257 | if (!force && Underwater && !is_swimmer(ptr)) {
258 | You_cant("ride that creature while under water.");
259 | return (FALSE);
260 | }
261 | if (!can_saddle(mtmp) || !can_ride(mtmp)) {
262 | if (!force)
263 | You_cant("ride such a creature.");
264 | return (0);
265 | }
266 |
267 | /* Is the player impaired? */
268 | if (!force && !is_floater(ptr) && !is_flyer(ptr) &&
269 | Levitation && !Lev_at_will) {
270 | You("cannot reach %s.", mon_nam(mtmp));
271 | return (FALSE);
272 | }
273 | if (!force && uarm && is_metallic(uarm) &&
274 | greatest_erosion(uarm)) {
275 | Your("%s armor is too stiff to be able to mount %s.",
276 | uarm->oeroded ? "rusty" : "corroded",
277 | mon_nam(mtmp));
278 | return (FALSE);
279 | }
280 | if (!force && (Confusion || Fumbling || Glib || Wounded_legs ||
281 | otmp->cursed || (u.ulevel+mtmp->mtame < rnd(MAXULEV/2+5)))) {
282 | You("slip while trying to get on %s.", mon_nam(mtmp));
283 | /* Unfortunately we don't have a version of the monster-naming
284 | * function that works well with "a" and "the" but ignores
285 | * hallucination. Fortunately, we know the monster must be saddled
286 | * at this point, and that it can't have type_is_pname(), so we
287 | * don't need to worry about the special cases such a function
288 | * would have to consider.
289 | */
290 | Sprintf(buf, "slipped while mounting a saddled %s",
291 | m_monnam(mtmp));
292 | losehp(rn1(5,10), buf, NO_KILLER_PREFIX);
293 | return (FALSE);
294 | }
295 |
296 | /* Success */
297 | if (!force) {
298 | if (Levitation && !is_floater(ptr) && !is_flyer(ptr))
299 | /* Must have Lev_at_will at this point */
300 | pline("%s magically floats up!", Monnam(mtmp));
301 | You("mount %s.", mon_nam(mtmp));
302 | }
303 | u.usteed = mtmp;
304 | remove_monster(mtmp->mx, mtmp->my);
305 | teleds(mtmp->mx, mtmp->my);
306 | return (TRUE);
307 | }
308 |
309 |
310 | /* You and your steed have moved */
311 | void
312 | exercise_steed()
313 | {
314 | if (!u.usteed)
315 | return;
316 |
317 | /* It takes many turns of riding to exercise skill */
318 | if (u.urideturns++ >= 100) {
319 | u.urideturns = 0;
320 | use_skill(P_RIDING, 1);
321 | }
322 | return;
323 | }
324 |
325 |
326 | /* The player kicks or whips the steed */
327 | void
328 | kick_steed()
329 | {
330 | if (!u.usteed)
331 | return;
332 |
333 | /* Make the steed less tame and check if it resists */
334 | if (u.usteed->mtame) u.usteed->mtame--;
335 | if (!u.usteed->mtame || (u.ulevel+u.usteed->mtame < rnd(MAXULEV/2+5))) {
336 | dismount_steed(DISMOUNT_THROWN);
337 | return;
338 | }
339 |
340 | pline("%s gallops!", Monnam(u.usteed));
341 | u.ugallop += rn1(20, 30);
342 | return;
343 | }
344 |
345 |
346 | /* Stop riding the current steed */
347 | void
348 | dismount_steed(reason)
349 | int reason; /* Player was thrown off etc. */
350 | {
351 | struct monst *mtmp;
352 | struct obj *otmp;
353 | coord cc;
354 | const char *verb = "fall";
355 | boolean repair_leg_damage = TRUE;
356 | unsigned save_utrap = u.utrap;
357 |
358 | /* Sanity checks */
359 | if (!(mtmp = u.usteed))
360 | /* Just return silently */
361 | return;
362 |
363 | /* Check the reason for dismounting */
364 | otmp = which_armor(mtmp, W_SADDLE);
365 | switch (reason) {
366 | case DISMOUNT_THROWN:
367 | verb = "are thrown";
368 | case DISMOUNT_FELL:
369 | You("%s off of %s!", verb, mon_nam(mtmp));
370 | losehp(rn1(10,10), "riding accident", KILLED_BY_AN);
371 | HWounded_legs += rn1(5, 5);
372 | EWounded_legs |= BOTH_SIDES;
373 | repair_leg_damage = FALSE;
374 | break;
375 | case DISMOUNT_POLY:
376 | You("can no longer ride %s.", mon_nam(u.usteed));
377 | break;
378 | case DISMOUNT_ENGULFED:
379 | /* caller displays message */
380 | break;
381 | case DISMOUNT_GENERIC:
382 | /* no messages, just make it so */
383 | break;
384 | case DISMOUNT_BYCHOICE:
385 | default:
386 | if (otmp && otmp->cursed) {
387 | You("can't. The saddle seems to be cursed.");
388 | otmp->bknown = TRUE;
389 | return;
390 | }
391 | if (!mtmp->mnamelth) {
392 | pline("You've been through the dungeon on %s with no name.",
393 | an(mtmp->data->mname));
394 | if (Hallucination)
395 | pline("It felt good to get out of the rain.");
396 | } else
397 | You("dismount %s.", mon_nam(mtmp));
398 | }
399 | /* While riding these refer to the steed's legs
400 | * so after dismounting they refer to the player's
401 | * legs once again.
402 | */
403 | if (repair_leg_damage) HWounded_legs = EWounded_legs = 0;
404 |
405 | /* Release the steed and saddle */
406 | u.usteed = 0;
407 | u.ugallop = 0L;
408 |
409 | /* Set player and steed's position. Try moving the player first */
410 | if (!DEADMONSTER(mtmp)) {
411 | place_monster(mtmp, u.ux, u.uy);
412 | if (!u.uswallow && !u.ustuck && enexto(&cc, u.ux, u.uy, youmonst.data)) {
413 | struct permonst *mdat = mtmp->data;
414 |
415 | /* The steed may drop into water/lava */
416 | if (!is_flyer(mdat) && !is_floater(mdat) && !is_clinger(mdat)) {
417 | if (is_pool(u.ux, u.uy)) {
418 | if (!Underwater)
419 | pline("%s falls into the %s!", Monnam(mtmp),
420 | surface(u.ux, u.uy));
421 | if (!is_swimmer(mdat) && !amphibious(mdat)) {
422 | killed(mtmp);
423 | adjalign(-1);
424 | }
425 | } else if (is_lava(u.ux, u.uy)) {
426 | pline("%s is pulled into the lava!", Monnam(mtmp));
427 | if (!likes_lava(mdat)) {
428 | killed(mtmp);
429 | adjalign(-1);
430 | }
431 | }
432 | }
433 | /* Steed dismounting consists of two steps: being moved to another
434 | * square, and descending to the floor. We have functions to do
435 | * each of these activities, but they're normally called
436 | * individually and include an attempt to look at or pick up the
437 | * objects on the floor:
438 | * teleds() --> spoteffects() --> pickup()
439 | * float_down() --> pickup()
440 | * We use this kludge to make sure there is only one such attempt.
441 | *
442 | * Clearly this is not the best way to do it. A full fix would
443 | * involve having these functions not call pickup() at all, instead
444 | * calling them first and calling pickup() afterwards. But it
445 | * would take a lot of work to keep this change from having any
446 | * unforseen side effects (for instance, you would no longer be
447 | * able to walk onto a square with a hole, and autopickup before
448 | * falling into the hole).
449 | */
450 | /* Keep steed here, move the player to cc; teleds() clears u.utrap */
451 | in_steed_dismounting = TRUE;
452 | teleds(cc.x, cc.y);
453 | in_steed_dismounting = FALSE;
454 |
455 | /* Put your steed in your trap */
456 | if (save_utrap)
457 | (void) mintrap(mtmp);
458 |
459 | /* Couldn't... try placing the steed */
460 | } else if (enexto(&cc, u.ux, u.uy, mtmp->data)) {
461 | /* Keep player here, move the steed to cc */
462 | rloc_to(mtmp, cc.x, cc.y);
463 | /* Player stays put */
464 | /* Otherwise, kill the steed */
465 | } else {
466 | killed(mtmp);
467 | adjalign(-1);
468 | }
469 | }
470 |
471 | /* Return the player to the floor */
472 | (void) float_down(0L, W_SADDLE);
473 | flags.botl = 1;
474 | if (reason != DISMOUNT_ENGULFED) {
475 | (void)encumber_msg();
476 | vision_full_recalc = 1;
477 | }
478 | return;
479 | }
480 |
481 | void
482 | place_monster(mon, x, y)
483 | struct monst *mon;
484 | int x, y;
485 | {
486 | if (mon == u.usteed ||
487 | /* special case is for convoluted vault guard handling */
488 | (DEADMONSTER(mon) && !(mon->isgd && x == 0 && y == 0))) {
489 | impossible("placing %s onto map?",
490 | (mon == u.usteed) ? "steed" : "defunct monster");
491 | return;
492 | }
493 | mon->mx = x, mon->my = y;
494 | level.monsters[x][y] = mon;
495 | }
496 |
497 | #endif /* STEED */
498 |
499 | /*steed.c*/