ViewVC Help
View File | Revision Log | Show Annotations | Download File
/cvs/deliantra/server/server/skill_util.c
Revision: 1.3
Committed: Sun Apr 30 12:07:48 2006 UTC (18 years ago) by pippijn
Content type: text/plain
Branch: MAIN
Changes since 1.2: +16 -0 lines
Log Message:
Adding code support for "flame touch"-alike skills for cold, electric,
acid and poison elemental races.

File Contents

# User Rev Content
1 root 1.1 /*
2     * static char *rcsid_skill_util_c =
3 pippijn 1.2 * "$Id$";
4 root 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     const char *skill_names[NUM_SKILLS];
56    
57     /* init_skills basically just sets up the skill_names table
58     * above. The index into the array is set up by the
59     * subtypes.
60     */
61     void init_skills(void) {
62     int i;
63     archetype *at;
64    
65     for (i=0; i<NUM_SKILLS; i++)
66     skill_names[i] = NULL;
67    
68     for(at = first_archetype;at!=NULL;at=at->next) {
69     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     at->clone.subtype, skill_names[at->clone.subtype], at->clone.skill);
73     } else {
74     skill_names[at->clone.subtype] = add_refcount(at->clone.skill);
75     }
76     }
77     }
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     if (!skill_names[i])
84     LOG(llevError, "init_skills: skill subtype %d doesn't have a name?\n",
85     i);
86     }
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     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     op->contr->last_skill_ob[tmp->subtype]->skill, tmp->skill);
105     }
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     }
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     if (tmp->type == SKILL && !strncasecmp(name, tmp->skill, strlen(name)) &&
136     strlen(tmp->skill) >= strlen(name)) skill = tmp;
137    
138     /* 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     }
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     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     }
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     if (tmp->type == SKILL && tmp->subtype == skillno) skill = tmp;
188    
189     /* 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     }
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     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     }
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     if (who->chosen_skill) apply_special(who, who->chosen_skill, AP_UNAPPLY);
249    
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     return 0;
255     }
256     if (flag & 0x1)
257     who->contr->shoottype = old_range;
258    
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     who->contr->ranges[range_skill] = NULL;
271     if (who->contr->shoottype == range_skill)
272     who->contr->shoottype = range_none;
273     }
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     int success=0, exp=0;
289     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     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     }
306    
307     switch(skill->subtype) {
308     case SK_LEVITATION:
309     /* Not 100% sure if this will work with new movement code -
310     * the levitation skill has move_type for flying, so when
311     * equipped, that should transfer to player, when not,
312     * shouldn't.
313     */
314     if(QUERY_FLAG(skill,FLAG_APPLIED)) {
315     CLEAR_FLAG(skill,FLAG_APPLIED);
316     new_draw_info(NDI_UNIQUE,0,op,"You come to earth.");
317     }
318     else {
319     SET_FLAG(skill,FLAG_APPLIED);
320     new_draw_info(NDI_UNIQUE,0,op,"You rise into the air!.");
321     }
322     fix_player(op);
323     success=1;
324     break;
325    
326     case SK_STEALING:
327     exp = success = steal(op, dir, skill);
328     break;
329    
330     case SK_LOCKPICKING:
331     exp = success = pick_lock(op, dir, skill);
332     break;
333    
334     case SK_HIDING:
335     exp = success = hide(op, skill);
336     break;
337    
338     case SK_JUMPING:
339     success = jump(op, dir, skill);
340     break;
341    
342     case SK_INSCRIPTION:
343     exp = success = write_on_item(op,string, skill);
344     break;
345    
346     case SK_MEDITATION:
347     meditate(op, skill);
348     success=1;
349     break;
350     /* note that the following 'attack' skills gain exp through hit_player() */
351    
352     case SK_KARATE:
353     (void) attack_hth(op,dir,"karate-chopped", skill);
354     break;
355    
356     case SK_PUNCHING:
357     (void) attack_hth(op,dir,"punched", skill);
358     break;
359    
360     case SK_FLAME_TOUCH:
361     (void) attack_hth(op,dir,"flamed", skill);
362     break;
363    
364 pippijn 1.3 case SK_SPARK_TOUCH:
365     (void) attack_hth(op,dir,"zapped", skill);
366     break;
367    
368     case SK_FROST_TOUCH:
369     (void) attack_hth(op,dir,"froze", skill);
370     break;
371    
372     case SK_ACID_TOUCH:
373     (void) attack_hth(op,dir,"dissolved", skill);
374     break;
375    
376     case SK_POISON_TOUCH:
377     (void) attack_hth(op,dir,"splashed with poison,", skill);
378     break;
379    
380 root 1.1 case SK_CLAWING:
381     (void) attack_hth(op,dir,"clawed", skill);
382     break;
383    
384     case SK_ONE_HANDED_WEAPON:
385     case SK_TWO_HANDED_WEAPON:
386     (void) attack_melee_weapon(op,dir,NULL, skill);
387     break;
388    
389     case SK_FIND_TRAPS:
390     exp = success = find_traps(op, skill);
391     break;
392    
393     case SK_SINGING:
394     exp = success = singing(op,dir, skill);
395     break;
396    
397     case SK_ORATORY:
398     exp = success = use_oratory(op,dir, skill);
399     break;
400    
401     case SK_SMITHERY:
402     case SK_BOWYER:
403     case SK_JEWELER:
404     case SK_ALCHEMY:
405     case SK_THAUMATURGY:
406     case SK_LITERACY:
407     case SK_WOODSMAN:
408     /* first, we try to find a cauldron, and do the alchemy thing.
409     * failing that, we go and identify stuff.
410     */
411     for (tmp=get_map_ob(op->map, op->x, op->y); tmp != NULL;tmp=next) {
412     next=tmp->above;
413     if(QUERY_FLAG(tmp, FLAG_IS_CAULDRON)) {
414     attempt_do_alchemy(op, tmp);
415     if (QUERY_FLAG(tmp, FLAG_APPLIED))
416     esrv_send_inventory(op, tmp);
417     did_alc=1;
418     }
419     }
420     if (did_alc == 0)
421     exp = success = skill_ident(op,skill);
422     break;
423    
424     case SK_DET_MAGIC:
425     case SK_DET_CURSE:
426     exp = success = skill_ident(op,skill);
427     break;
428    
429     case SK_DISARM_TRAPS:
430     exp = success = remove_trap(op,dir, skill);
431     break;
432    
433     case SK_THROWING:
434     success = skill_throw(op,part,dir,string, skill);
435     break;
436    
437     case SK_SET_TRAP:
438     new_draw_info(NDI_UNIQUE, 0,op,"This skill is not currently implemented.");
439     break;
440    
441     case SK_USE_MAGIC_ITEM:
442     case SK_MISSILE_WEAPON:
443     new_draw_info(NDI_UNIQUE, 0,op,"There is no special attack for this skill.");
444     break;
445    
446     case SK_PRAYING:
447     success = pray(op, skill);
448     break;
449    
450     case SK_BARGAINING:
451     success = describe_shop(op);
452     break;
453    
454     case SK_SORCERY:
455     case SK_EVOCATION:
456     case SK_PYROMANCY:
457     case SK_SUMMONING:
458     case SK_CLIMBING:
459     new_draw_info(NDI_UNIQUE, 0,op,"This skill is already in effect.");
460     break;
461    
462     default:
463     LOG(llevDebug,"%s attempted to use unknown skill: %d\n"
464     ,query_name(op), op->chosen_skill->stats.sp);
465     break;
466     }
467    
468     /* For players we now update the speed_left from using the skill.
469     * Monsters have no skill use time because of the random nature in
470     * which use_monster_skill is called already simulates this.
471     * If certain skills should take more/less time, that should be
472     * in the code for the skill itself.
473     */
474    
475     if(op->type==PLAYER) op->speed_left -= 1.0;
476    
477     /* this is a good place to add experience for successfull use of skills.
478     * Note that add_exp() will figure out player/monster experience
479     * gain problems.
480     */
481    
482     if(success && exp) change_exp(op,exp, skill->skill, 0);
483    
484     return success;
485     }
486    
487     /* calc_skill_exp() - calculates amount of experience can be gained for
488     * successfull use of a skill. Returns value of experience gain.
489     * Here we take the view that a player must 'overcome an opponent'
490     * in order to gain experience. Examples include foes killed combat,
491     * finding/disarming a trap, stealing from somebeing, etc.
492     * The gained experience is based primarily on the difference in levels,
493     * exp point value of vanquished foe, the relevent stats of the skill being
494     * used and modifications in the skills[] table.
495     *
496     * For now, monsters and players will be treated differently. Below I give
497     * the algorithm for *PLAYER* experience gain. Monster exp gain is simpler.
498     * Monsters just get 10% of the exp of the opponent.
499     *
500     * players get a ratio, eg, opponent lvl / player level. This is then
501     * multiplied by various things. If simple exp is true, then
502     * this multiplier, include the level difference, is always 1.
503     * This revised method prevents some cases where there are big gaps
504     * in the amount you get just because you are now equal level vs lower
505     * level
506     * who is player/creature that used the skill.
507     * op is the object that was 'defeated'.
508     * skill is the skill used. If no skill is used, it should just
509     * point back to who.
510     *
511     */
512    
513     int calc_skill_exp(object *who, object *op, object *skill) {
514     int op_exp=0,op_lvl= 0;
515     float base,value,lvl_mult=0.0;
516    
517     if (!skill) skill = who;
518    
519     /* Oct 95 - where we have an object, I expanded our treatment
520     * to 3 cases:
521     * non-living magic obj, runes and everything else.
522     *
523     * If an object is not alive and magical we set the base exp higher to
524     * help out exp awards for skill_ident skills. Also, if
525     * an item is type RUNE, we give out exp based on stats.Cha
526     * and level (this was the old system) -b.t.
527     */
528    
529     if(!op) { /* no item/creature */
530     op_lvl= who->map->difficulty < 1 ? 1: who->map->difficulty;
531     op_exp = 0;
532     } else if(op->type==RUNE || op->type==TRAP) { /* all traps. If stats.Cha > 1 we use that
533     * for the amount of experience */
534     op_exp = op->stats.Cha>1 ? op->stats.Cha : op->stats.exp;
535     op_lvl = op->level;
536     } else { /* all other items/living creatures */
537     op_exp = op->stats.exp;
538     op_lvl = op->level;
539     if(!QUERY_FLAG(op,FLAG_ALIVE)) { /* for ident/make items */
540     op_lvl += 5 * abs(op->magic);
541     }
542     }
543    
544     if(op_lvl<1) op_lvl = 1;
545    
546     if(who->type!=PLAYER) { /* for monsters only */
547     return ((int) (op_exp*0.1)+1); /* we add one to insure positive value is returned */
548     } else { /* for players */
549     base = op_exp;
550     /* if skill really is a skill, then we can look at the skill archetype for
551     * bse reward value (exp) and level multiplier factor.
552     */
553     if (skill->type == SKILL) {
554     base += skill->arch->clone.stats.exp;
555     if (settings.simple_exp) {
556     if (skill->arch->clone.level)
557     lvl_mult = (float) skill->arch->clone.level / 100.0;
558     else
559     lvl_mult = 1.0; /* no adjustment */
560     }
561     else {
562     if (skill->level)
563     lvl_mult = ((float) skill->arch->clone.level * (float) op_lvl) / ((float) skill->level * 100.0);
564     else
565     lvl_mult = 1.0;
566     }
567     } else {
568     /* Don't divide by zero here! */
569     lvl_mult = (float) op_lvl / (float) (skill->level?skill->level:1);
570     }
571     }
572    
573     /* assemble the exp total, and return value */
574    
575     value = base * lvl_mult;
576     if (value < 1) value=1; /* Always give at least 1 exp point */
577    
578     #ifdef SKILL_UTIL_DEBUG
579     LOG(llevDebug,"calc_skill_exp(): who: %s(lvl:%d) op:%s(lvl:%d)\n",
580     who->name,skill->level,op->name,op_lvl);
581     #endif
582     return ( (int) value);
583     }
584    
585     /* Learn skill. This inserts the requested skill in the player's
586     * inventory. The skill field of the scroll should have the
587     * exact name of the requested skill.
588     * This one actually teaches the player the skill as something
589     * they can equip.
590     * Return 0 if the player knows the skill, 1 if the
591     * player learns the skill, 2 otherwise.
592     */
593    
594     int
595     learn_skill (object *pl, object *scroll) {
596     object *tmp;
597    
598     if (!scroll->skill) {
599     LOG(llevError,"skill scroll %s does not have skill pointer set.\n", scroll->name);
600     return 2;
601     }
602    
603     /* can't use find_skill_by_name because we want skills the player knows
604     * but can't use natively.
605     */
606    
607     for (tmp=pl->inv; tmp!=NULL; tmp=tmp->below)
608     if (tmp->type == SKILL && !strncasecmp(scroll->skill, tmp->skill, strlen(scroll->skill))) break;
609    
610     /* player already knows it */
611     if (tmp && QUERY_FLAG(tmp, FLAG_CAN_USE_SKILL)) return 0;
612    
613    
614    
615     /* now a random change to learn, based on player Int.
616     * give bonus based on level - otherwise stupid characters
617     * might never be able to learn anything.
618     */
619     if(random_roll(0, 99, pl, PREFER_LOW)>(learn_spell[pl->stats.Int] + (pl->level/5)))
620     return 2; /* failure :< */
621    
622     if (!tmp)
623     tmp = give_skill_by_name(pl, scroll->skill);
624    
625     if (!tmp) {
626     LOG(llevError,"skill scroll %s does not have valid skill name (%s).\n", scroll->name, scroll->skill);
627     return 2;
628     }
629    
630     SET_FLAG(tmp, FLAG_CAN_USE_SKILL);
631     link_player_skills(pl);
632     return 1;
633     }
634    
635     /* Gives a percentage clipped to 0% -> 100% of a/b. */
636     /* Probably belongs in some global utils-type file? */
637     static int clipped_percent(sint64 a, sint64 b)
638     {
639     int rv;
640    
641     if (b <= 0)
642     return 0;
643    
644     rv = (int)((100.0f * ((float)a) / ((float)b) ) + 0.5f);
645    
646     if (rv < 0)
647     return 0;
648     else if (rv > 100)
649     return 100;
650    
651     return rv;
652     }
653    
654     /* show_skills() - Meant to allow players to examine
655     * their current skill list.
656     * This shows the amount of exp they have in the skills.
657     * we also include some other non skill related info (god,
658     * max weapon improvments, item power).
659     * Note this function is a bit more complicated becauase we
660     * we want ot sort the skills before printing them. If we
661     * just dumped this as we found it, this would be a bit
662     * simpler.
663     */
664    
665     void show_skills(object *op, const char* search) {
666     object *tmp=NULL;
667     char buf[MAX_BUF];
668     const char *cp;
669     int i,num_skills_found=0;
670 pippijn 1.2 static const char *const periods = "........................................";
671 root 1.1 /* Need to have a pointer and use strdup for qsort to work properly */
672     char skills[NUM_SKILLS][MAX_BUF];
673    
674    
675     for (tmp=op->inv; tmp!=NULL; tmp=tmp->below) {
676 pippijn 1.2 if (tmp->type == SKILL) {
677 root 1.1 if ( search && strstr(tmp->name,search)==NULL ) continue;
678     /* Basically want to fill this out to 40 spaces with periods */
679     sprintf(buf,"%s%s", tmp->name, periods);
680     buf[40] = 0;
681    
682 pippijn 1.2 if (settings.permanent_exp_ratio) {
683 root 1.1 #ifdef WIN32
684     sprintf(skills[num_skills_found++],"%slvl:%3d (xp:%I64d/%I64d/%d%%)",
685     buf,tmp->level,
686     tmp->stats.exp,
687     level_exp(tmp->level+1, op->expmul),
688     clipped_percent(tmp->perm_exp,tmp->stats.exp));
689     #else
690     sprintf(skills[num_skills_found++],"%slvl:%3d (xp:%lld/%lld/%d%%)",
691     buf,tmp->level,
692     tmp->stats.exp,
693     level_exp(tmp->level+1, op->expmul),
694     clipped_percent(tmp->perm_exp,tmp->stats.exp));
695     #endif
696     } else {
697     #ifdef WIN32
698     sprintf(skills[num_skills_found++], "%slvl:%3d (xp:%I64d/%I64d)",
699     buf,tmp->level,
700     tmp->stats.exp,
701     level_exp(tmp->level+1, op->expmul));
702     #else
703     sprintf(skills[num_skills_found++], "%slvl:%3d (xp:%lld/%lld)",
704     buf,tmp->level,
705     tmp->stats.exp,
706     level_exp(tmp->level+1, op->expmul));
707     #endif
708     }
709     /* I don't know why some characters get a bunch of skills, but
710     * it sometimes happens (maybe a leftover from bugier earlier code
711     * and those character are still about). In any case, lets handle
712     * it so it doesn't crash the server - otherwise, one character may
713     * crash the server numerous times.
714     */
715     if (num_skills_found >= NUM_SKILLS) {
716     new_draw_info(NDI_RED, 0, op, "Your character has too many skills.");
717     new_draw_info(NDI_RED, 0, op, "Something isn't right - contact the server admin");
718     break;
719     }
720     }
721     }
722    
723     clear_win_info(op);
724     new_draw_info(NDI_UNIQUE, 0,op,"Player skills:");
725     if (num_skills_found > 1) qsort(skills, num_skills_found, MAX_BUF, (int (*)(const void*, const void*))strcmp);
726    
727     for (i=0; i<num_skills_found; i++) {
728     new_draw_info(NDI_UNIQUE, 0, op, skills[i]);
729     }
730    
731     new_draw_info_format(NDI_UNIQUE, 0, op,
732     "You can handle %d weapon improvements.",op->level/5+5);
733    
734     cp = determine_god(op);
735     new_draw_info_format(NDI_UNIQUE, 0, op,
736     "You worship %s.", cp?cp:"no god at current time");
737    
738     new_draw_info_format(NDI_UNIQUE,0,op, "Your equipped item power is %d out of %d\n",
739 pippijn 1.2 op->contr->item_power, (int) (op->level * settings.item_power_factor));
740 root 1.1 }
741    
742     /* use_skill() - similar to invoke command, it executes the skill in the
743     * direction that the user is facing. Returns false if we are unable to
744     * change to the requested skill, or were unable to use the skill properly.
745     * This is tricky because skills can have spaces. We basically roll
746     * our own find_skill_by_name so we can try to do better string matching.
747     */
748    
749     int use_skill(object *op, const char *string) {
750     object *skop;
751     size_t len;
752    
753     if (!string) return 0;
754    
755     for (skop = op->inv; skop != NULL; skop=skop->below) {
756     if (skop->type == SKILL && QUERY_FLAG(skop, FLAG_CAN_USE_SKILL) &&
757     !strncasecmp(string, skop->skill, MIN(strlen(string), strlen(skop->skill))))
758     break;
759     else if (skop->type == SKILL_TOOL &&
760     !strncasecmp(string, skop->skill, MIN(strlen(string), strlen(skop->skill))))
761     break;
762     }
763     if (!skop) {
764     new_draw_info_format(NDI_UNIQUE, 0, op,
765     "Unable to find skill %s", string);
766     return 0;
767     }
768    
769     len=strlen(skop->skill);
770    
771     /* All this logic goes and skips over the skill name to find any
772     * options given to the skill. Its pretty simple - if there
773     * are extra parameters (as deteremined by string length), we
774     * want to skip over any leading spaces.
775     */
776     if(len>=strlen(string)) {
777     string=NULL;
778     } else {
779     string += len;
780     while(*string==0x20) string++;
781     if(strlen(string)==0) string = NULL;
782     }
783    
784     #ifdef SKILL_UTIL_DEBUG
785     LOG(llevDebug,"use_skill() got skill: %s\n",sknum>-1?skills[sknum].name:"none");
786     #endif
787    
788     /* Change to the new skill, then execute it. */
789     if(do_skill(op,op,skop, op->facing,string)) return 1;
790    
791     return 0;
792     }
793    
794    
795    
796     /* This finds the best unarmed skill the player has, and returns
797     * it. Best can vary a little - we consider clawing to always
798     * be the best for dragons.
799     * This could be more intelligent, eg, look at the skill level
800     * of the skill and go from there (eg, a level 40 puncher is
801     * is probably better than level 1 karate). OTOH, if you
802     * don't bother to set up your skill properly, that is the players
803     * problem (although, it might be nice to have a preferred skill
804     * field the player can set.
805     * Unlike the old code, we don't give out any skills - it is
806     * possible you just don't have any ability to get into unarmed
807     * combat. If everyone race/class should have one, this should
808     * be handled in the starting treasurelists, not in the code.
809     */
810     static object *find_best_player_hth_skill(object *op)
811     {
812     object *tmp, *best_skill=NULL;
813     int dragon = is_dragon_pl(op), last_skill=sizeof(unarmed_skills), i;
814    
815     for (tmp=op->inv; tmp; tmp=tmp->below) {
816     if (tmp->type == SKILL) {
817     if (dragon && tmp->subtype == SK_CLAWING) return tmp;
818    
819     /* The order in the array is preferred order. So basically,
820     * we just cut down the number to search - eg, if we find a skill
821     * early on in flame touch, then we only need to look into the unarmed_array
822     * to the entry before flame touch - don't care about the entries afterward,
823     * because they are infrerior skills.
824     * if we end up finding the best skill (i==0) might as well return
825     * right away - can't get any better than that.
826     */
827     for (i=0; i<last_skill; i++) {
828     if (tmp->subtype == unarmed_skills[i] && QUERY_FLAG(tmp, FLAG_CAN_USE_SKILL)) {
829     best_skill = tmp;
830     last_skill = i;
831     if (i==0) return best_skill;
832     }
833     }
834     }
835     }
836     return best_skill;
837     }
838    
839     /* do_skill_attack() - We have got an appropriate opponent from either
840     * move_player_attack() or skill_attack(). In this part we get on with
841     * attacking, take care of messages from the attack and changes in invisible.
842     * Returns true if the attack damaged the opponent.
843     * tmp is the targetted monster.
844     * op is what is attacking
845     * string is passed along to describe what messages to describe
846     * the damage.
847     */
848    
849     static int do_skill_attack(object *tmp, object *op, const char *string, object *skill) {
850     int success;
851    
852     /* For Players only: if there is no ready weapon, and no "attack" skill
853     * is readied either then try to find a skill for the player to use.
854     * it is presumed that if skill is set, it is a valid attack skill (eg,
855     * the caller should have set it appropriately). We still want to pass
856     * through that code if skill is set to change to the skill.
857     */
858     if(op->type==PLAYER) {
859     if (!QUERY_FLAG(op,FLAG_READY_WEAPON)) {
860     size_t i;
861    
862     if (!skill) {
863     /* See if the players chosen skill is a combat skill, and use
864     * it if appropriate.
865     */
866     if (op->chosen_skill) {
867     for (i=0; i<sizeof(unarmed_skills); i++)
868     if (op->chosen_skill->subtype == unarmed_skills[i]) {
869     skill = op->chosen_skill;
870     break;
871     }
872     }
873     /* If we didn't find a skill above, look harder for a good skill */
874     if (!skill) {
875     skill = find_best_player_hth_skill(op);
876    
877     if (!skill) {
878     new_draw_info(NDI_BLACK, 0, op, "You have no unarmed combat skills!");
879     return 0;
880     }
881     }
882     }
883     if (skill != op->chosen_skill) {
884     /* now try to ready the new skill */
885     if(!change_skill(op,skill,0)) { /* oh oh, trouble! */
886     new_draw_info_format(NDI_UNIQUE, 0, tmp, "Couldn't change to skill %s", skill->name);
887     return 0;
888     }
889     }
890     } else {
891     /* Seen some crashes below where current_weapon is not set,
892     * even though the flag says it is. So if current weapon isn't set,
893     * do some work in trying to find the object to use.
894     */
895     if (!op->current_weapon) {
896     object *tmp;
897    
898     LOG(llevError,"Player %s does not have current weapon set but flag_ready_weapon is set\n",
899     op->name);
900     for (tmp=op->inv; tmp; tmp=tmp->below)
901     if (tmp->type == WEAPON && QUERY_FLAG(tmp, FLAG_APPLIED)) break;
902    
903     if (op->current_weapon_script)
904     FREE_AND_CLEAR_STR(op->current_weapon_script);
905     if (!tmp) {
906     LOG(llevError,"Could not find applied weapon on %s\n",
907     op->name);
908     op->current_weapon=NULL;
909     return 0;
910     } else {
911     op->current_weapon = tmp;
912     op->current_weapon_script=add_string(query_name(tmp));
913     }
914     }
915    
916     /* Has ready weapon - make sure chosen_skill is set up properly */
917     if (!op->chosen_skill || op->current_weapon->skill != op->chosen_skill->skill) {
918     change_skill(op, find_skill_by_name(op, op->current_weapon->skill), 1);
919     }
920     }
921     }
922    
923     /* lose invisiblity/hiding status for running attacks */
924    
925     if(op->type==PLAYER && op->contr->tmp_invis) {
926     op->contr->tmp_invis=0;
927     op->invisible=0;
928     op->hide=0;
929     update_object(op,UP_OBJ_FACE);
930     }
931    
932     success = attack_ob(tmp,op);
933    
934     /* print appropriate messages to the player */
935    
936     if(success && string!=NULL && tmp && !QUERY_FLAG(tmp,FLAG_FREED)) {
937     if(op->type==PLAYER)
938     new_draw_info_format(NDI_UNIQUE, 0,op,
939     "You %s %s!",string,query_name(tmp));
940     else if(tmp->type==PLAYER)
941     new_draw_info_format(NDI_UNIQUE, 0,tmp,
942     "%s %s you!",query_name(op),string);
943     }
944     return success;
945     }
946    
947    
948     /* skill_attack() - Core routine for use when we attack using a skills
949     * system. In essence, this code handles
950     * all skill-based attacks, ie hth, missile and melee weapons should be
951     * treated here. If an opponent is already supplied by move_player(),
952     * we move right onto do_skill_attack(), otherwise we find if an
953     * appropriate opponent exists.
954     *
955     * This is called by move_player() and attack_hth()
956     *
957     * Initial implementation by -bt thomas@astro.psu.edu
958     */
959    
960     int skill_attack (object *tmp, object *pl, int dir, const char *string, object *skill) {
961     sint16 tx,ty;
962     mapstruct *m;
963     int mflags;
964    
965     if(!dir) dir=pl->facing;
966     tx=freearr_x[dir];
967     ty=freearr_y[dir];
968    
969     /* If we don't yet have an opponent, find if one exists, and attack.
970     * Legal opponents are the same as outlined in move_player_attack()
971     */
972    
973     if(tmp==NULL) {
974     m = pl->map;
975     tx = pl->x + freearr_x[dir];
976     ty = pl->y + freearr_y[dir];
977    
978     mflags = get_map_flags(m, &m, tx, ty, &tx, &ty);
979     if (mflags & P_OUT_OF_MAP) return 0;
980    
981     /* space must be blocked for there to be anything interesting to do */
982     if (!OB_TYPE_MOVE_BLOCK(pl, GET_MAP_MOVE_BLOCK(m, tx,ty))) return 0;
983    
984     for(tmp=get_map_ob(m, tx, ty); tmp; tmp=tmp->above)
985     if((QUERY_FLAG(tmp,FLAG_ALIVE) && tmp->stats.hp>=0)
986     || QUERY_FLAG(tmp, FLAG_CAN_ROLL)
987     || tmp->type==LOCKED_DOOR ) {
988     /* Don't attack party members */
989     if((pl->type==PLAYER && tmp->type==PLAYER) && (pl->contr->party!=NULL
990     && pl->contr->party==tmp->contr->party))
991     return 0;
992     break;
993     }
994     }
995     if (!tmp) {
996     if(pl->type==PLAYER)
997     new_draw_info(NDI_UNIQUE, 0,pl,"There is nothing to attack!");
998     return 0;
999     }
1000    
1001     return do_skill_attack(tmp,pl,string, skill);
1002     }
1003    
1004    
1005     /* attack_hth() - this handles all hand-to-hand attacks -b.t. */
1006     /* July 5, 1995 - I broke up attack_hth() into 2 parts. In the first
1007     * (attack_hth) we check for weapon use, etc in the second (the new
1008     * function skill_attack() we actually attack.
1009     */
1010    
1011     int attack_hth(object *pl, int dir, const char *string, object *skill) {
1012     object *enemy=NULL,*weapon;
1013    
1014     if(QUERY_FLAG(pl, FLAG_READY_WEAPON))
1015     for(weapon=pl->inv;weapon;weapon=weapon->below) {
1016     if (weapon->type==WEAPON && QUERY_FLAG(weapon, FLAG_APPLIED)) {
1017     CLEAR_FLAG(weapon,FLAG_APPLIED);
1018     CLEAR_FLAG(pl,FLAG_READY_WEAPON);
1019     fix_player(pl);
1020     if(pl->type==PLAYER) {
1021     new_draw_info(NDI_UNIQUE, 0,pl,"You unwield your weapon in order to attack.");
1022     esrv_update_item(UPD_FLAGS, pl, weapon);
1023     }
1024     break;
1025     }
1026     }
1027     return skill_attack(enemy,pl,dir,string, skill);
1028     }
1029    
1030    
1031     /* attack_melee_weapon() - this handles melee weapon attacks -b.t.
1032     * For now we are just checking to see if we have a ready weapon here.
1033     * But there is a real neato possible feature of this scheme which
1034     * bears mentioning:
1035     * Since we are only calling this from do_skill() in the future
1036     * we may make this routine handle 'special' melee weapons attacks
1037     * (like disarming manuever with sai) based on player SK_level and
1038     * weapon type.
1039     */
1040    
1041     int attack_melee_weapon(object *op, int dir, const char *string, object *skill) {
1042    
1043     if(!QUERY_FLAG(op, FLAG_READY_WEAPON)) {
1044     if(op->type==PLAYER)
1045     new_draw_info(NDI_UNIQUE, 0,op,"You have no ready weapon to attack with!");
1046     return 0;
1047     }
1048     return skill_attack(NULL,op,dir,string, skill);
1049    
1050     }