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

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

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

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

File Contents

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