ViewVC Help
View File | Revision Log | Show Annotations | Download File
/cvs/deliantra/server/server/skill_util.C
Revision: 1.29
Committed: Wed Mar 14 04:12:29 2007 UTC (17 years, 3 months ago) by root
Content type: text/plain
Branch: MAIN
Changes since 1.28: +1 -1 lines
Log Message:
- rewrote more face handling code
- automatically send smooth faces, as the client will need them anyways
  and it makes little sense to wait for the client to axk for it. of course,
  gcfclient suffers from weird ordering problems again.
- UP_OBJ_FACE was often abused in situations where other things changed,
  updated lots of spaces, probably more to be done.
- update_smooth became so small that inlining it actually clarified
  the code. similar for update_space, which is not inlined for other reasons.
- faces were not initialised properly
- add versioncheck for face data
- rewrite invisibility handling a bit: god finger etc. now makes you blink,
  blinking routine has changed to be less annoying and more useful while
  still indicating invisibleness.

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