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

# Content
1 /*
2 * 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
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
41 /* #define SKILL_UTIL_DEBUG */
42
43 #include <global.h>
44 #include <object.h>
45 #include <sproto.h>
46 #include <living.h> /* for defs of STR,CON,DEX,etc. -b.t. */
47 #include <spells.h>
48
49 const uint8_t skill_flags[NUM_SKILLS] = {
50 # define def(uc, flags) flags,
51 # include "skillinc.h"
52 # undef def
53 };
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 /* 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 void
63 init_skills (void)
64 {
65 for (archetype *at = first_archetype; at; at = at->next)
66 if (at->clone.type == SKILL)
67 {
68 if (skill_names[at->clone.subtype])
69 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
75 /* 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 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 }
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 void
89 link_player_skills (object *op)
90 {
91 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 }
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 object *
117 find_skill_by_name (object *who, const char *name)
118 {
119 object *skill = NULL, *skill_tool = NULL, *tmp;
120
121 if (!name)
122 return NULL;
123
124 /* 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 }
143 }
144 /* 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 }
159
160 if (!skill)
161 {
162 skill = give_skill_by_name (who, skill_tool->skill);
163 link_player_skills (who);
164 }
165
166 return skill;
167 }
168
169 return NULL;
170 }
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 object *
185 find_skill_by_number (object *who, int skillno)
186 {
187 object *skill = NULL, *skill_tool = NULL, *tmp;
188
189 if (skillno < 1 || skillno >= NUM_SKILLS)
190 return NULL;
191
192 for (tmp = who->inv; tmp != NULL; tmp = tmp->below)
193 {
194 if (tmp->type == SKILL && tmp->subtype == skillno)
195 skill = tmp;
196
197 /* 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 }
207 }
208 /* 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 }
223
224 if (!skill)
225 {
226 skill = give_skill_by_name (who, skill_tool->skill);
227 link_player_skills (who);
228 }
229
230 return skill;
231 }
232
233 return NULL;
234 }
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 int
248 change_skill (object *who, object *new_skill, int flag)
249 {
250 int old_range;
251
252 if (who->type != PLAYER)
253 return 0;
254
255 if (who->chosen_skill && who->chosen_skill == new_skill)
256 {
257 //who->contr->combat_skill = who->chosen_skill;
258 return 1;
259 }
260
261 if (!new_skill)
262 {
263 if (who->chosen_skill)
264 apply_special (who, who->chosen_skill, AP_UNAPPLY);
265
266 /* Only goal in this case was to unapply a skill */
267 return 0;
268 }
269
270 // move skill to front, so it will be preferred next time
271 new_skill->remove ();
272 who->insert (new_skill);
273
274 if (apply_special (who, new_skill, AP_APPLY))
275 return 0;
276
277 return 1;
278 }
279
280 /* This function just clears the chosen_skill and range_skill values
281 * in the player.
282 */
283 void
284 clear_skill (object *who)
285 {
286 who->chosen_skill = 0;
287 CLEAR_FLAG (who, FLAG_READY_SKILL);
288
289 if (who->type == PLAYER)
290 {
291 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 }
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 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 if (tmp->type == SKILL && tmp->skill == skill->skill)
328 break;
329
330 if (!tmp)
331 tmp = give_skill_by_name (op, skill->skill);
332
333 skill = tmp;
334 }
335
336 // 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
340 switch (skill->subtype)
341 {
342 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
363 case SK_STEALING:
364 exp = success = steal (op, dir, skill);
365 break;
366
367 case SK_LOCKPICKING:
368 exp = success = pick_lock (op, dir, skill);
369 break;
370
371 case SK_HIDING:
372 exp = success = hide (op, skill);
373 break;
374
375 case SK_JUMPING:
376 success = jump (op, dir, skill);
377 break;
378
379 case SK_INSCRIPTION:
380 exp = success = write_on_item (op, string, skill);
381 break;
382
383 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
389 case SK_KARATE:
390 attack_hth (op, dir, "karate-chopped", skill);
391 break;
392
393 case SK_PUNCHING:
394 attack_hth (op, dir, "punched", skill);
395 break;
396
397 case SK_FLAME_TOUCH:
398 attack_hth (op, dir, "flamed", skill);
399 break;
400
401 case SK_SPARK_TOUCH:
402 attack_hth (op, dir, "zapped", skill);
403 break;
404
405 case SK_SHIVER:
406 attack_hth (op, dir, "froze", skill);
407 break;
408
409 case SK_ACID_SPLASH:
410 attack_hth (op, dir, "dissolved", skill);
411 break;
412
413 case SK_POISON_NAIL:
414 attack_hth (op, dir, "injected poison into", skill);
415 break;
416
417 case SK_CLAWING:
418 attack_hth (op, dir, "clawed", skill);
419 break;
420
421 case SK_ONE_HANDED_WEAPON:
422 case SK_TWO_HANDED_WEAPON:
423 attack_melee_weapon (op, dir, NULL, skill);
424 break;
425
426 case SK_FIND_TRAPS:
427 exp = success = find_traps (op, skill);
428 break;
429
430 case SK_SINGING:
431 exp = success = singing (op, dir, skill);
432 break;
433
434 case SK_ORATORY:
435 exp = success = use_oratory (op, dir, skill);
436 break;
437
438 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
452 if (QUERY_FLAG (tmp, FLAG_IS_CAULDRON))
453 {
454 attempt_do_alchemy (op, tmp);
455
456 if (QUERY_FLAG (tmp, FLAG_APPLIED))
457 esrv_send_inventory (op, tmp);
458
459 did_alc = 1;
460 }
461 }
462
463 if (did_alc == 0)
464 exp = success = skill_ident (op, skill);
465
466 break;
467
468 case SK_DET_MAGIC:
469 case SK_DET_CURSE:
470 exp = success = skill_ident (op, skill);
471 break;
472
473 case SK_DISARM_TRAPS:
474 exp = success = remove_trap (op, dir, skill);
475 break;
476
477 case SK_THROWING:
478 success = skill_throw (op, part, dir, string, skill);
479 break;
480
481 case SK_SET_TRAP:
482 new_draw_info (NDI_UNIQUE, 0, op, "This skill is not currently implemented.");
483 break;
484
485 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
490 case SK_PRAYING:
491 success = pray (op, skill);
492 break;
493
494 case SK_BARGAINING:
495 success = describe_shop (op);
496 break;
497
498 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
506 default:
507 LOG (llevDebug, "%s attempted to use unknown skill: %d\n", query_name (op), op->chosen_skill->stats.sp);
508 break;
509 }
510
511 /* 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 }
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 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 * for the amount of experience */
584 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 }
620 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 }
627 }
628 else
629 {
630 /* Don't divide by zero here! */
631 lvl_mult = (float) op_lvl / (float) (skill->level ? skill->level : 1);
632 }
633 }
634
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 #ifdef SKILL_UTIL_DEBUG
642 LOG (llevDebug, "calc_skill_exp(): who: %s(lvl:%d) op:%s(lvl:%d)\n", who->name, skill->level, op->name, op_lvl);
643 #endif
644 return ((int) value);
645 }
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 learn_skill (object *pl, object *scroll)
657 {
658 object *tmp;
659
660 if (!scroll->skill)
661 {
662 LOG (llevError, "skill scroll %s does not have skill pointer set.\n", &scroll->name);
663 return 2;
664 }
665
666 /* can't use find_skill_by_name because we want skills the player knows
667 * but can't use natively.
668 */
669
670 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
674 /* player already knows it */
675 if (tmp && QUERY_FLAG (tmp, FLAG_CAN_USE_SKILL))
676 return 0;
677
678 /* 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
685 if (!tmp)
686 tmp = give_skill_by_name (pl, scroll->skill);
687
688 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 }
693
694 SET_FLAG (tmp, FLAG_CAN_USE_SKILL);
695 link_player_skills (pl);
696 return 1;
697 }
698
699 /* Gives a percentage clipped to 0% -> 100% of a/b. */
700
701 /* Probably belongs in some global utils-type file? */
702 static int
703 clipped_percent (sint64 a, sint64 b)
704 {
705 int rv;
706
707 if (b <= 0)
708 return 0;
709
710 rv = (int) ((100.0f * ((float) a) / ((float) b)) + 0.5f);
711
712 if (rv < 0)
713 return 0;
714 else if (rv > 100)
715 return 100;
716
717 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
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 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 }
760 else
761 {
762 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 }
765 /* 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 }
777 }
778 }
779
780 clear_win_info (op);
781 new_draw_info (NDI_UNIQUE, 0, op, "Player skills:");
782 if (num_skills_found > 1)
783 qsort (skills, num_skills_found, MAX_BUF, (int (*)(const void *, const void *)) std::strcmp);
784
785 for (i = 0; i < num_skills_found; i++)
786 new_draw_info (NDI_UNIQUE, 0, op, skills[i]);
787
788 new_draw_info_format (NDI_UNIQUE, 0, op, "You can handle %d weapon improvements.", op->level / 5 + 5);
789
790 cp = determine_god (op);
791 new_draw_info_format (NDI_UNIQUE, 0, op, "You worship %s.", cp ? cp : "no god at current time");
792
793 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 }
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 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 }
821 if (!skop)
822 {
823 new_draw_info_format (NDI_UNIQUE, 0, op, "Unable to find skill %s", string);
824 return 0;
825 }
826
827 len = strlen (skop->skill);
828
829 /* 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 }
838 else
839 {
840 string += len;
841 while (*string == 0x20)
842 string++;
843 if (strlen (string) == 0)
844 string = NULL;
845 }
846
847 #ifdef SKILL_UTIL_DEBUG
848 LOG (llevDebug, "use_skill() got skill: %s\n", sknum > -1 ? skills[sknum].name : "none");
849 #endif
850
851 /* 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 }
857
858 static bool
859 hth_skill_p (object *skill)
860 {
861 return (skill_flags [skill->subtype] & (SF_COMBAT | SF_NEED_WEAPON)) == SF_COMBAT;
862 }
863
864 /* This finds the first unarmed skill the player has, and returns it.
865 */
866 static object *
867 find_player_hth_skill (object *op)
868 {
869 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
873 return 0;
874 }
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 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 if (op->chosen_skill && hth_skill_p (op->chosen_skill))
907 skill = op->chosen_skill;
908 else
909 {
910 skill = find_player_hth_skill (op);
911
912 if (!skill)
913 {
914 new_draw_info (NDI_BLACK, 0, op, "You have no unarmed combat skills!");
915 return 0;
916 }
917 }
918 }
919
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 }
926 }
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 op->current_weapon = tmp;
950 }
951
952 change_skill (op, find_skill_by_name (op, op->current_weapon->skill), 1);
953 }
954 }
955
956 /* 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 update_object (op, UP_OBJ_CHANGE);
964 }
965
966 int success = attack_ob (tmp, op);
967
968 /* print appropriate messages to the player */
969
970 if (success && string && tmp && !QUERY_FLAG (tmp, FLAG_FREED))
971 {
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
978 return success;
979 }
980
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 int
993 skill_attack (object *tmp, object *pl, int dir, const char *string, object *skill)
994 {
995 sint16 tx, ty;
996 maptile *m;
997 int mflags;
998
999 if (!dir)
1000 dir = pl->facing;
1001
1002 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 if (!tmp)
1009 {
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 for (tmp = GET_MAP_OB (m, tx, ty); tmp; tmp = tmp->above)
1023 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
1029 break;
1030 }
1031 }
1032
1033 if (!tmp)
1034 {
1035 if (pl->type == PLAYER)
1036 new_draw_info (NDI_UNIQUE, 0, pl, "There is nothing to attack!");
1037
1038 return 0;
1039 }
1040
1041 return do_skill_attack (tmp, pl, string, skill);
1042 }
1043
1044
1045 /* attack_hth() - this handles all hand-to-hand attacks -b.t. */
1046
1047 /* 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 static int
1053 attack_hth (object *pl, int dir, const char *string, object *skill)
1054 {
1055 object *enemy = NULL, *weapon;
1056
1057 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 pl->update_stats ();
1065 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
1074 return skill_attack (enemy, pl, dir, string, skill);
1075 }
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 static int
1087 attack_melee_weapon (object *op, int dir, const char *string, object *skill)
1088 {
1089
1090 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
1095 return 0;
1096 }
1097
1098 return skill_attack (NULL, op, dir, string, skill);
1099
1100 }