ViewVC Help
View File | Revision Log | Show Annotations | Download File
/cvs/deliantra/server/server/skills.C
Revision: 1.35
Committed: Sat May 19 00:31:08 2007 UTC (17 years ago) by root
Content type: text/plain
Branch: MAIN
CVS Tags: rel-2_1
Changes since 1.34: +11 -9 lines
Log Message:
A player must never apply two skills. crossfire always has and will ever
complain about that.

Nevertheless, levitation (and ONLY levitation) gets applied in addition to
other skills. Now that cannot work with the new skill system. Introduce a
special flag for levitation only so it only gets half-applied.

This probably fixes quite a number of older "has two skills applied"
messages.

File Contents

# Content
1 /*
2 * CrossFire, A Multiplayer game
3 *
4 * Copyright (C) 2005, 2006, 2007 Marc Lehmann & Crossfire+ Development Team
5 * Copyright (C) 2003 Mark Wedel & Crossfire Development Team
6 * Copyright (C) 1992 Frank Tore Johansen
7 *
8 * This program is free software; you can redistribute it and/or modify
9 * it under the terms of the GNU General Public License as published by
10 * the Free Software Foundation; either version 2 of the License, or
11 * (at your option) any later version.
12 *
13 * This program is distributed in the hope that it will be useful,
14 * but WITHOUT ANY WARRANTY; without even the implied warranty of
15 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16 * GNU General Public License for more details.
17 *
18 * You should have received a copy of the GNU General Public License
19 * along with this program; if not, write to the Free Software
20 * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
21 *
22 * The authors can be reached via e-mail to <crossfire@schmorp.de>
23 */
24
25 #include <global.h>
26 #include <object.h>
27 #include <sproto.h>
28 #include <living.h>
29 #include <skills.h>
30 #include <spells.h>
31 #include <book.h>
32
33 /* adj_stealchance() - increased values indicate better attempts */
34 static int
35 adj_stealchance (object *op, object *victim, int roll)
36 {
37 object *equip;
38
39 if (!op || !victim || !roll)
40 return -1;
41
42 /* Only prohibit stealing if the player does not have a free
43 * hand available and in fact does have hands.
44 */
45 if (op->type == PLAYER && op->slot[body_arm].used <= 0 && op->slot[body_arm].info)
46 {
47 new_draw_info (NDI_UNIQUE, 0, op, "But you have no free hands to steal with!");
48 return -1;
49 }
50
51 /* ADJUSTMENTS */
52
53 /* Its harder to steal from hostile beings! */
54 if (!QUERY_FLAG (victim, FLAG_UNAGGRESSIVE))
55 roll = roll / 2;
56
57 /* Easier to steal from sleeping beings, or if the thief is
58 * unseen */
59 if (QUERY_FLAG (victim, FLAG_SLEEP))
60 roll = roll * 3;
61 else if (op->invisible)
62 roll = roll * 2;
63
64 /* check stealing 'encumberance'. Having this equipment applied makes
65 * it quite a bit harder to steal.
66 */
67 for (equip = op->inv; equip; equip = equip->below)
68 {
69 if (equip->type == WEAPON && QUERY_FLAG (equip, FLAG_APPLIED))
70 {
71 roll -= equip->weight / 10000;
72 }
73 if (equip->type == BOW && QUERY_FLAG (equip, FLAG_APPLIED))
74 roll -= equip->weight / 5000;
75 if (equip->type == SHIELD && QUERY_FLAG (equip, FLAG_APPLIED))
76 {
77 roll -= equip->weight / 2000;
78 }
79 if (equip->type == ARMOUR && QUERY_FLAG (equip, FLAG_APPLIED))
80 roll -= equip->weight / 5000;
81 if (equip->type == GLOVES && QUERY_FLAG (equip, FLAG_APPLIED))
82 roll -= equip->weight / 100;
83 }
84 if (roll < 0)
85 roll = 0;
86 return roll;
87 }
88
89 /*
90 * When stealing: dependent on the intelligence/wisdom of whom you're
91 * stealing from (op in attempt_steal), offset by your dexterity and
92 * skill at stealing. They may notice your attempt, whether successful
93 * or not.
94 * op is the target (person being pilfered)
95 * who is the person doing the stealing.
96 * skill is the skill object (stealing).
97 */
98 static int
99 attempt_steal (object *op, object *who, object *skill)
100 {
101 object *success = NULL, *tmp = NULL, *next;
102 int roll = 0, chance = 0, stats_value;
103 rv_vector rv;
104
105 stats_value = ((who->stats.Dex + who->stats.Int) * 3) / 2;
106
107 /* if the victim is aware of a thief in the area (FLAG_NO_STEAL set on them)
108 * they will try to prevent stealing if they can. Only unseen theives will
109 * have much chance of success.
110 */
111 if (op->type != PLAYER && QUERY_FLAG (op, FLAG_NO_STEAL))
112 {
113 if (can_detect_enemy (op, who, &rv))
114 {
115 npc_call_help (op);
116 CLEAR_FLAG (op, FLAG_UNAGGRESSIVE);
117 new_draw_info (NDI_UNIQUE, 0, who, "Your attempt is prevented!");
118 return 0;
119 }
120 else /* help npc to detect thief next time by raising its wisdom */
121 op->stats.Wis += (op->stats.Int / 5) + 1;
122 if (op->stats.Wis > MAX_STAT)
123 op->stats.Wis = MAX_STAT;
124 }
125
126 if (op->type == PLAYER && QUERY_FLAG (op, FLAG_WIZ))
127 {
128 new_draw_info (NDI_UNIQUE, 0, who, "You can't steal from the dungeon master!\n");
129 return 0;
130 }
131
132 // only allow stealing between hostile players (TODO: probably should change)
133 if (op->type == PLAYER && who->type == PLAYER && (who->contr->peaceful || op->contr->peaceful))
134 {
135 new_draw_info (NDI_UNIQUE, 0, who, "You can't steal from other players!\n");
136 return 0;
137 }
138
139 /* Ok then, go thru their inventory, stealing */
140 for (tmp = op->inv; tmp; tmp = next)
141 {
142 next = tmp->below;
143
144 /* you can't steal worn items, starting items, wiz stuff,
145 * innate abilities, or items w/o a type. Generally
146 * speaking, the invisibility flag prevents experience or
147 * abilities from being stolen since these types are currently
148 * always invisible objects. I was implicit here so as to prevent
149 * future possible problems. -b.t.
150 * Flesh items generated w/ fix_flesh_item should have FLAG_NO_STEAL
151 * already -b.t.
152 */
153
154 if (QUERY_FLAG (tmp, FLAG_APPLIED)
155 || !tmp->type
156 || tmp->type == SPELL
157 || QUERY_FLAG (tmp, FLAG_STARTEQUIP)
158 || QUERY_FLAG (tmp, FLAG_NO_STEAL)
159 || tmp->invisible)
160 continue;
161
162 /* Okay, try stealing this item. Dependent on dexterity of thief,
163 * skill level, see the adj_stealroll fctn for more detail.
164 */
165
166 roll = die_roll (2, 100, who, PREFER_LOW) / 2; /* weighted 1-100 */
167
168 if ((chance = adj_stealchance (who, op, (stats_value + skill->level * 10 - op->level * 3))) == -1)
169 return 0;
170 else if (roll < chance)
171 {
172 pick_up (who, tmp);
173 /* need to see if the player actually stole this item -
174 * if it is in the players inv, assume it is. This prevents
175 * abuses where the player can not carry the item, so just
176 * keeps stealing it over and over.
177 */
178 if (tmp->destroyed () || tmp->env != op)
179 {
180 /* for players, play_sound: steals item */
181 success = tmp;
182 CLEAR_FLAG (tmp, FLAG_INV_LOCKED);
183
184 /* Don't delete it from target player until we know
185 * the thief has picked it up. can't just look at tmp->count,
186 * as it's possible that it got merged when picked up.
187 */
188 if (op->type == PLAYER)
189 esrv_del_item (op->contr, tmp->count);
190 }
191 break;
192 }
193 } /* for loop looking for an item */
194
195 if (!tmp)
196 {
197 new_draw_info_format (NDI_UNIQUE, 0, who, "%s%s has nothing you can steal!", op->type == PLAYER ? "" : "The ", query_name (op));
198 return 0;
199 }
200
201 /* If you arent high enough level, you might get something BUT
202 * the victim will notice your stealing attempt. Ditto if you
203 * attempt to steal something heavy off them, they're bound to notice
204 */
205
206 if ((roll >= skill->level) || !chance
207 || (tmp && tmp->weight > (250 * (random_roll (0, stats_value + skill->level * 10 - 1, who, PREFER_LOW)))))
208 {
209
210 /* victim figures out where the thief is! */
211 if (who->hide)
212 make_visible (who);
213
214 if (op->type != PLAYER)
215 {
216 /* The unaggressives look after themselves 8) */
217 if (who->type == PLAYER)
218 {
219 npc_call_help (op);
220 new_draw_info_format (NDI_UNIQUE, 0, who, "%s notices your attempted pilfering!", query_name (op));
221 }
222 CLEAR_FLAG (op, FLAG_UNAGGRESSIVE);
223 /* all remaining npc items are guarded now. Set flag NO_STEAL
224 * on the victim.
225 */
226 SET_FLAG (op, FLAG_NO_STEAL);
227 }
228 else
229 { /* stealing from another player */
230 char buf[MAX_BUF];
231
232 /* Notify the other player */
233 if (success && who->stats.Int > random_roll (0, 19, op, PREFER_LOW))
234 {
235 sprintf (buf, "Your %s is missing!", query_name (success));
236 }
237 else
238 {
239 sprintf (buf, "Your pack feels strangely lighter.");
240 }
241 new_draw_info (NDI_UNIQUE, 0, op, buf);
242 if (!success)
243 {
244 if (who->invisible)
245 {
246 sprintf (buf, "you feel itchy fingers getting at your pack.");
247 }
248 else
249 {
250 sprintf (buf, "%s looks very shifty.", query_name (who));
251 }
252 new_draw_info (NDI_UNIQUE, 0, op, buf);
253 }
254 } /* else stealing from another player */
255 /* play_sound("stop! thief!"); kindofthing */
256 } /* if you weren't 100% successful */
257 return success ? 1 : 0;
258 }
259
260 int
261 steal (object *op, int dir, object *skill)
262 {
263 object *tmp, *next;
264 sint16 x, y;
265 maptile *m;
266 int mflags;
267
268 x = op->x + freearr_x[dir];
269 y = op->y + freearr_y[dir];
270
271 if (dir == 0)
272 {
273 /* Can't steal from ourself! */
274 return 0;
275 }
276
277 m = op->map;
278 mflags = get_map_flags (m, &m, x, y, &x, &y);
279 /* Out of map - can't do it. If nothing alive on this space,
280 * don't need to look any further.
281 */
282 if ((mflags & P_OUT_OF_MAP) || !(mflags & P_IS_ALIVE))
283 return 0;
284
285 /* If player can't move onto the space, can't steal from it. */
286 if (OB_TYPE_MOVE_BLOCK (op, GET_MAP_MOVE_BLOCK (m, x, y)))
287 return 0;
288
289 /* Find the topmost object at this spot */
290 for (tmp = GET_MAP_OB (m, x, y); tmp != NULL && tmp->above != NULL; tmp = tmp->above);
291
292 /* For all the stacked objects at this point, attempt a steal */
293 for (; tmp != NULL; tmp = next)
294 {
295 next = tmp->below;
296 /* Minor hack--for multi square beings - make sure we get
297 * the 'head' coz 'tail' objects have no inventory! - b.t.
298 */
299 if (tmp->head)
300 tmp = tmp->head;
301
302 if (tmp->type != PLAYER && !QUERY_FLAG (tmp, FLAG_MONSTER))
303 continue;
304
305 /* do not reveal hidden DMs */
306 if (tmp->type == PLAYER && QUERY_FLAG (tmp, FLAG_WIZ) && tmp->contr->hidden)
307 continue;
308 if (attempt_steal (tmp, op, skill))
309 {
310 if (tmp->type == PLAYER) /* no xp for stealing from another player */
311 return 0;
312
313 /* no xp for stealing from pets (of players) */
314 if (QUERY_FLAG (tmp, FLAG_FRIENDLY) && tmp->attack_movement == PETMOVE)
315 {
316 object *owner = tmp->owner;
317
318 if (owner != NULL && owner->type == PLAYER)
319 return 0;
320 }
321
322 // reduce monster experience by experience we gained, as to
323 // limit the amount of exp that can be gained by stealing from monsters
324 // (jessies gave ~20,000,000 exp otherwise.
325 int exp = calc_skill_exp (op, tmp, skill);
326
327 exp = MIN (tmp->stats.exp, exp);
328 tmp->stats.exp -= exp;
329 return exp;
330 }
331 }
332 return 0;
333 }
334
335 static int
336 attempt_pick_lock (object *door, object *pl, object *skill)
337 {
338 int difficulty = pl->map->difficulty ? pl->map->difficulty : 0;
339 int success = 0, number; /* did we get anything? */
340
341
342 /* Try to pick the lock on this item (doors only for now).
343 * Dependent on dexterity/skill SK_level of the player and
344 * the map level difficulty.
345 */
346 number = (die_roll (2, 40, pl, PREFER_LOW) - 2) / 2;
347 if (number < (pl->stats.Dex + skill->level - difficulty))
348 {
349 remove_door (door);
350 success = 1;
351 }
352 else if (door->inv && (door->inv->type == RUNE || door->inv->type == TRAP))
353 { /* set off any traps? */
354 spring_trap (door->inv, pl);
355 }
356 return success;
357 }
358
359
360 /* Implementation by bt. (thomas@astro.psu.edu)
361 * monster implementation 7-7-95 by bt.
362 */
363
364 int
365 pick_lock (object *pl, int dir, object *skill)
366 {
367 object *tmp;
368 int x = pl->x + freearr_x[dir];
369 int y = pl->y + freearr_y[dir];
370
371 if (!dir)
372 dir = pl->facing;
373
374 /* For all the stacked objects at this point find a door */
375 if (out_of_map (pl->map, x, y))
376 {
377 new_draw_info (NDI_UNIQUE, 0, pl, "There is no lock there.");
378 return 0;
379 }
380
381 for (tmp = GET_MAP_OB (pl->map, x, y); tmp; tmp = tmp->above)
382 if (tmp->type == DOOR || tmp->type == LOCKED_DOOR)
383 break;
384
385 if (!tmp)
386 {
387 new_draw_info (NDI_UNIQUE, 0, pl, "There is no lock there.");
388 return 0;
389 }
390 if (tmp->type == LOCKED_DOOR)
391 {
392 new_draw_info (NDI_UNIQUE, 0, pl, "You can't pick that lock!");
393 return 0;
394 }
395
396 if (!tmp->move_block)
397 {
398 new_draw_info (NDI_UNIQUE, 0, pl, "The door has no lock!");
399 return 0;
400 }
401
402 if (attempt_pick_lock (tmp, pl, skill))
403 {
404 new_draw_info (NDI_UNIQUE, 0, pl, "You pick the lock.");
405 return calc_skill_exp (pl, NULL, skill);
406 }
407 else
408 {
409 new_draw_info (NDI_UNIQUE, 0, pl, "You fail to pick the lock.");
410 return 0;
411 }
412 }
413
414 /* HIDE CODE. The user becomes undetectable (not just 'invisible') for
415 * a short while (success and duration dependant on player SK_level,
416 * dexterity, charisma, and map difficulty).
417 * Players have a good chance of becoming 'unhidden' if they move
418 * and like invisiblity will be come visible if they attack
419 * Implemented by b.t. (thomas@astro.psu.edu)
420 * July 7, 1995 - made hiding possible for monsters. -b.t.
421 */
422 static int
423 attempt_hide (object *op, object *skill)
424 {
425 int number, difficulty = op->map->difficulty;
426 int terrain = hideability (op);
427
428 if (terrain < -10) /* not enough cover here */
429 return 0;
430
431 /* Hiding success and duration dependant on skill level,
432 * op->stats.Dex, map difficulty and terrain.
433 */
434 number = (die_roll (2, 25, op, PREFER_LOW) - 2) / 2;
435
436 if (!stand_near_hostile (op) && (number < (op->stats.Dex + skill->level + terrain - difficulty)))
437 {
438 op->invisible += 100; /* set the level of 'hiddeness' */
439
440 if (op->type == PLAYER)
441 op->contr->tmp_invis = 1;
442
443 op->hide = 1;
444 return 1;
445 }
446
447 return 0;
448 }
449
450 /* patched this to take terrain into consideration */
451 int
452 hide (object *op, object *skill)
453 {
454 /* the preliminaries -- Can we really hide now? */
455 /* this keeps monsters from using invisibilty spells and hiding */
456
457 if (QUERY_FLAG (op, FLAG_MAKE_INVIS))
458 {
459 new_draw_info (NDI_UNIQUE, 0, op, "You don't need to hide while invisible!");
460 return 0;
461 }
462 else if (!op->hide && op->invisible > 0 && op->type == PLAYER)
463 {
464 new_draw_info (NDI_UNIQUE, 0, op, "Your attempt to hide breaks the invisibility spell!");
465 make_visible (op);
466 }
467
468 if (op->invisible > 50 * skill->level)
469 {
470 new_draw_info (NDI_UNIQUE, 0, op, "You are as hidden as you can get.");
471 return 0;
472 }
473
474 if (attempt_hide (op, skill))
475 {
476 new_draw_info (NDI_UNIQUE, 0, op, "You hide in the shadows.");
477 update_object (op, UP_OBJ_FACE);
478 return calc_skill_exp (op, NULL, skill);
479 }
480
481 new_draw_info (NDI_UNIQUE, 0, op, "You fail to conceal yourself.");
482 return 0;
483 }
484
485 /* stop_jump() - End of jump. Clear flags, restore the map, and
486 * freeze the jumper a while to simulate the exhaustion
487 * of jumping.
488 */
489 static void
490 stop_jump (object *pl, int dist, int spaces)
491 {
492 pl->update_stats ();
493 pl->map->insert (pl, pl->x, pl->y, pl);
494 }
495
496 static int
497 attempt_jump (object *pl, int dir, int spaces, object *skill)
498 {
499 object *tmp;
500 int i, exp = 0, dx = freearr_x[dir], dy = freearr_y[dir], mflags;
501 sint16 x, y;
502 maptile *m;
503
504 /* Jump loop. Go through spaces opject wants to jump. Halt the
505 * jump if a wall or creature is in the way. We set FLAG_FLYING
506 * temporarily to allow player to aviod exits/archs that are not
507 * fly_on, fly_off. This will also prevent pickup of objects
508 * while jumping over them.
509 */
510
511 pl->remove ();
512
513 /*
514 * I don't think this is actually needed - all the movement
515 * code is handled in this function, and I don't see anyplace
516 * that cares about the move_type being flying.
517 */
518 pl->move_type |= MOVE_FLY_LOW;
519
520 for (i = 0; i <= spaces; i++)
521 {
522 x = pl->x + dx;
523 y = pl->y + dy;
524 m = pl->map;
525
526 mflags = get_map_flags (m, &m, x, y, &x, &y);
527
528 if (mflags & P_OUT_OF_MAP)
529 {
530 (void) stop_jump (pl, i, spaces);
531 return 0;
532 }
533 if (OB_TYPE_MOVE_BLOCK (pl, GET_MAP_MOVE_BLOCK (m, x, y)))
534 {
535 new_draw_info (NDI_UNIQUE, 0, pl, "Your jump is blocked.");
536 stop_jump (pl, i, spaces);
537 return 0;
538 }
539
540 for (tmp = GET_MAP_OB (m, x, y); tmp; tmp = tmp->above)
541 {
542 /* Jump into creature */
543 if (QUERY_FLAG (tmp, FLAG_MONSTER) || (tmp->type == PLAYER && (!QUERY_FLAG (tmp, FLAG_WIZ) || !tmp->contr->hidden)))
544 {
545 new_draw_info_format (NDI_UNIQUE, 0, pl, "You jump into %s%s.", tmp->type == PLAYER ? "" : "the ", &tmp->name);
546 if (tmp->type != PLAYER ||
547 (pl->type == PLAYER && pl->contr->party == NULL) ||
548 (pl->type == PLAYER && tmp->type == PLAYER && pl->contr->party != tmp->contr->party))
549 exp = skill_attack (tmp, pl, pl->facing, "kicked", skill); /* pl makes an attack */
550 stop_jump (pl, i, spaces);
551 return exp; /* note that calc_skill_exp() is already called by skill_attack() */
552 }
553 /* If the space has fly on set (no matter what the space is),
554 * we should get the effects - after all, the player is
555 * effectively flying.
556 */
557 if (tmp->move_on & MOVE_FLY_LOW)
558 {
559 pl->x = x;
560 pl->y = y;
561 pl->map = m;
562 stop_jump (pl, i, spaces);
563 return calc_skill_exp (pl, NULL, skill);
564 }
565 }
566 pl->x = x;
567 pl->y = y;
568 pl->map = m;
569 }
570 stop_jump (pl, i, spaces);
571 return calc_skill_exp (pl, NULL, skill);
572 }
573
574 /* jump() - this is both a new type of movement for player/monsters and
575 * an attack as well.
576 * Perhaps we should allow more spaces based on level, eg, level 50
577 * jumper can jump several spaces?
578 */
579
580 int
581 jump (object *pl, int dir, object *skill)
582 {
583 int spaces = 0, stats;
584 int str = pl->stats.Str;
585 int dex = pl->stats.Dex;
586
587 dex = dex ? dex : 15;
588 str = str ? str : 10;
589
590 stats = str * str * str * dex * skill->level;
591
592 if (pl->carrying != 0) /* don't want div by zero !! */
593 spaces = (int) (stats / pl->carrying);
594 else
595 spaces = 2; /* pl has no objects - gets the far jump */
596
597 if (spaces > 2)
598 spaces = 2;
599 else if (spaces == 0)
600 {
601 new_draw_info (NDI_UNIQUE, 0, pl, "You are carrying too much weight to jump.");
602 return 0;
603 }
604 return attempt_jump (pl, dir, spaces, skill);
605 }
606
607
608 /* skill_ident() - this code is supposed to allow players to identify
609 * classes of objects with the various "auto-ident" skills. Player must
610 * have unidentified objects of the right type in order for the skill
611 * to work. While multiple classes of objects may be identified,
612 * this code is kind of yucky -- it would be nice to make it a bit
613 * more generalized. Right now, skill indices are embedded in this routine.
614 * Returns amount of experience gained (on successful ident).
615 * - b.t. (thomas@astro.psu.edu)
616 */
617
618 static int
619 do_skill_detect_curse (object *pl, object *skill)
620 {
621 object *tmp;
622 int success = 0;
623
624 for (tmp = pl->inv; tmp; tmp = tmp->below)
625 if (!tmp->invisible
626 && !QUERY_FLAG (tmp, FLAG_IDENTIFIED) && !QUERY_FLAG (tmp, FLAG_KNOWN_CURSED)
627 && (QUERY_FLAG (tmp, FLAG_CURSED) || QUERY_FLAG (tmp, FLAG_DAMNED)) && tmp->item_power < skill->level)
628 {
629 SET_FLAG (tmp, FLAG_KNOWN_CURSED);
630 esrv_update_item (UPD_FLAGS, pl, tmp);
631 success += calc_skill_exp (pl, tmp, skill);
632 }
633
634 /* Check ground, too, but only objects the player could pick up */
635 for (tmp = GET_MAP_OB (pl->map, pl->x, pl->y); tmp; tmp = tmp->above)
636 if (can_pick (pl, tmp) &&
637 !QUERY_FLAG (tmp, FLAG_IDENTIFIED) &&
638 !QUERY_FLAG (tmp, FLAG_KNOWN_CURSED)
639 && (QUERY_FLAG (tmp, FLAG_CURSED) || QUERY_FLAG (tmp, FLAG_DAMNED)) && tmp->item_power < skill->level)
640 {
641 SET_FLAG (tmp, FLAG_KNOWN_CURSED);
642 esrv_update_item (UPD_FLAGS, pl, tmp);
643 success += calc_skill_exp (pl, tmp, skill);
644 }
645
646 return success;
647 }
648
649 static int
650 do_skill_detect_magic (object *pl, object *skill)
651 {
652 object *tmp;
653 int success = 0;
654
655 for (tmp = pl->inv; tmp; tmp = tmp->below)
656 if (!tmp->invisible
657 && !QUERY_FLAG (tmp, FLAG_IDENTIFIED) && !QUERY_FLAG (tmp, FLAG_KNOWN_MAGICAL)
658 && (is_magical (tmp)) && tmp->item_power < skill->level)
659 {
660 SET_FLAG (tmp, FLAG_KNOWN_MAGICAL);
661 esrv_update_item (UPD_FLAGS, pl, tmp);
662 success += calc_skill_exp (pl, tmp, skill);
663 }
664
665 /* Check ground, too, but like above, only if the object can be picked up */
666 for (tmp = GET_MAP_OB (pl->map, pl->x, pl->y); tmp; tmp = tmp->above)
667 if (can_pick (pl, tmp) &&
668 !QUERY_FLAG (tmp, FLAG_IDENTIFIED) && !QUERY_FLAG (tmp, FLAG_KNOWN_MAGICAL) && (is_magical (tmp)) && tmp->item_power < skill->level)
669 {
670 SET_FLAG (tmp, FLAG_KNOWN_MAGICAL);
671 esrv_update_item (UPD_FLAGS, pl, tmp);
672 success += calc_skill_exp (pl, tmp, skill);
673 }
674
675 return success;
676 }
677
678 /* Helper function for do_skill_ident, so that we can loop
679 * over inventory AND objects on the ground conveniently.
680 */
681 int
682 do_skill_ident2 (object *tmp, object *pl, int obj_class, object *skill)
683 {
684 int success = 0, chance;
685 int skill_value = skill->level * pl->stats.Int ? pl->stats.Int : 10;
686
687 if (!QUERY_FLAG (tmp, FLAG_IDENTIFIED) && !QUERY_FLAG (tmp, FLAG_NO_SKILL_IDENT)
688 && need_identify (tmp) && !tmp->invisible && tmp->type == obj_class)
689 {
690 chance = die_roll (3, 10, pl, PREFER_LOW) - 3 + rndm (0, (tmp->magic ? tmp->magic * 5 : 1) - 1);
691
692 if (skill_value >= chance)
693 {
694 identify (tmp);
695
696 if (pl->type == PLAYER)
697 {
698 new_draw_info_format (NDI_UNIQUE, 0, pl, "You identify %s.", long_desc (tmp, pl));
699
700 if (tmp->msg)
701 {
702 new_draw_info (NDI_UNIQUE, 0, pl, "The item has a story:");
703 new_draw_info (NDI_UNIQUE, 0, pl, tmp->msg);
704 }
705
706 /* identify will take care of updating the item if it is in the players inventory. IF on map, do it here */
707 if (tmp->map)
708 esrv_send_item (pl, tmp);
709 }
710 success += calc_skill_exp (pl, tmp, skill);
711 }
712 else
713 SET_FLAG (tmp, FLAG_NO_SKILL_IDENT);
714 }
715
716 return success;
717 }
718
719 /* do_skill_ident() - workhorse for skill_ident() -b.t.
720 */
721 static int
722 do_skill_ident (object *pl, int obj_class, object *skill)
723 {
724 object *tmp;
725 int success = 0;
726
727 for (tmp = pl->inv; tmp; tmp = tmp->below)
728 success += do_skill_ident2 (tmp, pl, obj_class, skill);
729 /* check the ground */
730
731 for (tmp = GET_MAP_OB (pl->map, pl->x, pl->y); tmp; tmp = tmp->above)
732 success += do_skill_ident2 (tmp, pl, obj_class, skill);
733
734 return success;
735 }
736
737 int
738 skill_ident (object *pl, object *skill)
739 {
740 int success = 0;
741
742 if (pl->type != PLAYER)
743 return 0; /* only players will skill-identify */
744
745 new_draw_info (NDI_UNIQUE, 0, pl, "You look at the objects nearby...");
746
747 switch (skill->subtype)
748 {
749 case SK_SMITHERY:
750 success += do_skill_ident (pl, WEAPON, skill) + do_skill_ident (pl, ARMOUR, skill)
751 + do_skill_ident (pl, BRACERS, skill) + do_skill_ident (pl, CLOAK, skill)
752 + do_skill_ident (pl, BOOTS, skill) + do_skill_ident (pl, SHIELD, skill)
753 + do_skill_ident (pl, GIRDLE, skill) + do_skill_ident (pl, HELMET, skill) + do_skill_ident (pl, GLOVES, skill);
754 break;
755
756 case SK_BOWYER:
757 success += do_skill_ident (pl, BOW, skill) + do_skill_ident (pl, ARROW, skill);
758 break;
759
760 case SK_ALCHEMY:
761 success += do_skill_ident (pl, POTION, skill) + do_skill_ident (pl, POISON, skill)
762 + do_skill_ident (pl, CONTAINER, skill) + do_skill_ident (pl, DRINK, skill) + do_skill_ident (pl, INORGANIC, skill);
763 break;
764
765 case SK_WOODSMAN:
766 success += do_skill_ident (pl, FOOD, skill) + do_skill_ident (pl, DRINK, skill) + do_skill_ident (pl, FLESH, skill);
767 break;
768
769 case SK_JEWELER:
770 success += do_skill_ident (pl, GEM, skill) + do_skill_ident (pl, RING, skill) + do_skill_ident (pl, AMULET, skill);
771 break;
772
773 case SK_LITERACY:
774 success += do_skill_ident (pl, SPELLBOOK, skill) + do_skill_ident (pl, SCROLL, skill) + do_skill_ident (pl, BOOK, skill);
775 break;
776
777 case SK_THAUMATURGY:
778 success += do_skill_ident (pl, WAND, skill) + do_skill_ident (pl, ROD, skill) + do_skill_ident (pl, HORN, skill);
779 break;
780
781 case SK_DET_CURSE:
782 success = do_skill_detect_curse (pl, skill);
783 if (success)
784 new_draw_info (NDI_UNIQUE, 0, pl, "...and discover cursed items!");
785 break;
786
787 case SK_DET_MAGIC:
788 success = do_skill_detect_magic (pl, skill);
789 if (success)
790 new_draw_info (NDI_UNIQUE, 0, pl, "...and discover items imbued with mystic forces!");
791 break;
792
793 default:
794 LOG (llevError, "Error: bad call to skill_ident()\n");
795 return 0;
796 break;
797 }
798 if (!success)
799 {
800 new_draw_info (NDI_UNIQUE, 0, pl, "...and learn nothing more.");
801 }
802 return success;
803 }
804
805 /* players using this skill can 'charm' a monster --
806 * into working for them. It can only be used on
807 * non-special (see below) 'neutral' creatures.
808 * -b.t. (thomas@astro.psu.edu)
809 */
810 int
811 use_oratory (object *pl, int dir, object *skill)
812 {
813 if (pl->type != PLAYER)
814 return 0; /* only players use this skill */
815
816 sint16 x = pl->x + freearr_x[dir],
817 y = pl->y + freearr_y[dir];
818 maptile *m = pl->map;
819
820 int mflags = get_map_flags (m, &m, x, y, &x, &y);
821 if (mflags & P_OUT_OF_MAP)
822 return 0;
823
824 /* Save some processing - we have the flag already anyways
825 */
826 if (!(mflags & P_IS_ALIVE))
827 {
828 new_draw_info (NDI_UNIQUE, 0, pl, "There is nothing to orate to.");
829 return 0;
830 }
831
832 object *tmp;
833 for (tmp = GET_MAP_OB (m, x, y); tmp; tmp = tmp->above)
834 {
835 /* can't persuade players - return because there is nothing else
836 * on that space to charm. Same for multi space monsters and
837 * special monsters - we don't allow them to be charmed, and there
838 * is no reason to do further processing since they should be the
839 * only monster on the space.
840 */
841 if (tmp->type == PLAYER
842 || tmp->more || tmp->head_ () != tmp
843 || tmp->msg)
844 return 0;
845
846 if (QUERY_FLAG (tmp, FLAG_MONSTER))
847 break;
848 }
849
850 if (!tmp)
851 {
852 new_draw_info (NDI_UNIQUE, 0, pl, "There is nothing to orate to.");
853 return 0;
854 }
855
856 new_draw_info_format (NDI_UNIQUE, 0, pl, "You orate to the %s.", query_name (tmp));
857
858 /* the following conditions limit who may be 'charmed' */
859
860 /* it's hostile! */
861 if (!QUERY_FLAG (tmp, FLAG_UNAGGRESSIVE) && !QUERY_FLAG (tmp, FLAG_FRIENDLY))
862 {
863 new_draw_info_format (NDI_UNIQUE, 0, pl, "Too bad the %s isn't listening!\n", query_name (tmp));
864 return 0;
865 }
866
867 /* it's already allied! */
868 if (QUERY_FLAG (tmp, FLAG_FRIENDLY) && tmp->attack_movement == PETMOVE)
869 {
870 if (tmp->owner == pl)
871 {
872 new_draw_info (NDI_UNIQUE, 0, pl, "Your follower loves your speech.\n");
873 return 0;
874 }
875 else if (skill->level > tmp->level)
876 {
877 /* you steal the follower. Perhaps we should really look at the
878 * level of the owner above?
879 */
880 tmp->set_owner (pl);
881 tmp->skill = skill->skill;
882
883 new_draw_info_format (NDI_UNIQUE, 0, pl, "You convince the %s to follow you instead!\n", query_name (tmp));
884 /* Abuse fix - don't give exp since this can otherwise
885 * be used by a couple players to gets lots of exp.
886 */
887 return 0;
888 }
889 else
890 {
891 /* In this case, you can't steal it from the other player */
892 return 0;
893 }
894 } /* Creature was already a pet of someone */
895
896 int level = skill->level + (pl->stats.Cha - tmp->stats.Int) / 2;
897
898 /* Ok, got a 'sucker' lets try to make them a follower */
899 if (level > 0 && tmp->level < (random_roll (0, level - 1, pl, PREFER_HIGH) - 1))
900 {
901 new_draw_info_format (NDI_UNIQUE, 0, pl, "You convince the %s to become your follower.\n", query_name (tmp));
902
903 tmp->set_owner (pl);
904 tmp->skill = skill->skill;
905 tmp->stats.exp = 0;
906 add_friendly_object (tmp);
907 tmp->attack_movement = PETMOVE;
908 return calc_skill_exp (pl, tmp, skill);
909 }
910 /* Charm failed. Creature may be angry now */
911 else if ((skill->level + ((pl->stats.Cha - 10) / 2)) < random_roll (1, 2 * tmp->level, pl, PREFER_LOW))
912 {
913 new_draw_info_format (NDI_UNIQUE, 0, pl, "Your speech angers the %s!\n", query_name (tmp));
914 if (QUERY_FLAG (tmp, FLAG_FRIENDLY))
915 {
916 CLEAR_FLAG (tmp, FLAG_FRIENDLY);
917 remove_friendly_object (tmp);
918 tmp->attack_movement = 0; /* needed? */
919 }
920
921 CLEAR_FLAG (tmp, FLAG_UNAGGRESSIVE);
922 }
923
924 return 0; /* Fall through - if we get here, we didn't charm anything */
925 }
926
927 /* Singing() -this skill allows the player to pacify nearby creatures.
928 * There are few limitations on who/what kind of
929 * non-player creatures that may be pacified. Right now, a player
930 * may pacify creatures which have Int == 0. In this routine, once
931 * successfully pacified the creature gets Int=1. Thus, a player
932 * may only pacify a creature once.
933 * BTW, I appologize for the naming of the skill, I couldnt think
934 * of anything better! -b.t.
935 */
936 int
937 singing (object *pl, int dir, object *skill)
938 {
939 int i, exp = 0;
940 object *tmp;
941 maptile *m;
942 sint16 x, y;
943
944 if (pl->type != PLAYER)
945 return 0; /* only players use this skill */
946
947 new_draw_info_format (NDI_UNIQUE, 0, pl, "You sing.");
948 for (i = 0; i < MIN (skill->level, SIZEOFFREE); i++)
949 {
950 x = pl->x + freearr_x[i];
951 y = pl->y + freearr_y[i];
952 m = pl->map;
953
954 int mflags = get_map_flags (m, &m, x, y, &x, &y);
955 if (mflags & P_OUT_OF_MAP)
956 continue;
957 if (!(mflags & P_IS_ALIVE))
958 continue;
959
960 for (tmp = GET_MAP_OB (m, x, y); tmp; tmp = tmp->above)
961 {
962 if (QUERY_FLAG (tmp, FLAG_MONSTER))
963 break;
964 /* can't affect players */
965 if (tmp->type == PLAYER)
966 break;
967 }
968
969 /* Whole bunch of checks to see if this is a type of monster that would
970 * listen to singing.
971 */
972 if (tmp && QUERY_FLAG (tmp, FLAG_MONSTER) && !QUERY_FLAG (tmp, FLAG_NO_STEAL) && /* Been charmed or abused before */
973 !QUERY_FLAG (tmp, FLAG_SPLITTING) && /* no ears */
974 !QUERY_FLAG (tmp, FLAG_HITBACK) && /* was here before */
975 (tmp->level <= skill->level) && (!tmp->head) && !QUERY_FLAG (tmp, FLAG_UNDEAD) && !QUERY_FLAG (tmp, FLAG_UNAGGRESSIVE) && /* already calm */
976 !QUERY_FLAG (tmp, FLAG_FRIENDLY))
977 { /* already calm */
978
979 /* stealing isn't really related (although, maybe it should
980 * be). This is mainly to prevent singing to the same monster
981 * over and over again and getting exp for it.
982 */
983 int level = skill->level + (pl->stats.Cha - 5 - tmp->stats.Int) / 2;
984
985 if (level && tmp->level < random_roll (0, level - 1, pl, PREFER_HIGH))
986 {
987 SET_FLAG (tmp, FLAG_UNAGGRESSIVE);
988 new_draw_info_format (NDI_UNIQUE, 0, pl, "You calm down the %s\n", query_name (tmp));
989 /* Give exp only if they are not aware */
990
991 if (!QUERY_FLAG (tmp, FLAG_NO_STEAL))
992 exp += calc_skill_exp (pl, tmp, skill);
993
994 SET_FLAG (tmp, FLAG_NO_STEAL);
995 }
996 else
997 {
998 new_draw_info_format (NDI_UNIQUE, 0, pl, "Too bad the %s isn't listening!\n", query_name (tmp));
999 SET_FLAG (tmp, FLAG_NO_STEAL);
1000 }
1001 }
1002 }
1003 return exp;
1004 }
1005
1006 /* The find_traps skill (aka, search). Checks for traps
1007 * on the spaces or in certain objects
1008 */
1009
1010 int
1011 find_traps (object *pl, object *skill)
1012 {
1013 object *tmp, *tmp2;
1014 int i, expsum = 0, mflags;
1015 sint16 x, y;
1016 maptile *m;
1017
1018 /* First we search all around us for runes and traps, which are
1019 * all type RUNE
1020 */
1021
1022 for (i = 0; i < 9; i++)
1023 {
1024 x = pl->x + freearr_x[i];
1025 y = pl->y + freearr_y[i];
1026 m = pl->map;
1027
1028 mflags = get_map_flags (m, &m, x, y, &x, &y);
1029 if (mflags & P_OUT_OF_MAP)
1030 continue;
1031
1032 /* Check everything in the square for trapness */
1033 for (tmp = GET_MAP_OB (m, x, y); tmp != NULL; tmp = tmp->above)
1034 {
1035
1036 /* And now we'd better do an inventory traversal of each
1037 * of these objects' inventory
1038 * We can narrow this down a bit - no reason to search through
1039 * the players inventory or monsters for that matter.
1040 */
1041 if (tmp->type != PLAYER && !QUERY_FLAG (tmp, FLAG_MONSTER))
1042 {
1043 for (tmp2 = tmp->inv; tmp2 != NULL; tmp2 = tmp2->below)
1044 if (tmp2->type == RUNE || tmp2->type == TRAP)
1045 if (trap_see (pl, tmp2))
1046 {
1047 trap_show (tmp2, tmp);
1048 if (tmp2->stats.Cha > 1)
1049 {
1050 if (!tmp2->owner || tmp2->owner->type != PLAYER)
1051 expsum += calc_skill_exp (pl, tmp2, skill);
1052
1053 tmp2->stats.Cha = 1; /* unhide the trap */
1054 }
1055 }
1056 }
1057 if ((tmp->type == RUNE || tmp->type == TRAP) && trap_see (pl, tmp))
1058 {
1059 trap_show (tmp, tmp);
1060 if (tmp->stats.Cha > 1)
1061 {
1062 if (!tmp->owner || tmp->owner->type != PLAYER)
1063 expsum += calc_skill_exp (pl, tmp, skill);
1064 tmp->stats.Cha = 1; /* unhide the trap */
1065 }
1066 }
1067 }
1068 }
1069 new_draw_info (NDI_BLACK, 0, pl, "You search the area.");
1070 return expsum;
1071 }
1072
1073 /* remove_trap() - This skill will disarm any previously discovered trap
1074 * the algorithm is based (almost totally) on the old command_disarm() - b.t.
1075 */
1076
1077 int
1078 remove_trap (object *op, int dir, object *skill)
1079 {
1080 object *tmp, *tmp2;
1081 int i, success = 0, mflags;
1082 maptile *m;
1083 sint16 x, y;
1084
1085 for (i = 0; i < 9; i++)
1086 {
1087 x = op->x + freearr_x[i];
1088 y = op->y + freearr_y[i];
1089 m = op->map;
1090
1091 mflags = get_map_flags (m, &m, x, y, &x, &y);
1092 if (mflags & P_OUT_OF_MAP)
1093 continue;
1094
1095 /* Check everything in the square for trapness */
1096 for (tmp = GET_MAP_OB (m, x, y); tmp != NULL; tmp = tmp->above)
1097 {
1098 /* And now we'd better do an inventory traversal of each
1099 * of these objects inventory. Like above, only
1100 * do this for interesting objects.
1101 */
1102
1103 if (tmp->type != PLAYER && !QUERY_FLAG (tmp, FLAG_MONSTER))
1104 {
1105 for (tmp2 = tmp->inv; tmp2 != NULL; tmp2 = tmp2->below)
1106 if ((tmp2->type == RUNE || tmp2->type == TRAP) && tmp2->stats.Cha <= 1)
1107 {
1108 trap_show (tmp2, tmp);
1109 if (trap_disarm (op, tmp2, 1, skill) && (!tmp2->owner || tmp2->owner->type != PLAYER))
1110 {
1111 tmp->stats.exp = tmp->stats.Cha * tmp->level;
1112 success += calc_skill_exp (op, tmp2, skill);
1113 }
1114 }
1115 }
1116 if ((tmp->type == RUNE || tmp->type == TRAP) && tmp->stats.Cha <= 1)
1117 {
1118 trap_show (tmp, tmp);
1119 if (trap_disarm (op, tmp, 1, skill) && (!tmp->owner || tmp->owner->type != PLAYER))
1120 {
1121 tmp->stats.exp = tmp->stats.Cha * tmp->level;
1122 success += calc_skill_exp (op, tmp, skill);
1123 }
1124 }
1125 }
1126 }
1127 return success;
1128 }
1129
1130
1131 /* pray() - when this skill is called from do_skill(), it allows
1132 * the player to regain lost grace points at a faster rate. -b.t.
1133 * This always returns 0 - return value is used by calling function
1134 * such that if it returns true, player gets exp in that skill. This
1135 * the effect here can be done on demand, we probably don't want to
1136 * give infinite exp by returning true in any cases.
1137 */
1138
1139 int
1140 pray (object *pl, object *skill)
1141 {
1142 char buf[MAX_BUF];
1143 object *tmp;
1144
1145 if (pl->type != PLAYER)
1146 return 0;
1147
1148 strcpy (buf, "You pray.");
1149
1150 /* Check all objects - we could stop at floor objects,
1151 * but if someone buries an altar, I don't see a problem with
1152 * going through all the objects, and it shouldn't be much slower
1153 * than extra checks on object attributes.
1154 */
1155 for (tmp = pl->below; tmp != NULL; tmp = tmp->below)
1156 {
1157 /* Only if the altar actually belongs to someone do you get special benefits */
1158 if (tmp && tmp->type == HOLY_ALTAR && tmp->other_arch)
1159 {
1160 sprintf (buf, "You pray over the %s.", &tmp->name);
1161 pray_at_altar (pl, tmp, skill);
1162 break; /* Only pray at one altar */
1163 }
1164 }
1165
1166 new_draw_info (NDI_BLACK, 0, pl, buf);
1167
1168 if (pl->stats.grace < pl->stats.maxgrace)
1169 {
1170 pl->stats.grace++;
1171 pl->last_grace = -1;
1172 }
1173 return 0;
1174 }
1175
1176 /* This skill allows the player to regain a few sp or hp for a
1177 * brief period of concentration. No armour or weapons may be
1178 * wielded/applied for this to work. The amount of time needed
1179 * to concentrate and the # of points regained is dependant on
1180 * the level of the user. - b.t. thomas@astro.psu.edu
1181 */
1182
1183 void
1184 meditate (object *pl, object *skill)
1185 {
1186 object *tmp;
1187
1188 if (pl->type != PLAYER)
1189 return; /* players only */
1190
1191 /* check if pl has removed encumbering armour and weapons */
1192 if (QUERY_FLAG (pl, FLAG_READY_WEAPON) && (skill->level < 6))
1193 {
1194 new_draw_info (NDI_UNIQUE, 0, pl, "You can't concentrate while wielding a weapon!\n");
1195 return;
1196 }
1197 else
1198 {
1199 for (tmp = pl->inv; tmp; tmp = tmp->below)
1200 if (((tmp->type == ARMOUR && skill->level < 12)
1201 || (tmp->type == HELMET && skill->level < 10)
1202 || (tmp->type == SHIELD && skill->level < 6)
1203 || (tmp->type == BOOTS && skill->level < 4)
1204 || (tmp->type == GLOVES && skill->level < 2))
1205 && QUERY_FLAG (tmp, FLAG_APPLIED))
1206 {
1207 new_draw_info (NDI_UNIQUE, 0, pl, "You can't concentrate while wearing so much armour!\n");
1208 return;
1209 }
1210 }
1211
1212 /* ok let's meditate! Spell points are regained first, then once
1213 * they are maxed we get back hp. Actual incrementing of values
1214 * is handled by the do_some_living() (in player.c). This way magical
1215 * bonuses for healing/sp regeneration are included properly
1216 * No matter what, we will eat up some playing time trying to
1217 * meditate. (see 'factor' variable for what sets the amount of time)
1218 */
1219
1220 new_draw_info (NDI_BLACK, 0, pl, "You meditate.");
1221
1222 if (pl->stats.sp < pl->stats.maxsp)
1223 {
1224 pl->stats.sp++;
1225 pl->last_sp = -1;
1226 }
1227 else if (pl->stats.hp < pl->stats.maxhp)
1228 {
1229 pl->stats.hp++;
1230 pl->last_heal = -1;
1231 }
1232 }
1233
1234 /* write_note() - this routine allows players to inscribe messages in
1235 * ordinary 'books' (anything that is type BOOK). b.t.
1236 */
1237 static int
1238 write_note (object *pl, object *item, const char *msg, object *skill)
1239 {
1240 char buf[1024];
1241 object *newBook = NULL;
1242
1243 /* a pair of sanity checks */
1244 if (!item || item->type != BOOK)
1245 return 0;
1246
1247 if (!msg)
1248 {
1249 new_draw_info (NDI_UNIQUE, 0, pl, "No message to write!");
1250 new_draw_info_format (NDI_UNIQUE, 0, pl, "Usage: use_skill %s <message>", &skill->skill);
1251 return 0;
1252 }
1253
1254 if (strcasestr_local (msg, "endmsg"))
1255 {
1256 new_draw_info (NDI_UNIQUE, 0, pl, "Trying to cheat now are we?");
1257 return 0;
1258 }
1259
1260 if (INVOKE_OBJECT (INSCRIBE_NOTE, item, ARG_PLAYER (pl->contr), ARG_STRING (msg), ARG_OBJECT (skill)))
1261 return strlen (msg);
1262
1263 buf[0] = 0;
1264 if (!book_overflow (item->msg, msg, sizeof (buf)))
1265 { /* add msg string to book */
1266 if (item->msg)
1267 strcpy (buf, item->msg);
1268
1269 strcat (buf, msg);
1270 strcat (buf, "\n"); /* new msg needs a LF */
1271 if (item->nrof > 1)
1272 {
1273 newBook = item->clone ();
1274 decrease_ob (item);
1275 esrv_send_item (pl, item);
1276 newBook->nrof = 1;
1277 newBook->msg = buf;
1278 newBook = insert_ob_in_ob (newBook, pl);
1279 esrv_send_item (pl, newBook);
1280 }
1281 else
1282 {
1283 item->msg = buf;
1284 /* This shouldn't be necessary - the object hasn't changed in any
1285 * visible way
1286 */
1287 /* esrv_send_item(pl, item); */
1288 }
1289
1290 new_draw_info_format (NDI_UNIQUE, 0, pl, "You write in the %s.", query_short_name (item));
1291 return strlen (msg);
1292 }
1293 else
1294 new_draw_info_format (NDI_UNIQUE, 0, pl, "Your message won't fit in the %s!", query_short_name (item));
1295
1296 return 0;
1297 }
1298
1299 /* write_scroll() - this routine allows players to inscribe spell scrolls
1300 * of spells which they know. Backfire effects are possible with the
1301 * severity of the backlash correlated with the difficulty of the scroll
1302 * that is attempted. -b.t. thomas@astro.psu.edu
1303 */
1304
1305 static int
1306 write_scroll (object *pl, object *scroll, object *skill)
1307 {
1308 int success = 0, confused = 0;
1309 object *newscroll, *chosen_spell, *tmp;
1310
1311 /* this is a sanity check */
1312 if (scroll->type != SCROLL)
1313 {
1314 new_draw_info (NDI_UNIQUE, 0, pl, "A spell can only be inscribed into a scroll!");
1315 return 0;
1316 }
1317
1318 /* Check if we are ready to attempt inscription */
1319 chosen_spell = pl->contr->ranged_ob;
1320 if (!chosen_spell || chosen_spell->type != SPELL)
1321 {
1322 new_draw_info (NDI_UNIQUE, 0, pl, "You need a spell readied in order to inscribe!");
1323 return 0;
1324 }
1325
1326 if (SP_level_spellpoint_cost (pl, chosen_spell, SPELL_GRACE) > pl->stats.grace)
1327 {
1328 new_draw_info_format (NDI_UNIQUE, 0, pl, "You don't have enough grace to write a scroll of %s.", &chosen_spell->name);
1329 return 0;
1330 }
1331
1332 if (SP_level_spellpoint_cost (pl, chosen_spell, SPELL_MANA) > pl->stats.sp)
1333 {
1334 new_draw_info_format (NDI_UNIQUE, 0, pl, "You don't have enough mana to write a scroll of %s.", &chosen_spell->name);
1335 return 0;
1336 }
1337
1338 /* if there is a spell already on the scroll then player could easily
1339 * accidently read it while trying to write the new one. give player
1340 * a 50% chance to overwrite spell at their own level
1341 */
1342 if ((scroll->stats.sp || scroll->inv) && random_roll (0, scroll->level * 2, pl, PREFER_LOW) > skill->level)
1343 {
1344 new_draw_info_format (NDI_UNIQUE, 0, pl, "Oops! You accidently read it while trying to write on it.");
1345 manual_apply (pl, scroll, 0);
1346 return 0;
1347 }
1348
1349 /* ok, we are ready to try inscription */
1350 if (QUERY_FLAG (pl, FLAG_CONFUSED))
1351 confused = 1;
1352
1353 /* Lost mana/grace no matter what */
1354 pl->stats.grace -= SP_level_spellpoint_cost (pl, chosen_spell, SPELL_GRACE);
1355 pl->stats.sp -= SP_level_spellpoint_cost (pl, chosen_spell, SPELL_MANA);
1356
1357 if (random_roll (0, chosen_spell->level * 4 - 1, pl, PREFER_LOW) < skill->level)
1358 {
1359 if (scroll->nrof > 1)
1360 {
1361 newscroll = scroll->clone ();
1362 decrease_ob (scroll);
1363 newscroll->nrof = 1;
1364 }
1365 else
1366 newscroll = scroll;
1367
1368 if (!confused)
1369 {
1370 newscroll->level = MAX (skill->level, chosen_spell->level);
1371 new_draw_info (NDI_UNIQUE, 0, pl, "You succeed in writing a new scroll.");
1372 }
1373 else
1374 {
1375 chosen_spell = find_random_spell_in_ob (pl, NULL);
1376 if (!chosen_spell)
1377 return 0;
1378
1379 newscroll->level = MAX (skill->level, chosen_spell->level);
1380 new_draw_info (NDI_UNIQUE, 0, pl, "In your confused state, you write down some odd spell.");
1381 }
1382
1383 if (newscroll->inv)
1384 newscroll->inv->destroy ();
1385
1386 tmp = chosen_spell->clone ();
1387 insert_ob_in_ob (tmp, newscroll);
1388
1389 /* Same code as from treasure.c - so they can better merge.
1390 * if players want to sell them, so be it.
1391 */
1392 newscroll->value = newscroll->arch->clone.value * newscroll->inv->value * (newscroll->level + 50) / (newscroll->inv->level + 50);
1393 newscroll->stats.exp = newscroll->value / 5;
1394
1395 /* wait until finished manipulating the scroll before inserting it */
1396 if (newscroll == scroll)
1397 {
1398 /* Remove to correctly merge with other items which may exist in inventory */
1399 newscroll->remove ();
1400 esrv_del_item (pl->contr, newscroll->count);
1401 }
1402
1403 newscroll = insert_ob_in_ob (newscroll, pl);
1404 esrv_send_item (pl, newscroll);
1405 success = calc_skill_exp (pl, newscroll, skill);
1406 if (!confused)
1407 success *= 2;
1408 success = success * skill->level;
1409 return success;
1410
1411 }
1412 else
1413 { /* Inscription has failed */
1414
1415 if (chosen_spell->level > skill->level || confused)
1416 { /*backfire! */
1417 new_draw_info (NDI_UNIQUE, 0, pl, "Ouch! Your attempt to write a new scroll strains your mind!");
1418 if (random_roll (0, 1, pl, PREFER_LOW) == 1)
1419 pl->drain_specific_stat (4);
1420 else
1421 {
1422 confuse_player (pl, pl, 99);
1423 return (-30 * chosen_spell->level);
1424 }
1425 }
1426 else if (random_roll (0, pl->stats.Int - 1, pl, PREFER_HIGH) < 15)
1427 {
1428 new_draw_info (NDI_UNIQUE, 0, pl, "Your attempt to write a new scroll rattles your mind!");
1429 confuse_player (pl, pl, 99);
1430 }
1431 else
1432 new_draw_info (NDI_UNIQUE, 0, pl, "You fail to write a new scroll.");
1433 }
1434
1435 return 0;
1436 }
1437
1438 /* write_on_item() - wrapper for write_note and write_scroll */
1439 int
1440 write_on_item (object *pl, const char *params, object *skill)
1441 {
1442 object *item;
1443 const char *string = params;
1444 int msgtype;
1445 archetype *skat;
1446
1447 if (pl->type != PLAYER)
1448 return 0;
1449
1450 if (!params)
1451 {
1452 params = "";
1453 string = params;
1454 }
1455
1456 skat = get_archetype_by_type_subtype (SKILL, SK_LITERACY);
1457
1458 /* Need to be able to read before we can write! */
1459 if (!find_skill_by_name (pl, skat->clone.skill))
1460 {
1461 new_draw_info (NDI_UNIQUE, 0, pl, "You must learn to read before you can write!");
1462 return 0;
1463 }
1464
1465 /* if there is a message then it goes in a book and no message means
1466 * write active spell into the scroll
1467 */
1468 msgtype = (string[0] != '\0') ? BOOK : SCROLL;
1469
1470 /* find an item of correct type to write on */
1471 if (!(item = find_marked_object (pl)))
1472 {
1473 new_draw_info (NDI_UNIQUE, 0, pl, "You don't have any marked item to write on.");
1474 return 0;
1475 }
1476
1477 if (QUERY_FLAG (item, FLAG_UNPAID))
1478 {
1479 new_draw_info (NDI_UNIQUE, 0, pl, "You had better pay for that before you write on it.");
1480 return 0;
1481 }
1482 if (msgtype != item->type)
1483 {
1484 new_draw_info_format (NDI_UNIQUE, 0, pl, "You have no %s to write on", msgtype == BOOK ? "book" : "scroll");
1485 return 0;
1486 }
1487
1488 if (msgtype == SCROLL)
1489 return write_scroll (pl, item, skill);
1490 else if (msgtype == BOOK)
1491 return write_note (pl, item, string, skill);
1492
1493 return 0;
1494 }
1495
1496 /* find_throw_ob() - if we request an object, then
1497 * we search for it in the inventory of the owner (you've
1498 * got to be carrying something in order to throw it!).
1499 * If we didnt request an object, then the top object in inventory
1500 * (that is "throwable", ie no throwing your skills away!)
1501 * is the object of choice. Also check to see if object is
1502 * 'throwable' (ie not applied cursed obj, worn, etc).
1503 */
1504 static object *
1505 find_throw_ob (object *op, const char *request)
1506 {
1507 object *tmp;
1508
1509 if (!op)
1510 { /* safety */
1511 LOG (llevError, "find_throw_ob(): confused! have a NULL thrower!\n");
1512 return (object *) NULL;
1513 }
1514
1515 /* prefer marked item */
1516 tmp = find_marked_object (op);
1517 if (tmp != NULL)
1518 {
1519 /* can't toss invisible or inv-locked items */
1520 if (tmp->invisible || QUERY_FLAG (tmp, FLAG_INV_LOCKED))
1521 {
1522 tmp = NULL;
1523 }
1524 }
1525
1526 /* look through the inventory */
1527 if (tmp == NULL)
1528 {
1529 for (tmp = op->inv; tmp != NULL; tmp = tmp->below)
1530 {
1531 /* can't toss invisible or inv-locked items */
1532 if (tmp->invisible || QUERY_FLAG (tmp, FLAG_INV_LOCKED))
1533 continue;
1534 if (!request || !strcmp (query_name (tmp), request) || !strcmp (tmp->name, request))
1535 break;
1536 }
1537 }
1538
1539 /* this should prevent us from throwing away
1540 * cursed items, worn armour, etc. Only weapons
1541 * can be thrown from 'hand'.
1542 */
1543 if (!tmp)
1544 return NULL;
1545
1546 if (QUERY_FLAG (tmp, FLAG_APPLIED))
1547 {
1548 if (tmp->type != WEAPON)
1549 {
1550 new_draw_info_format (NDI_UNIQUE, 0, op, "You can't throw %s.", query_name (tmp));
1551 tmp = NULL;
1552 }
1553 else if (QUERY_FLAG (tmp, FLAG_CURSED) || QUERY_FLAG (tmp, FLAG_DAMNED))
1554 {
1555 new_draw_info_format (NDI_UNIQUE, 0, op, "The %s sticks to your hand!", query_name (tmp));
1556 tmp = NULL;
1557 }
1558 else
1559 {
1560 if (apply_special (op, tmp, AP_UNAPPLY | AP_NO_MERGE))
1561 {
1562 LOG (llevError, "BUG: find_throw_ob(): couldn't unapply\n");
1563 tmp = NULL;
1564 }
1565 }
1566 }
1567 else if (QUERY_FLAG (tmp, FLAG_UNPAID))
1568 {
1569 new_draw_info_format (NDI_UNIQUE, 0, op, "You should pay for the %s first.", query_name (tmp));
1570 tmp = NULL;
1571 }
1572
1573 if (tmp && QUERY_FLAG (tmp, FLAG_INV_LOCKED))
1574 {
1575 LOG (llevError, "BUG: find_throw_ob(): object is locked\n");
1576 tmp = NULL;
1577 }
1578 return tmp;
1579 }
1580
1581 /* make_throw_ob() We construct the 'carrier' object in
1582 * which we will insert the object that is being thrown.
1583 * This combination becomes the 'thrown object'. -b.t.
1584 */
1585 static object *
1586 make_throw_ob (object *orig)
1587 {
1588 if (!orig)
1589 return NULL;
1590
1591 if (QUERY_FLAG (orig, FLAG_APPLIED))
1592 {
1593 LOG (llevError, "BUG: make_throw_ob(): ob is applied\n");
1594 /* insufficient workaround, but better than nothing */
1595 CLEAR_FLAG (orig, FLAG_APPLIED);
1596 }
1597
1598 object *toss_item = orig->clone ();
1599
1600 toss_item->type = THROWN_OBJ;
1601 CLEAR_FLAG (toss_item, FLAG_CHANGING);
1602 toss_item->stats.dam = 0; /* default damage */
1603 insert_ob_in_ob (orig, toss_item);
1604 return toss_item;
1605 }
1606
1607 /* do_throw() - op throws any object toss_item. This code
1608 * was borrowed from fire_bow.
1609 * Returns 1 if skill was successfully used, 0 if not
1610 */
1611 static int
1612 do_throw (object *op, object *part, object *toss_item, int dir, object *skill)
1613 {
1614 object *throw_ob = toss_item, *left = NULL;
1615 int eff_str = 0, maxc, str = op->stats.Str, dam = 0;
1616 int pause_f, weight_f = 0, mflags;
1617 float str_factor = 1.0, load_factor = 1.0, item_factor = 1.0;
1618 maptile *m;
1619 sint16 sx, sy;
1620
1621 if (throw_ob == NULL)
1622 {
1623 if (op->type == PLAYER)
1624 new_draw_info (NDI_UNIQUE, 0, op, "You have nothing to throw.");
1625
1626 return 0;
1627 }
1628 if (QUERY_FLAG (throw_ob, FLAG_STARTEQUIP))
1629 {
1630 if (op->type == PLAYER)
1631 new_draw_info (NDI_UNIQUE, 0, op, "The gods won't let you throw that.");
1632
1633 return 0;
1634 }
1635
1636 /* Because throwing effectiveness must be reduced by the
1637 * encumbrance of the thrower and weight of the object. THus,
1638 * we use the concept of 'effective strength' as defined below.
1639 */
1640
1641 /* if str exceeds MAX_STAT (30, eg giants), lets assign a str_factor > 1 */
1642 if (str > MAX_STAT)
1643 {
1644 str_factor = (float) str / (float) MAX_STAT;
1645 str = MAX_STAT;
1646 }
1647
1648 /* the more we carry, the less we can throw. Limit only on players */
1649 maxc = max_carry[str] * 1000;
1650 if (op->carrying > maxc && op->type == PLAYER)
1651 load_factor = (float) maxc / (float) op->carrying;
1652
1653 /* lighter items are thrown harder, farther, faster */
1654 if (throw_ob->weight > 0)
1655 item_factor = (float) maxc / (float) (3.0 * throw_ob->weight);
1656 else
1657 { /* 0 or negative weight?!? Odd object, can't throw it */
1658 new_draw_info_format (NDI_UNIQUE, 0, op, "You can't throw %s.\n", query_name (throw_ob));
1659 return 0;
1660 }
1661
1662 eff_str = (int) (str * (load_factor < 1.0 ? load_factor : 1.0));
1663 eff_str = (int) ((float) eff_str * item_factor * str_factor);
1664
1665 /* alas, arrays limit us to a value of MAX_STAT (30). Use str_factor to
1666 * account for super-strong throwers. */
1667 if (eff_str > MAX_STAT)
1668 eff_str = MAX_STAT;
1669
1670 #ifdef DEBUG_THROW
1671 LOG (llevDebug, "%s carries %d, eff_str=%d\n", op->name, op->carrying, eff_str);
1672 LOG (llevDebug, " max_c=%d, item_f=%f, load_f=%f, str=%d\n", maxc, item_factor, load_factor, op->stats.Str);
1673 LOG (llevDebug, " str_factor=%f\n", str_factor);
1674 LOG (llevDebug, " item %s weight= %d\n", throw_ob->name, throw_ob->weight);
1675 #endif
1676
1677 /* 3 things here prevent a throw, you aimed at your feet, you
1678 * have no effective throwing strength, or you threw at something
1679 * that flying objects can't get through.
1680 */
1681 mflags = get_map_flags (part->map, &m, part->x + freearr_x[dir], part->y + freearr_y[dir], &sx, &sy);
1682
1683 if (!dir || (eff_str <= 1) || (mflags & P_OUT_OF_MAP) || (GET_MAP_MOVE_BLOCK (m, sx, sy) & MOVE_FLY_LOW))
1684 {
1685 /* bounces off 'wall', and drops to feet */
1686 throw_ob->insert_at (part, op);
1687
1688 if (op->type == PLAYER)
1689 {
1690 if (eff_str <= 1)
1691 new_draw_info_format (NDI_UNIQUE, 0, op, "Your load is so heavy you drop %s to the ground.", query_name (throw_ob));
1692 else if (!dir)
1693 new_draw_info_format (NDI_UNIQUE, 0, op, "You throw %s at the ground.", query_name (throw_ob));
1694 else
1695 new_draw_info (NDI_UNIQUE, 0, op, "Something is in the way.");
1696 }
1697
1698 return 0;
1699 } /* if object can't be thrown */
1700
1701 left = throw_ob; /* these are throwing objects left to the player */
1702
1703 /* sometimes get_split_ob can't split an object (because op->nrof==0?)
1704 * and returns NULL. We must use 'left' then
1705 */
1706
1707 if ((throw_ob = get_split_ob (throw_ob, 1)) == NULL)
1708 {
1709 throw_ob = left;
1710 left->remove ();
1711 if (op->type == PLAYER)
1712 esrv_del_item (op->contr, left->count);
1713 }
1714 else if (op->type == PLAYER)
1715 {
1716 if (left->destroyed ())
1717 esrv_del_item (op->contr, left->count);
1718 else
1719 esrv_update_item (UPD_NROF, op, left);
1720 }
1721
1722 /* special case: throwing powdery substances like dust, dirt */
1723 if (throw_ob->type == POTION && throw_ob->subtype == POT_DUST)
1724 {
1725 cast_dust (op, throw_ob, dir);
1726 return 1;
1727 }
1728
1729 /* Make a thrown object -- insert real object in a 'carrier' object.
1730 * If unsuccessfull at making the "thrown_obj", we just reinsert
1731 * the original object back into inventory and exit
1732 */
1733 if ((toss_item = make_throw_ob (throw_ob)))
1734 {
1735 throw_ob = toss_item;
1736 throw_ob->skill = skill->skill;
1737 }
1738 else
1739 {
1740 insert_ob_in_ob (throw_ob, op);
1741 return 0;
1742 }
1743
1744 throw_ob->set_owner (op);
1745 /* At some point in the attack code, the actual real object (op->inv)
1746 * becomes the hitter. As such, we need to make sure that has a proper
1747 * owner value so exp goes to the right place.
1748 */
1749 throw_ob->inv->set_owner (op);
1750 throw_ob->direction = dir;
1751
1752 /* the damage bonus from the force of the throw */
1753 dam = (int) (str_factor * dam_bonus[eff_str]);
1754
1755 /* Now, lets adjust the properties of the thrown_ob. */
1756
1757 /* how far to fly */
1758 throw_ob->last_sp = (eff_str * 3) / 5;
1759
1760 /* speed */
1761 throw_ob->set_speed (min (1.0, speed_bonus[eff_str] + 1.0) / 1.5); /* no faster than an arrow! */
1762
1763 /* item damage. Eff_str and item weight influence damage done */
1764 weight_f = (throw_ob->weight / 2000) > MAX_STAT ? MAX_STAT : (throw_ob->weight / 2000);
1765 throw_ob->stats.dam += (dam / 3) + dam_bonus[weight_f] + (throw_ob->weight / 15000) - 2;
1766
1767 /* chance of breaking. Proportional to force used and weight of item */
1768 throw_ob->stats.food = (dam / 2) + (throw_ob->weight / 60000);
1769
1770 /* replace 25 with a call to clone.arch wc? messes up w/ NPC */
1771 throw_ob->stats.wc = 25 - dex_bonus[op->stats.Dex] - thaco_bonus[eff_str] - skill->level;
1772
1773 /* the properties of objects which are meant to be thrown (ie dart,
1774 * throwing knife, etc) will differ from ordinary items. Lets tailor
1775 * this stuff in here.
1776 */
1777
1778 if (QUERY_FLAG (throw_ob->inv, FLAG_IS_THROWN))
1779 {
1780 throw_ob->last_sp += eff_str / 3; /* fly a little further */
1781 throw_ob->stats.dam += throw_ob->inv->stats.dam + throw_ob->magic + 2;
1782 throw_ob->stats.wc -= throw_ob->magic + throw_ob->inv->stats.wc;
1783 /* only throw objects get directional faces */
1784 if (GET_ANIM_ID (throw_ob) && NUM_ANIMATIONS (throw_ob))
1785 SET_ANIMATION (throw_ob, dir);
1786 }
1787 else
1788 {
1789 uint16 mat = throw_ob->materials;
1790
1791 /* some materials will adjust properties.. */
1792 if (mat & M_LEATHER)
1793 {
1794 throw_ob->stats.dam -= 1;
1795 throw_ob->stats.food -= 10;
1796 }
1797
1798 if (mat & M_GLASS)
1799 throw_ob->stats.food += 60;
1800
1801 if (mat & M_ORGANIC)
1802 {
1803 throw_ob->stats.dam -= 3;
1804 throw_ob->stats.food += 55;
1805 }
1806
1807 if (mat & M_PAPER || mat & M_CLOTH)
1808 {
1809 throw_ob->stats.dam -= 5;
1810 throw_ob->speed *= 0.8;
1811 throw_ob->stats.wc += 3;
1812 throw_ob->stats.food -= 30;
1813 }
1814
1815 /* light obj have more wind resistance, fly slower */
1816 if (throw_ob->weight > 500)
1817 throw_ob->speed *= 0.8;
1818
1819 if (throw_ob->weight > 50)
1820 throw_ob->speed *= 0.5;
1821 } /* else tailor thrown object */
1822
1823 /* some limits, and safeties (needed?) */
1824 if (throw_ob->stats.dam < 0)
1825 throw_ob->stats.dam = 0;
1826 if (throw_ob->last_sp > eff_str)
1827 throw_ob->last_sp = eff_str;
1828 if (throw_ob->stats.food < 0)
1829 throw_ob->stats.food = 0;
1830 if (throw_ob->stats.food > 100)
1831 throw_ob->stats.food = 100;
1832 if (throw_ob->stats.wc > 30)
1833 throw_ob->stats.wc = 30;
1834
1835 /* how long to pause the thrower. Higher values mean less pause */
1836 pause_f = ((2 * eff_str) / 3) + 20 + skill->level;
1837
1838 /* Put a lower limit on this */
1839 if (pause_f < 10)
1840 pause_f = 10;
1841 if (pause_f > 100)
1842 pause_f = 100;
1843
1844 /* Changed in 0.94.2 - the calculation before was really goofy.
1845 * In short summary, a throw can take anywhere between speed 5 and
1846 * speed 0.5
1847 */
1848 op->speed_left -= 50 / pause_f;
1849
1850 throw_ob->speed_left = 0;
1851 throw_ob->map = part->map;
1852
1853 throw_ob->move_type = MOVE_FLY_LOW;
1854 throw_ob->move_on = MOVE_FLY_LOW | MOVE_WALK;
1855
1856 #if 0
1857 /* need to put in a good sound for this */
1858 play_sound_map (op->map, op->x, op->y, SOUND_THROW_OBJ);
1859 #endif
1860
1861 /* Lauwenmark - Now we can call the associated script_throw event (if any) */
1862 INVOKE_OBJECT (THROW, throw_ob, ARG_OBJECT (op));
1863 #ifdef DEBUG_THROW
1864 LOG (llevDebug, " pause_f=%d \n", pause_f);
1865 LOG (llevDebug, " %s stats: wc=%d dam=%d dist=%d spd=%f break=%d\n",
1866 throw_ob->name, throw_ob->stats.wc, throw_ob->stats.dam, throw_ob->last_sp, throw_ob->speed, throw_ob->stats.food);
1867 LOG (llevDebug, "inserting tossitem (%d) into map\n", throw_ob->count);
1868 #endif
1869
1870 throw_ob->insert_at (part, op);
1871
1872 if (!throw_ob->destroyed ())
1873 move_arrow (throw_ob);
1874
1875 return 1;
1876 }
1877
1878 int
1879 skill_throw (object *op, object *part, int dir, const char *params, object *skill)
1880 {
1881 object *throw_ob;
1882
1883 if (op->type == PLAYER)
1884 throw_ob = find_throw_ob (op, params);
1885 else
1886 throw_ob = find_mon_throw_ob (op);
1887
1888 return do_throw (op, part, throw_ob, dir, skill);
1889 }