ViewVC Help
View File | Revision Log | Show Annotations | Download File
/cvs/deliantra/server/server/skills.C
Revision: 1.38
Committed: Sun Jul 1 05:00:20 2007 UTC (16 years, 11 months ago) by root
Content type: text/plain
Branch: MAIN
Changes since 1.37: +10 -11 lines
Log Message:
- upgrade crossfire trt to the GPL version 3 (hopefully correctly).
- add a single file covered by the GNU Affero General Public License
  (which is not yet released, so I used the current draft, which is
  legally a bit wavy, but its likely better than nothing as it expresses
  direct intent by the authors, and we can upgrade as soon as it has been
  released).
  * this should ensure availability of source code for the server at least
    and hopefully also archetypes and maps even when modified versions
    are not being distributed, in accordance of section 13 of the agplv3.

File Contents

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