ViewVC Help
View File | Revision Log | Show Annotations | Download File
/cvs/deliantra/server/server/skill_util.C
Revision: 1.7
Committed: Sun Sep 3 00:18:42 2006 UTC (17 years, 9 months ago) by root
Content type: text/plain
Branch: MAIN
Changes since 1.6: +15 -33 lines
Log Message:
THIS CODE WILL NOT COMPILE
use the STABLE tag instead.

- major changes in object lifetime and memory management
- replaced manual refcounting by shstr class
- removed quest system
- many optimisations
- major changes

File Contents

# User Rev Content
1 elmex 1.1 /*
2     * static char *rcsid_skill_util_c =
3 root 1.7 * "$Id: skill_util.C,v 1.6 2006-08-30 06:06:27 root Exp $";
4 elmex 1.1 */
5     /*
6     CrossFire, A Multiplayer game for X-windows
7    
8     Copryight (C) 2002 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 author can be reached via e-mail to crossfire-devel@real-time.com
26     */
27    
28     /* Created July 95 to separate skill utilities from actual skills -b.t. */
29    
30     /* Reconfigured skills code to allow linking of skills to experience
31     * categories. This is done solely through the init_new_exp_system() fctn.
32     * June/July 1995 -b.t. thomas@astro.psu.edu
33     */
34    
35     /* July 1995 - Initial associated skills coding. Experience gains
36     * come solely from the use of skills. Overcoming an opponent (in combat,
37     * finding/disarming a trap, stealing from somebeing, etc) gains
38     * experience. Calc_skill_exp() handles the gained experience using
39     * modifications in the skills[] table. - b.t.
40     */
41    
42     /* define the following for skills utility debuging */
43     /* #define SKILL_UTIL_DEBUG */
44    
45     #define WANT_UNARMED_SKILLS
46    
47     #include <global.h>
48     #include <object.h>
49     #ifndef __CEXTRACT__
50     #include <sproto.h>
51     #endif
52     #include <living.h> /* for defs of STR,CON,DEX,etc. -b.t.*/
53     #include <spells.h>
54    
55     static int attack_hth(object *pl, int dir, const char *string, object *skill);
56     static int attack_melee_weapon(object *op, int dir, const char *string, object *skill);
57    
58 root 1.7 shstr skill_names[NUM_SKILLS];
59 elmex 1.1
60     /* init_skills basically just sets up the skill_names table
61     * above. The index into the array is set up by the
62     * subtypes.
63     */
64     void init_skills(void) {
65     int i;
66     archetype *at;
67    
68     for(at = first_archetype;at!=NULL;at=at->next) {
69 root 1.5 if (at->clone.type == SKILL) {
70     if (skill_names[at->clone.subtype] != NULL) {
71     LOG(llevError, "init_skills: multiple skill using same subtype %d, %s, %s\n",
72 root 1.7 at->clone.subtype, &skill_names[at->clone.subtype], &at->clone.skill);
73 root 1.5 } else {
74 root 1.7 skill_names[at->clone.subtype] = at->clone.skill;
75 root 1.5 }
76     }
77 elmex 1.1 }
78    
79     /* This isn't really an error if there is no skill subtype set, but
80     * checking for this may catch some user errors.
81     */
82     for (i=1; i<NUM_SKILLS; i++) {
83 root 1.5 if (!skill_names[i])
84     LOG(llevError, "init_skills: skill subtype %d doesn't have a name?\n",
85     i);
86 elmex 1.1 }
87     }
88    
89    
90     /* This function goes through the player inventory and sets
91     * up the last_skills[] array in the player object.
92     * the last_skills[] is used to more quickly lookup skills -
93     * mostly used for sending exp.
94     */
95     void link_player_skills(object *op)
96     {
97     object *tmp;
98    
99     for (tmp=op->inv; tmp; tmp=tmp->below) {
100 root 1.5 if (tmp->type == SKILL) {
101     /* This is really a warning, hence no else below */
102     if (op->contr->last_skill_ob[tmp->subtype] && op->contr->last_skill_ob[tmp->subtype] != tmp) {
103     LOG(llevError,"Multiple skills with the same subtype? %s, %s\n",
104 root 1.7 &op->contr->last_skill_ob[tmp->subtype]->skill, &tmp->skill);
105 root 1.5 }
106     if (tmp->subtype >= NUM_SKILLS) {
107     LOG(llevError,"Invalid subtype number %d (range 0-%d)\n",
108     tmp->subtype, NUM_SKILLS);
109     } else {
110     op->contr->last_skill_ob[tmp->subtype] = tmp;
111     op->contr->last_skill_exp[tmp->subtype] = -1;
112     }
113     }
114 elmex 1.1 }
115     }
116    
117     /* This returns the skill pointer of the given name (the
118     * one that accumlates exp, has the level, etc).
119     *
120     * It is presumed that the player will be needing to actually
121     * use the skill, so thus if use of the skill requires a skill
122     * tool, this code will equip it.
123     */
124     object *find_skill_by_name(object *who, const char *name)
125     {
126     object *skill=NULL, *skill_tool=NULL, *tmp;
127    
128     if (!name) return NULL;
129    
130     /* We make sure the length of the string in the object is greater
131     * in length than the passed string. Eg, if we have a skill called
132     * 'hi', we don't want to match if the user passed 'high'
133     */
134     for (tmp=who->inv; tmp!=NULL; tmp=tmp->below) {
135 root 1.5 if (tmp->type == SKILL && !strncasecmp(name, tmp->skill, strlen(name)) &&
136     strlen(tmp->skill) >= strlen(name)) skill = tmp;
137 elmex 1.1
138 root 1.5 /* Try to find appropriate skilltool. If the player has one already
139     * applied, we try to keep using that one.
140     */
141     else if (tmp->type == SKILL_TOOL && !strncasecmp(name, tmp->skill, strlen(name)) &&
142     strlen(tmp->skill) >= strlen(name)) {
143     if (QUERY_FLAG(tmp, FLAG_APPLIED)) skill_tool = tmp;
144     else if (!skill_tool || !QUERY_FLAG(skill_tool, FLAG_APPLIED))
145     skill_tool = tmp;
146     }
147 elmex 1.1 }
148     /* If this is a skill that can be used without a tool, return it */
149     if (skill && QUERY_FLAG(skill, FLAG_CAN_USE_SKILL)) return skill;
150    
151     /* Player has a tool to use the skill. IF not applied, apply it -
152     * if not successful, return null. If they do have the skill tool
153     * but not the skill itself, give it to them.
154     */
155     if (skill_tool) {
156 root 1.5 if (!QUERY_FLAG(skill_tool, FLAG_APPLIED)) {
157     if (apply_special(who, skill_tool, 0)) return NULL;
158     }
159     if (!skill) {
160     skill = give_skill_by_name(who, skill_tool->skill);
161     link_player_skills(who);
162     }
163     return skill;
164 elmex 1.1 }
165     return NULL;
166     }
167    
168    
169     /* This returns the skill pointer of the given name (the
170     * one that accumlates exp, has the level, etc).
171     *
172     * It is presumed that the player will be needing to actually
173     * use the skill, so thus if use of the skill requires a skill
174     * tool, this code will equip it.
175     *
176     * This code is basically the same as find_skill_by_name() above,
177     * but instead a skill name, we search by matching number.
178     * this replaces find_skill.
179     */
180     object *find_skill_by_number(object *who, int skillno)
181     {
182     object *skill=NULL, *skill_tool=NULL, *tmp;
183    
184     if (skillno < 1 || skillno >= NUM_SKILLS) return NULL;
185    
186     for (tmp=who->inv; tmp!=NULL; tmp=tmp->below) {
187 root 1.5 if (tmp->type == SKILL && tmp->subtype == skillno) skill = tmp;
188 elmex 1.1
189 root 1.5 /* Try to find appropriate skilltool. If the player has one already
190     * applied, we try to keep using that one.
191     */
192     else if (tmp->type == SKILL_TOOL && tmp->subtype == skillno) {
193     if (QUERY_FLAG(tmp, FLAG_APPLIED)) skill_tool = tmp;
194     else if (!skill_tool || !QUERY_FLAG(skill_tool, FLAG_APPLIED))
195     skill_tool = tmp;
196     }
197 elmex 1.1 }
198     /* If this is a skill that can be used without a tool, return it */
199     if (skill && QUERY_FLAG(skill, FLAG_CAN_USE_SKILL)) return skill;
200    
201     /* Player has a tool to use the skill. IF not applied, apply it -
202     * if not successful, return null. If they do have the skill tool
203     * but not the skill itself, give it to them.
204     */
205     if (skill_tool) {
206 root 1.5 if (!QUERY_FLAG(skill_tool, FLAG_APPLIED)) {
207     if (apply_special(who, skill_tool, 0)) return NULL;
208     }
209     if (!skill) {
210     skill = give_skill_by_name(who, skill_tool->skill);
211     link_player_skills(who);
212     }
213     return skill;
214 elmex 1.1 }
215     return NULL;
216     }
217    
218     /* This changes the objects skill to new_skill.
219     * note that this function doesn't always need to get used -
220     * you can now add skill exp to the player without the chosen_skill being
221     * set. This function is of most interest to players to update
222     * the various range information.
223     * if new_skill is null, this just unapplies the skill.
224     * flag has the current meaning:
225     * 0x1: If set, don't update the range pointer. This is useful when we
226     * need to ready a new skill, but don't want to clobber range.
227     * return 1 on success, 0 on error
228     */
229    
230     int change_skill (object *who, object *new_skill, int flag)
231     {
232     int old_range;
233    
234     if ( who->type != PLAYER )
235     return 0;
236    
237     old_range = who->contr->shoottype;
238    
239     if (who->chosen_skill && who->chosen_skill == new_skill)
240     {
241     /* optimization for changing skill to current skill */
242     if (who->type == PLAYER && !(flag & 0x1))
243     who->contr->shoottype = range_skill;
244     return 1;
245     }
246    
247     if (!new_skill || who->chosen_skill)
248 root 1.5 if (who->chosen_skill) apply_special(who, who->chosen_skill, AP_UNAPPLY);
249 elmex 1.1
250     /* Only goal in this case was to unapply a skill */
251     if (!new_skill) return 0;
252    
253     if (apply_special (who, new_skill, AP_APPLY)) {
254 root 1.5 return 0;
255 elmex 1.1 }
256     if (flag & 0x1)
257 root 1.5 who->contr->shoottype = (rangetype) old_range;
258 elmex 1.1
259     return 1;
260     }
261    
262     /* This function just clears the chosen_skill and range_skill values
263     * inthe player.
264     */
265     void clear_skill(object *who)
266     {
267     who->chosen_skill = NULL;
268     CLEAR_FLAG(who, FLAG_READY_SKILL);
269     if (who->type == PLAYER) {
270 root 1.5 who->contr->ranges[range_skill] = NULL;
271     if (who->contr->shoottype == range_skill)
272     who->contr->shoottype = range_none;
273 elmex 1.1 }
274     }
275    
276     /* do_skill() - Main skills use function-similar in scope to cast_spell().
277     * We handle all requests for skill use outside of some combat here.
278     * We require a separate routine outside of fire() so as to allow monsters
279     * to utilize skills. Returns 1 on use of skill, otherwise 0.
280     * This is changed (2002-11-30) from the old method that returned
281     * exp - no caller needed that info, but it also prevented the callers
282     * from know if a skill was actually used, as many skills don't
283     * give any exp for their direct use (eg, throwing).
284     * It returns 0 if no skill was used.
285     */
286    
287     int do_skill (object *op, object *part, object *skill, int dir, const char *string) {
288 root 1.2 int success=0, exp=0;
289 elmex 1.1 int did_alc = 0;
290     object *tmp, *next;
291    
292     if (!skill) return 0;
293    
294     /* The code below presumes that the skill points to the object that
295     * holds the exp, level, etc of the skill. So if this is a player
296     * go and try to find the actual real skill pointer, and if the
297     * the player doesn't have a bucket for that, create one.
298     */
299     if (skill->type != SKILL && op->type == PLAYER) {
300 root 1.5 for (tmp = op->inv; tmp!=NULL; tmp=tmp->below) {
301     if (tmp->type == SKILL && tmp->skill == skill->skill) break;
302     }
303     if (!tmp) tmp=give_skill_by_name(op, skill->skill);
304     skill = tmp;
305 elmex 1.1 }
306    
307 root 1.2 // skill, by_whom, on_which_object, which direction, skill_argument
308     if (INVOKE_OBJECT (USE_SKILL, skill, ARG_OBJECT (op), ARG_OBJECT (part), ARG_INT (dir), ARG_STRING (string)))
309     return 0;
310 elmex 1.1
311     switch(skill->subtype) {
312 root 1.5 case SK_LEVITATION:
313     /* Not 100% sure if this will work with new movement code -
314     * the levitation skill has move_type for flying, so when
315     * equipped, that should transfer to player, when not,
316     * shouldn't.
317     */
318     if(QUERY_FLAG(skill,FLAG_APPLIED)) {
319     CLEAR_FLAG(skill,FLAG_APPLIED);
320     new_draw_info(NDI_UNIQUE,0,op,"You come to earth.");
321     }
322     else {
323     SET_FLAG(skill,FLAG_APPLIED);
324     new_draw_info(NDI_UNIQUE,0,op,"You rise into the air!.");
325     }
326     fix_player(op);
327     success=1;
328     break;
329    
330     case SK_STEALING:
331     exp = success = steal(op, dir, skill);
332     break;
333    
334     case SK_LOCKPICKING:
335     exp = success = pick_lock(op, dir, skill);
336     break;
337    
338     case SK_HIDING:
339     exp = success = hide(op, skill);
340     break;
341    
342     case SK_JUMPING:
343     success = jump(op, dir, skill);
344     break;
345    
346     case SK_INSCRIPTION:
347     exp = success = write_on_item(op,string, skill);
348     break;
349    
350     case SK_MEDITATION:
351     meditate(op, skill);
352     success=1;
353     break;
354     /* note that the following 'attack' skills gain exp through hit_player() */
355    
356     case SK_KARATE:
357     (void) attack_hth(op,dir,"karate-chopped", skill);
358     break;
359    
360     case SK_PUNCHING:
361     (void) attack_hth(op,dir,"punched", skill);
362     break;
363    
364     case SK_FLAME_TOUCH:
365     (void) attack_hth(op,dir,"flamed", skill);
366     break;
367 elmex 1.1
368     case SK_SPARK_TOUCH:
369     (void) attack_hth(op,dir,"zapped", skill);
370     break;
371    
372     case SK_SHIVER:
373     (void) attack_hth(op,dir,"froze", skill);
374     break;
375    
376     case SK_ACID_SPLASH:
377     (void) attack_hth(op,dir,"dissolved", skill);
378     break;
379    
380     case SK_POISON_NAIL:
381     (void) attack_hth(op,dir,"injected poison into", skill);
382     break;
383    
384 root 1.5 case SK_CLAWING:
385     (void) attack_hth(op,dir,"clawed", skill);
386     break;
387    
388     case SK_ONE_HANDED_WEAPON:
389     case SK_TWO_HANDED_WEAPON:
390     (void) attack_melee_weapon(op,dir,NULL, skill);
391     break;
392    
393     case SK_FIND_TRAPS:
394     exp = success = find_traps(op, skill);
395     break;
396    
397     case SK_SINGING:
398     exp = success = singing(op,dir, skill);
399     break;
400    
401     case SK_ORATORY:
402     exp = success = use_oratory(op,dir, skill);
403     break;
404    
405     case SK_SMITHERY:
406     case SK_BOWYER:
407     case SK_JEWELER:
408     case SK_ALCHEMY:
409     case SK_THAUMATURGY:
410     case SK_LITERACY:
411     case SK_WOODSMAN:
412     /* first, we try to find a cauldron, and do the alchemy thing.
413     * failing that, we go and identify stuff.
414     */
415     for (tmp=get_map_ob(op->map, op->x, op->y); tmp != NULL;tmp=next) {
416     next=tmp->above;
417     if(QUERY_FLAG(tmp, FLAG_IS_CAULDRON)) {
418     attempt_do_alchemy(op, tmp);
419     if (QUERY_FLAG(tmp, FLAG_APPLIED))
420     esrv_send_inventory(op, tmp);
421     did_alc=1;
422     }
423     }
424     if (did_alc == 0)
425     exp = success = skill_ident(op,skill);
426     break;
427    
428     case SK_DET_MAGIC:
429     case SK_DET_CURSE:
430     exp = success = skill_ident(op,skill);
431     break;
432    
433     case SK_DISARM_TRAPS:
434     exp = success = remove_trap(op,dir, skill);
435     break;
436    
437     case SK_THROWING:
438     success = skill_throw(op,part,dir,string, skill);
439     break;
440    
441     case SK_SET_TRAP:
442     new_draw_info(NDI_UNIQUE, 0,op,"This skill is not currently implemented.");
443     break;
444    
445     case SK_USE_MAGIC_ITEM:
446     case SK_MISSILE_WEAPON:
447     new_draw_info(NDI_UNIQUE, 0,op,"There is no special attack for this skill.");
448     break;
449    
450     case SK_PRAYING:
451     success = pray(op, skill);
452     break;
453    
454     case SK_BARGAINING:
455     success = describe_shop(op);
456     break;
457 elmex 1.1
458 root 1.5 case SK_SORCERY:
459     case SK_EVOCATION:
460     case SK_PYROMANCY:
461     case SK_SUMMONING:
462     case SK_CLIMBING:
463     new_draw_info(NDI_UNIQUE, 0,op,"This skill is already in effect.");
464     break;
465    
466     default:
467     LOG(llevDebug,"%s attempted to use unknown skill: %d\n"
468 elmex 1.1 ,query_name(op), op->chosen_skill->stats.sp);
469 root 1.5 break;
470 elmex 1.1 }
471    
472     /* For players we now update the speed_left from using the skill.
473     * Monsters have no skill use time because of the random nature in
474     * which use_monster_skill is called already simulates this.
475     * If certain skills should take more/less time, that should be
476     * in the code for the skill itself.
477     */
478    
479     if(op->type==PLAYER) op->speed_left -= 1.0;
480    
481     /* this is a good place to add experience for successfull use of skills.
482     * Note that add_exp() will figure out player/monster experience
483     * gain problems.
484     */
485    
486     if(success && exp) change_exp(op,exp, skill->skill, 0);
487    
488     return success;
489     }
490    
491     /* calc_skill_exp() - calculates amount of experience can be gained for
492     * successfull use of a skill. Returns value of experience gain.
493     * Here we take the view that a player must 'overcome an opponent'
494     * in order to gain experience. Examples include foes killed combat,
495     * finding/disarming a trap, stealing from somebeing, etc.
496     * The gained experience is based primarily on the difference in levels,
497     * exp point value of vanquished foe, the relevent stats of the skill being
498     * used and modifications in the skills[] table.
499     *
500     * For now, monsters and players will be treated differently. Below I give
501     * the algorithm for *PLAYER* experience gain. Monster exp gain is simpler.
502     * Monsters just get 10% of the exp of the opponent.
503     *
504     * players get a ratio, eg, opponent lvl / player level. This is then
505     * multiplied by various things. If simple exp is true, then
506     * this multiplier, include the level difference, is always 1.
507     * This revised method prevents some cases where there are big gaps
508     * in the amount you get just because you are now equal level vs lower
509     * level
510     * who is player/creature that used the skill.
511     * op is the object that was 'defeated'.
512     * skill is the skill used. If no skill is used, it should just
513     * point back to who.
514     *
515     */
516    
517     int calc_skill_exp(object *who, object *op, object *skill) {
518     int op_exp=0,op_lvl= 0;
519     float base,value,lvl_mult=0.0;
520    
521     if (!skill) skill = who;
522    
523     /* Oct 95 - where we have an object, I expanded our treatment
524     * to 3 cases:
525     * non-living magic obj, runes and everything else.
526     *
527     * If an object is not alive and magical we set the base exp higher to
528     * help out exp awards for skill_ident skills. Also, if
529     * an item is type RUNE, we give out exp based on stats.Cha
530     * and level (this was the old system) -b.t.
531     */
532    
533     if(!op) { /* no item/creature */
534     op_lvl= who->map->difficulty < 1 ? 1: who->map->difficulty;
535     op_exp = 0;
536     } else if(op->type==RUNE || op->type==TRAP) { /* all traps. If stats.Cha > 1 we use that
537 root 1.5 * for the amount of experience */
538 elmex 1.1 op_exp = op->stats.Cha>1 ? op->stats.Cha : op->stats.exp;
539     op_lvl = op->level;
540     } else { /* all other items/living creatures */
541     op_exp = op->stats.exp;
542 root 1.5 op_lvl = op->level;
543 elmex 1.1 if(!QUERY_FLAG(op,FLAG_ALIVE)) { /* for ident/make items */
544 root 1.5 op_lvl += 5 * abs(op->magic);
545     }
546 elmex 1.1 }
547    
548     if(op_lvl<1) op_lvl = 1;
549    
550     if(who->type!=PLAYER) { /* for monsters only */
551     return ((int) (op_exp*0.1)+1); /* we add one to insure positive value is returned */
552     } else { /* for players */
553 root 1.5 base = op_exp;
554     /* if skill really is a skill, then we can look at the skill archetype for
555     * bse reward value (exp) and level multiplier factor.
556     */
557     if (skill->type == SKILL) {
558     base += skill->arch->clone.stats.exp;
559     if (settings.simple_exp) {
560     if (skill->arch->clone.level)
561     lvl_mult = (float) skill->arch->clone.level / 100.0;
562     else
563     lvl_mult = 1.0; /* no adjustment */
564     }
565     else {
566     if (skill->level)
567     lvl_mult = ((float) skill->arch->clone.level * (float) op_lvl) / ((float) skill->level * 100.0);
568     else
569     lvl_mult = 1.0;
570     }
571     } else {
572     /* Don't divide by zero here! */
573     lvl_mult = (float) op_lvl / (float) (skill->level?skill->level:1);
574     }
575 elmex 1.1 }
576    
577     /* assemble the exp total, and return value */
578    
579     value = base * lvl_mult;
580     if (value < 1) value=1; /* Always give at least 1 exp point */
581    
582     #ifdef SKILL_UTIL_DEBUG
583     LOG(llevDebug,"calc_skill_exp(): who: %s(lvl:%d) op:%s(lvl:%d)\n",
584 root 1.5 who->name,skill->level,op->name,op_lvl);
585 elmex 1.1 #endif
586     return ( (int) value);
587     }
588    
589     /* Learn skill. This inserts the requested skill in the player's
590     * inventory. The skill field of the scroll should have the
591     * exact name of the requested skill.
592     * This one actually teaches the player the skill as something
593     * they can equip.
594     * Return 0 if the player knows the skill, 1 if the
595     * player learns the skill, 2 otherwise.
596     */
597    
598     int
599     learn_skill (object *pl, object *scroll) {
600     object *tmp;
601    
602     if (!scroll->skill) {
603 root 1.7 LOG(llevError,"skill scroll %s does not have skill pointer set.\n", &scroll->name);
604 root 1.5 return 2;
605 elmex 1.1 }
606    
607     /* can't use find_skill_by_name because we want skills the player knows
608     * but can't use natively.
609     */
610    
611     for (tmp=pl->inv; tmp!=NULL; tmp=tmp->below)
612 root 1.5 if (tmp->type == SKILL && !strncasecmp(scroll->skill, tmp->skill, strlen(scroll->skill))) break;
613 elmex 1.1
614     /* player already knows it */
615     if (tmp && QUERY_FLAG(tmp, FLAG_CAN_USE_SKILL)) return 0;
616    
617    
618    
619     /* now a random change to learn, based on player Int.
620     * give bonus based on level - otherwise stupid characters
621     * might never be able to learn anything.
622     */
623     if(random_roll(0, 99, pl, PREFER_LOW)>(learn_spell[pl->stats.Int] + (pl->level/5)))
624 root 1.5 return 2; /* failure :< */
625 elmex 1.1
626     if (!tmp)
627 root 1.5 tmp = give_skill_by_name(pl, scroll->skill);
628 elmex 1.1
629     if (!tmp) {
630 root 1.7 LOG(llevError,"skill scroll %s does not have valid skill name (%s).\n", &scroll->name, &scroll->skill);
631 root 1.5 return 2;
632 elmex 1.1 }
633    
634     SET_FLAG(tmp, FLAG_CAN_USE_SKILL);
635     link_player_skills(pl);
636     return 1;
637     }
638    
639     /* Gives a percentage clipped to 0% -> 100% of a/b. */
640     /* Probably belongs in some global utils-type file? */
641     static int clipped_percent(sint64 a, sint64 b)
642     {
643     int rv;
644    
645     if (b <= 0)
646     return 0;
647    
648     rv = (int)((100.0f * ((float)a) / ((float)b) ) + 0.5f);
649    
650     if (rv < 0)
651     return 0;
652     else if (rv > 100)
653     return 100;
654    
655     return rv;
656     }
657    
658     /* show_skills() - Meant to allow players to examine
659     * their current skill list.
660     * This shows the amount of exp they have in the skills.
661     * we also include some other non skill related info (god,
662     * max weapon improvments, item power).
663     * Note this function is a bit more complicated becauase we
664     * we want ot sort the skills before printing them. If we
665     * just dumped this as we found it, this would be a bit
666     * simpler.
667     */
668    
669     void show_skills(object *op, const char* search) {
670     object *tmp=NULL;
671     char buf[MAX_BUF];
672     const char *cp;
673     int i,num_skills_found=0;
674     static const char *const periods = "........................................";
675     /* Need to have a pointer and use strdup for qsort to work properly */
676     char skills[NUM_SKILLS][MAX_BUF];
677    
678    
679     for (tmp=op->inv; tmp!=NULL; tmp=tmp->below) {
680 root 1.5 if (tmp->type == SKILL) {
681     if ( search && strstr(tmp->name,search)==NULL ) continue;
682     /* Basically want to fill this out to 40 spaces with periods */
683 root 1.7 sprintf(buf,"%s%s", &tmp->name, periods);
684 root 1.5 buf[40] = 0;
685 elmex 1.1
686 root 1.5 if (settings.permanent_exp_ratio) {
687     sprintf(skills[num_skills_found++],"%slvl:%3d (xp:%lld/%lld/%d%%)",
688     buf,tmp->level,
689 root 1.7 (long long)tmp->stats.exp,
690     (long long)level_exp(tmp->level+1, op->expmul),
691 root 1.5 clipped_percent(tmp->perm_exp,tmp->stats.exp));
692     } else {
693     sprintf(skills[num_skills_found++], "%slvl:%3d (xp:%lld/%lld)",
694     buf,tmp->level,
695 root 1.7 (long long)tmp->stats.exp,
696     (long long)level_exp(tmp->level+1, op->expmul));
697 root 1.5 }
698     /* I don't know why some characters get a bunch of skills, but
699     * it sometimes happens (maybe a leftover from bugier earlier code
700     * and those character are still about). In any case, lets handle
701     * it so it doesn't crash the server - otherwise, one character may
702     * crash the server numerous times.
703     */
704     if (num_skills_found >= NUM_SKILLS) {
705     new_draw_info(NDI_RED, 0, op, "Your character has too many skills.");
706     new_draw_info(NDI_RED, 0, op, "Something isn't right - contact the server admin");
707     break;
708     }
709     }
710 elmex 1.1 }
711    
712     clear_win_info(op);
713     new_draw_info(NDI_UNIQUE, 0,op,"Player skills:");
714     if (num_skills_found > 1) qsort(skills, num_skills_found, MAX_BUF, (int (*)(const void*, const void*))strcmp);
715    
716     for (i=0; i<num_skills_found; i++) {
717 root 1.5 new_draw_info(NDI_UNIQUE, 0, op, skills[i]);
718 elmex 1.1 }
719    
720     new_draw_info_format(NDI_UNIQUE, 0, op,
721 root 1.5 "You can handle %d weapon improvements.",op->level/5+5);
722 elmex 1.1
723     cp = determine_god(op);
724     new_draw_info_format(NDI_UNIQUE, 0, op,
725 root 1.5 "You worship %s.", cp?cp:"no god at current time");
726 elmex 1.1
727     new_draw_info_format(NDI_UNIQUE,0,op, "Your equipped item power is %d out of %d\n",
728 root 1.5 op->contr->item_power, (int) (op->level * settings.item_power_factor));
729 elmex 1.1 }
730    
731     /* use_skill() - similar to invoke command, it executes the skill in the
732     * direction that the user is facing. Returns false if we are unable to
733     * change to the requested skill, or were unable to use the skill properly.
734     * This is tricky because skills can have spaces. We basically roll
735     * our own find_skill_by_name so we can try to do better string matching.
736     */
737    
738     int use_skill(object *op, const char *string) {
739     object *skop;
740     size_t len;
741    
742     if (!string) return 0;
743    
744     for (skop = op->inv; skop != NULL; skop=skop->below) {
745 root 1.5 if (skop->type == SKILL && QUERY_FLAG(skop, FLAG_CAN_USE_SKILL) &&
746     !strncasecmp(string, skop->skill, MIN(strlen(string), strlen(skop->skill))))
747     break;
748     else if (skop->type == SKILL_TOOL &&
749     !strncasecmp(string, skop->skill, MIN(strlen(string), strlen(skop->skill))))
750     break;
751 elmex 1.1 }
752     if (!skop) {
753 root 1.5 new_draw_info_format(NDI_UNIQUE, 0, op,
754     "Unable to find skill %s", string);
755     return 0;
756 elmex 1.1 }
757    
758     len=strlen(skop->skill);
759    
760     /* All this logic goes and skips over the skill name to find any
761     * options given to the skill. Its pretty simple - if there
762     * are extra parameters (as deteremined by string length), we
763     * want to skip over any leading spaces.
764     */
765     if(len>=strlen(string)) {
766 root 1.5 string=NULL;
767 elmex 1.1 } else {
768 root 1.5 string += len;
769     while(*string==0x20) string++;
770     if(strlen(string)==0) string = NULL;
771 elmex 1.1 }
772    
773     #ifdef SKILL_UTIL_DEBUG
774     LOG(llevDebug,"use_skill() got skill: %s\n",sknum>-1?skills[sknum].name:"none");
775     #endif
776    
777     /* Change to the new skill, then execute it. */
778     if(do_skill(op,op,skop, op->facing,string)) return 1;
779    
780     return 0;
781     }
782    
783    
784    
785     /* This finds the best unarmed skill the player has, and returns
786     * it. Best can vary a little - we consider clawing to always
787     * be the best for dragons.
788     * This could be more intelligent, eg, look at the skill level
789     * of the skill and go from there (eg, a level 40 puncher is
790     * is probably better than level 1 karate). OTOH, if you
791     * don't bother to set up your skill properly, that is the players
792     * problem (although, it might be nice to have a preferred skill
793     * field the player can set.
794     * Unlike the old code, we don't give out any skills - it is
795     * possible you just don't have any ability to get into unarmed
796     * combat. If everyone race/class should have one, this should
797     * be handled in the starting treasurelists, not in the code.
798     */
799     static object *find_best_player_hth_skill(object *op)
800     {
801     object *tmp, *best_skill=NULL;
802     int dragon = is_dragon_pl(op), last_skill=sizeof(unarmed_skills), i;
803    
804     for (tmp=op->inv; tmp; tmp=tmp->below) {
805 root 1.5 if (tmp->type == SKILL) {
806     if (dragon && tmp->subtype == SK_CLAWING) return tmp;
807 elmex 1.1
808 root 1.5 /* The order in the array is preferred order. So basically,
809     * we just cut down the number to search - eg, if we find a skill
810     * early on in flame touch, then we only need to look into the unarmed_array
811     * to the entry before flame touch - don't care about the entries afterward,
812     * because they are infrerior skills.
813     * if we end up finding the best skill (i==0) might as well return
814     * right away - can't get any better than that.
815     */
816     for (i=0; i<last_skill; i++) {
817     if (tmp->subtype == unarmed_skills[i] && QUERY_FLAG(tmp, FLAG_CAN_USE_SKILL)) {
818     best_skill = tmp;
819     last_skill = i;
820     if (i==0) return best_skill;
821     }
822     }
823     }
824 elmex 1.1 }
825     return best_skill;
826     }
827    
828     /* do_skill_attack() - We have got an appropriate opponent from either
829     * move_player_attack() or skill_attack(). In this part we get on with
830     * attacking, take care of messages from the attack and changes in invisible.
831     * Returns true if the attack damaged the opponent.
832     * tmp is the targetted monster.
833     * op is what is attacking
834     * string is passed along to describe what messages to describe
835     * the damage.
836     */
837    
838     static int do_skill_attack(object *tmp, object *op, const char *string, object *skill) {
839     int success;
840    
841 root 1.6 if (INVOKE_OBJECT (SKILL_ATTACK, op, ARG_OBJECT (tmp), ARG_STRING (string), ARG_OBJECT (skill)))
842     return RESULT_INT (0);
843    
844 elmex 1.1 /* For Players only: if there is no ready weapon, and no "attack" skill
845     * is readied either then try to find a skill for the player to use.
846     * it is presumed that if skill is set, it is a valid attack skill (eg,
847     * the caller should have set it appropriately). We still want to pass
848     * through that code if skill is set to change to the skill.
849     */
850     if(op->type==PLAYER) {
851 root 1.5 if (!QUERY_FLAG(op,FLAG_READY_WEAPON)) {
852     size_t i;
853 elmex 1.1
854 root 1.5 if (!skill) {
855     /* See if the players chosen skill is a combat skill, and use
856     * it if appropriate.
857     */
858     if (op->chosen_skill) {
859     for (i=0; i<sizeof(unarmed_skills); i++)
860     if (op->chosen_skill->subtype == unarmed_skills[i]) {
861     skill = op->chosen_skill;
862     break;
863     }
864     }
865     /* If we didn't find a skill above, look harder for a good skill */
866     if (!skill) {
867 elmex 1.1 skill = find_best_player_hth_skill(op);
868    
869 root 1.5 if (!skill) {
870     new_draw_info(NDI_BLACK, 0, op, "You have no unarmed combat skills!");
871     return 0;
872     }
873     }
874     }
875     if (skill != op->chosen_skill) {
876     /* now try to ready the new skill */
877     if(!change_skill(op,skill,0)) { /* oh oh, trouble! */
878 root 1.7 new_draw_info_format(NDI_UNIQUE, 0, tmp, "Couldn't change to skill %s", &skill->name);
879 root 1.5 return 0;
880     }
881     }
882     } else {
883     /* Seen some crashes below where current_weapon is not set,
884     * even though the flag says it is. So if current weapon isn't set,
885     * do some work in trying to find the object to use.
886     */
887     if (!op->current_weapon) {
888     object *tmp;
889    
890     LOG(llevError,"Player %s does not have current weapon set but flag_ready_weapon is set\n",
891 root 1.7 &op->name);
892 root 1.5 for (tmp=op->inv; tmp; tmp=tmp->below)
893     if (tmp->type == WEAPON && QUERY_FLAG(tmp, FLAG_APPLIED)) break;
894    
895     if (!tmp) {
896     LOG(llevError,"Could not find applied weapon on %s\n",
897 root 1.7 &op->name);
898 root 1.5 op->current_weapon=NULL;
899     return 0;
900     } else {
901     op->current_weapon = tmp;
902     }
903     }
904    
905     /* Has ready weapon - make sure chosen_skill is set up properly */
906     if (!op->chosen_skill || op->current_weapon->skill != op->chosen_skill->skill) {
907     change_skill(op, find_skill_by_name(op, op->current_weapon->skill), 1);
908     }
909     }
910 elmex 1.1 }
911    
912     /* lose invisiblity/hiding status for running attacks */
913    
914     if(op->type==PLAYER && op->contr->tmp_invis) {
915 root 1.5 op->contr->tmp_invis=0;
916     op->invisible=0;
917     op->hide=0;
918     update_object(op,UP_OBJ_FACE);
919 elmex 1.1 }
920    
921     success = attack_ob(tmp,op);
922    
923     /* print appropriate messages to the player */
924    
925     if(success && string!=NULL && tmp && !QUERY_FLAG(tmp,FLAG_FREED)) {
926 root 1.5 if(op->type==PLAYER)
927     new_draw_info_format(NDI_UNIQUE, 0,op,
928     "You %s %s!",string,query_name(tmp));
929     else if(tmp->type==PLAYER)
930     new_draw_info_format(NDI_UNIQUE, 0,tmp,
931     "%s %s you!",query_name(op),string);
932 elmex 1.1 }
933     return success;
934     }
935    
936    
937     /* skill_attack() - Core routine for use when we attack using a skills
938     * system. In essence, this code handles
939     * all skill-based attacks, ie hth, missile and melee weapons should be
940     * treated here. If an opponent is already supplied by move_player(),
941     * we move right onto do_skill_attack(), otherwise we find if an
942     * appropriate opponent exists.
943     *
944     * This is called by move_player() and attack_hth()
945     *
946     * Initial implementation by -bt thomas@astro.psu.edu
947     */
948    
949     int skill_attack (object *tmp, object *pl, int dir, const char *string, object *skill) {
950     sint16 tx,ty;
951     mapstruct *m;
952     int mflags;
953    
954     if(!dir) dir=pl->facing;
955     tx=freearr_x[dir];
956     ty=freearr_y[dir];
957    
958     /* If we don't yet have an opponent, find if one exists, and attack.
959     * Legal opponents are the same as outlined in move_player_attack()
960     */
961    
962     if(tmp==NULL) {
963 root 1.5 m = pl->map;
964     tx = pl->x + freearr_x[dir];
965     ty = pl->y + freearr_y[dir];
966    
967     mflags = get_map_flags(m, &m, tx, ty, &tx, &ty);
968     if (mflags & P_OUT_OF_MAP) return 0;
969    
970     /* space must be blocked for there to be anything interesting to do */
971     if (!OB_TYPE_MOVE_BLOCK(pl, GET_MAP_MOVE_BLOCK(m, tx,ty))) return 0;
972    
973     for(tmp=get_map_ob(m, tx, ty); tmp; tmp=tmp->above)
974     if((QUERY_FLAG(tmp,FLAG_ALIVE) && tmp->stats.hp>=0)
975     || QUERY_FLAG(tmp, FLAG_CAN_ROLL)
976     || tmp->type==LOCKED_DOOR ) {
977     /* Don't attack party members */
978 elmex 1.1 if((pl->type==PLAYER && tmp->type==PLAYER) && (pl->contr->party!=NULL
979     && pl->contr->party==tmp->contr->party))
980     return 0;
981     break;
982 root 1.5 }
983 elmex 1.1 }
984     if (!tmp) {
985 root 1.5 if(pl->type==PLAYER)
986     new_draw_info(NDI_UNIQUE, 0,pl,"There is nothing to attack!");
987     return 0;
988 elmex 1.1 }
989    
990     return do_skill_attack(tmp,pl,string, skill);
991     }
992    
993    
994     /* attack_hth() - this handles all hand-to-hand attacks -b.t. */
995     /* July 5, 1995 - I broke up attack_hth() into 2 parts. In the first
996     * (attack_hth) we check for weapon use, etc in the second (the new
997     * function skill_attack() we actually attack.
998     */
999    
1000     static int attack_hth(object *pl, int dir, const char *string, object *skill) {
1001     object *enemy=NULL,*weapon;
1002    
1003     if(QUERY_FLAG(pl, FLAG_READY_WEAPON))
1004 root 1.5 for(weapon=pl->inv;weapon;weapon=weapon->below) {
1005     if (weapon->type==WEAPON && QUERY_FLAG(weapon, FLAG_APPLIED)) {
1006     CLEAR_FLAG(weapon,FLAG_APPLIED);
1007     CLEAR_FLAG(pl,FLAG_READY_WEAPON);
1008     fix_player(pl);
1009     if(pl->type==PLAYER) {
1010     new_draw_info(NDI_UNIQUE, 0,pl,"You unwield your weapon in order to attack.");
1011     esrv_update_item(UPD_FLAGS, pl, weapon);
1012     }
1013     break;
1014     }
1015     }
1016 elmex 1.1 return skill_attack(enemy,pl,dir,string, skill);
1017     }
1018    
1019    
1020     /* attack_melee_weapon() - this handles melee weapon attacks -b.t.
1021     * For now we are just checking to see if we have a ready weapon here.
1022     * But there is a real neato possible feature of this scheme which
1023     * bears mentioning:
1024     * Since we are only calling this from do_skill() in the future
1025     * we may make this routine handle 'special' melee weapons attacks
1026     * (like disarming manuever with sai) based on player SK_level and
1027     * weapon type.
1028     */
1029    
1030     static int attack_melee_weapon(object *op, int dir, const char *string, object *skill) {
1031    
1032     if(!QUERY_FLAG(op, FLAG_READY_WEAPON)) {
1033 root 1.5 if(op->type==PLAYER)
1034     new_draw_info(NDI_UNIQUE, 0,op,"You have no ready weapon to attack with!");
1035     return 0;
1036 elmex 1.1 }
1037     return skill_attack(NULL,op,dir,string, skill);
1038    
1039     }