ViewVC Help
View File | Revision Log | Show Annotations | Download File
/cvs/deliantra/server/server/skills.C
Revision: 1.28
Committed: Mon Apr 30 04:25:30 2007 UTC (17 years, 1 month ago) by root
Content type: text/plain
Branch: MAIN
Changes since 1.27: +2 -3 lines
Log Message:
This is the first rough cut of the skill use system (use the STABLE tag).

Details will likely change, and combat skills do not work very well, but
it works quite well.

Players no longer have a shoottype or range slots, instead, each player
has these members:

   combat_skill/combat_ob  the currently selected skill (and weapon)
                           for direct attacks.
   ranged_skill/ranged_ob  the currently selected ranged skill (and
                           bow/spell/item)
   golem                   the currently-controlled golem, if any.

File Contents

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