ViewVC Help
View File | Revision Log | Show Annotations | Download File
/cvs/deliantra/server/server/skills.c
Revision: 1.1
Committed: Fri Feb 3 07:14:38 2006 UTC (18 years, 3 months ago) by root
Content type: text/plain
Branch: MAIN
Branch point for: UPSTREAM
Log Message:
Initial revision

File Contents

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