1 | /* |
1 | /* |
2 | * This file is part of Deliantra, the Roguelike Realtime MMORPG. |
2 | * This file is part of Deliantra, the Roguelike Realtime MMORPG. |
3 | * |
3 | * |
4 | * Copyright (©) 2005,2006,2007,2008,2009,2010 Marc Alexander Lehmann / Robin Redeker / the Deliantra team |
4 | * Copyright (©) 2005,2006,2007,2008,2009,2010,2011 Marc Alexander Lehmann / Robin Redeker / the Deliantra team |
5 | * Copyright (©) 2002 Mark Wedel & Crossfire Development Team |
5 | * Copyright (©) 2002 Mark Wedel & Crossfire Development Team |
6 | * Copyright (©) 1992 Frank Tore Johansen |
6 | * Copyright (©) 1992 Frank Tore Johansen |
7 | * |
7 | * |
8 | * Deliantra is free software: you can redistribute it and/or modify it under |
8 | * Deliantra is free software: you can redistribute it and/or modify it under |
9 | * the terms of the Affero GNU General Public License as published by the |
9 | * the terms of the Affero GNU General Public License as published by the |
… | |
… | |
51 | # define def(uc, flags) flags, |
51 | # define def(uc, flags) flags, |
52 | # include "skillinc.h" |
52 | # include "skillinc.h" |
53 | # undef def |
53 | # undef def |
54 | }; |
54 | }; |
55 | |
55 | |
|
|
56 | vector<object_ptr> skillvec; |
|
|
57 | |
56 | static int attack_hth (object *pl, int dir, const char *string, object *skill); |
58 | static int attack_hth (object *pl, int dir, const char *string, object *skill); |
57 | static int attack_melee_weapon (object *op, int dir, const char *string, object *skill); |
59 | static int attack_melee_weapon (object *op, int dir, const char *string, object *skill); |
58 | |
60 | |
59 | /* init_skills basically just sets up the skill_names table |
61 | /* init_skills basically just sets up the skill_names table |
60 | * above. The index into the array is set up by the |
62 | * above. The index into the array is set up by the |
61 | * subtypes. |
63 | * subtypes. |
62 | */ |
64 | */ |
63 | void |
65 | void |
64 | init_skills () |
66 | init_skills () |
65 | { |
67 | { |
66 | for_all_archetypes (at) |
68 | // nop |
67 | if (at->type == SKILL) |
69 | } |
|
|
70 | |
|
|
71 | void |
|
|
72 | add_skill_archetype (object *o) |
|
|
73 | { |
|
|
74 | assert (("skill name must equal skill skill", o->name == o->skill)); |
|
|
75 | |
|
|
76 | for (vector<object_ptr>::iterator i = skillvec.begin (); i != skillvec.end (); ++i) |
|
|
77 | if ((*i)->name == o->name) |
68 | { |
78 | { |
69 | if (skill_names[at->subtype]) |
79 | // replace existing entry |
70 | LOG (llevError, "init_skills: multiple skill using same subtype %d, %s, %s\n", |
80 | SKILL_INDEX (o) = i - skillvec.begin (); |
71 | at->subtype, &skill_names[at->subtype], &at->skill); |
81 | *i = o; |
72 | else |
82 | return; |
73 | skill_names[at->subtype] = at->skill; |
|
|
74 | } |
83 | } |
75 | |
84 | |
76 | /* This isn't really an error if there is no skill subtype set, but |
85 | // add new entry |
77 | * checking for this may catch some user errors. |
86 | assert (("only CS_NUM_SKILLS skills supported by client protocol", skillvec.size () < CS_NUM_SKILLS)); |
78 | */ |
87 | SKILL_INDEX (o) = skillvec.size (); |
79 | for (int i = 1; i < NUM_SKILLS; i++) |
88 | skillvec.push_back (o); |
80 | if (!skill_names[i]) |
|
|
81 | LOG (llevError, "init_skills: skill subtype %d doesn't have a name?\n", i); |
|
|
82 | } |
89 | } |
83 | |
90 | |
84 | /* This function goes through the player inventory and sets |
91 | /* This function goes through the player inventory and sets |
85 | * up the last_skills[] array in the player object. |
92 | * up the last_skills[] array in the player object. |
86 | * the last_skills[] is used to more quickly lookup skills - |
93 | * the last_skills[] is used to more quickly lookup skills - |
87 | * mostly used for sending exp. |
94 | * mostly used for sending exp. |
88 | */ |
95 | */ |
89 | void |
96 | void |
90 | player::link_skills () |
97 | player::link_skills () |
91 | { |
98 | { |
92 | for (int i = 0; i < NUM_SKILLS; ++i) |
99 | for (int i = 0; i < CS_NUM_SKILLS; ++i) |
93 | last_skill_ob [i] = 0; |
100 | last_skill_ob [i] = 0; |
94 | |
101 | |
95 | for (object *tmp = ob->inv; tmp; tmp = tmp->below) |
102 | for (object *tmp = ob->inv; tmp; tmp = tmp->below) |
96 | if (tmp->type == SKILL) |
103 | if (tmp->type == SKILL) |
97 | { |
104 | { |
|
|
105 | int idx = SKILL_INDEX (tmp); |
|
|
106 | |
98 | assert (IN_RANGE_EXC (tmp->subtype, 0, NUM_SKILLS)); |
107 | assert (IN_RANGE_EXC (idx, 0, CS_NUM_SKILLS)); |
99 | |
108 | |
100 | if (last_skill_ob [tmp->subtype] != tmp) |
109 | if (last_skill_ob [idx] != tmp) |
101 | { |
110 | { |
102 | /* This is really a warning, hence no else below */ |
|
|
103 | if (last_skill_ob [tmp->subtype]) |
|
|
104 | LOG (llevError, "Multiple skills with the same subtype? %s, %s\n", |
|
|
105 | &last_skill_ob [tmp->subtype]->skill, &tmp->skill); |
|
|
106 | |
|
|
107 | last_skill_ob [tmp->subtype] = tmp; |
111 | last_skill_ob [idx] = tmp; |
108 | if (ns) |
112 | if (ns) |
109 | ns->last_skill_exp [tmp->subtype] = -1; |
113 | ns->last_skill_exp [idx] = -1; |
110 | } |
114 | } |
111 | } |
115 | } |
112 | } |
116 | } |
113 | |
117 | |
114 | static object * |
118 | static object * |
… | |
… | |
195 | * tool, this code will equip it. |
199 | * tool, this code will equip it. |
196 | * |
200 | * |
197 | * This code is basically the same as find_skill_by_name() above, |
201 | * This code is basically the same as find_skill_by_name() above, |
198 | * but instead a skill name, we search by matching number. |
202 | * but instead a skill name, we search by matching number. |
199 | * this replaces find_skill. |
203 | * this replaces find_skill. |
|
|
204 | * |
|
|
205 | * MUST NOT BE USED IN NEW CODE! (schmorp) |
200 | */ |
206 | */ |
201 | object * |
207 | object * |
202 | find_skill_by_number (object *who, int skillno) |
208 | find_skill_by_number (object *who, int skillno) |
203 | { |
209 | { |
204 | for (object *tmp = who->inv; tmp; tmp = tmp->below) |
210 | for (object *tmp = who->inv; tmp; tmp = tmp->below) |
… | |
… | |
440 | new_draw_info (NDI_UNIQUE, 0, op, "This skill is already in effect."); |
446 | new_draw_info (NDI_UNIQUE, 0, op, "This skill is already in effect."); |
441 | break; |
447 | break; |
442 | |
448 | |
443 | case SK_MINING: |
449 | case SK_MINING: |
444 | success = skill_mining (op, part, skill, dir, string); |
450 | success = skill_mining (op, part, skill, dir, string); |
445 | break; |
|
|
446 | |
|
|
447 | case SK_FISHING: |
|
|
448 | success = skill_fishing (op, part, skill, dir, string); |
|
|
449 | break; |
451 | break; |
450 | |
452 | |
451 | default: |
453 | default: |
452 | LOG (llevDebug, "%s attempted to use unknown skill: %d\n", query_name (op), op->chosen_skill->stats.sp); |
454 | LOG (llevDebug, "%s attempted to use unknown skill: %d\n", query_name (op), op->chosen_skill->stats.sp); |
453 | break; |
455 | break; |
… | |
… | |
647 | return 100; |
649 | return 100; |
648 | |
650 | |
649 | return rv; |
651 | return rv; |
650 | } |
652 | } |
651 | |
653 | |
|
|
654 | static int |
|
|
655 | cmp_skillp (const void *sk1, const void *sk2) |
|
|
656 | { |
|
|
657 | return strcmp (&((*(const object **) sk1)->name), |
|
|
658 | &((*(const object **) sk2)->name)); |
|
|
659 | } |
|
|
660 | |
652 | /* show_skills() - Meant to allow players to examine |
661 | /* show_skills() - Meant to allow players to examine |
653 | * their current skill list. |
662 | * their current skill list. |
654 | * This shows the amount of exp they have in the skills. |
663 | * This shows the amount of exp they have in the skills. |
655 | * we also include some other non skill related info (god, |
664 | * we also include some other non skill related info (god, |
656 | * max weapon improvments, item power). |
665 | * max weapon improvments, item power). |
657 | * Note this function is a bit more complicated becauase we |
666 | * Note this function is a bit more complicated becauase we |
658 | * we want ot sort the skills before printing them. If we |
667 | * we want ot sort the skills before printing them. If we |
659 | * just dumped this as we found it, this would be a bit |
668 | * just dumped this as we found it, this would be a bit |
660 | * simpler. |
669 | * simpler. |
661 | */ |
670 | */ |
662 | //TODO: egad, do it in perl, do not suffer from the big buffer on stack, make it one single drawinfo. |
|
|
663 | void |
671 | void |
664 | show_skills (object *pl, const char *search) |
672 | show_skills (object *pl, const char *search) |
665 | { |
673 | { |
666 | const char *cp; |
674 | const char *cp; |
667 | int i, num_skills_found = 0; |
675 | int i, num_skills_found = 0; |
668 | const char *const periods = ".............................."; // 30 |
676 | object *skills[CS_NUM_SKILLS]; |
669 | |
|
|
670 | /* Need to have a pointer and use strdup for qsort to work properly */ |
|
|
671 | char skills[NUM_SKILLS][128]; // d'oh |
|
|
672 | |
|
|
673 | object *op = pl->contr->ob; |
677 | object *op = pl->contr->ob; |
674 | |
678 | |
|
|
679 | /* find the skills */ |
675 | for (object *tmp = op->inv; tmp; tmp = tmp->below) |
680 | for (object *tmp = op->inv; tmp; tmp = tmp->below) |
676 | { |
681 | { |
677 | if (tmp->type == SKILL) |
682 | if (tmp->type == SKILL) |
678 | { |
683 | { |
679 | if (search && !tmp->name.contains (search)) |
684 | if (search && !tmp->name.contains (search)) |
680 | continue; |
685 | continue; |
681 | |
686 | |
682 | char buf[30]; |
687 | skills[num_skills_found++] = tmp; |
683 | |
|
|
684 | /* Basically want to fill this out to 30 spaces with periods */ |
|
|
685 | snprintf (buf, sizeof (buf), "%s%s", &tmp->name, periods); |
|
|
686 | |
|
|
687 | if (settings.permanent_exp_ratio) |
|
|
688 | snprintf (skills[num_skills_found++], sizeof (skills [0]), "%slvl:%3d (xp:%" PRId64 "/%" PRId64 "/%d%%)", |
|
|
689 | buf, tmp->level, tmp->stats.exp, |
|
|
690 | level_exp (tmp->level + 1, op->expmul), clipped_percent (tmp->perm_exp, tmp->stats.exp)); |
|
|
691 | else |
|
|
692 | snprintf (skills[num_skills_found++], sizeof (skills [0]), "%slvl:%3d (xp:%" PRId64 "/%" PRId64 ")", |
|
|
693 | buf, tmp->level, tmp->stats.exp, level_exp (tmp->level + 1, op->expmul)); |
|
|
694 | |
688 | |
695 | /* I don't know why some characters get a bunch of skills, but |
689 | /* I don't know why some characters get a bunch of skills, but |
696 | * it sometimes happens (maybe a leftover from bugier earlier code |
690 | * it sometimes happens (maybe a leftover from bugier earlier code |
697 | * and those character are still about). In any case, lets handle |
691 | * and those character are still about). In any case, lets handle |
698 | * it so it doesn't crash the server - otherwise, one character may |
692 | * it so it doesn't crash the server - otherwise, one character may |
699 | * crash the server numerous times. |
693 | * crash the server numerous times. |
700 | */ |
694 | */ |
701 | if (num_skills_found >= NUM_SKILLS) |
695 | if (num_skills_found >= CS_NUM_SKILLS) |
702 | { |
696 | { |
703 | new_draw_info (NDI_RED | NDI_REPLY, 0, op, "Your character has too many skills."); |
697 | new_draw_info (NDI_RED | NDI_REPLY, 0, op, "Your character has too many skills."); |
704 | new_draw_info (NDI_RED | NDI_REPLY, 0, op, "Something isn't right - contact the server admin"); |
698 | new_draw_info (NDI_RED | NDI_REPLY, 0, op, "Something isn't right - contact the server admin"); |
705 | break; |
699 | break; |
706 | } |
700 | } |
… | |
… | |
709 | |
703 | |
710 | dynbuf_text &msg = msg_dynbuf; msg.clear (); |
704 | dynbuf_text &msg = msg_dynbuf; msg.clear (); |
711 | |
705 | |
712 | msg << "T<Player skills:>\n\n"; |
706 | msg << "T<Player skills:>\n\n"; |
713 | if (num_skills_found > 1) |
707 | if (num_skills_found > 1) |
714 | qsort (skills, num_skills_found, sizeof (skills [0]), (int (*)(const void *, const void *)) std::strcmp); |
708 | qsort (skills, num_skills_found, sizeof (skills [0]), cmp_skillp); |
715 | |
709 | |
|
|
710 | char buf[31]; /* +1 for '\0' */ |
|
|
711 | const char *const periods = ".............................."; // 30 |
|
|
712 | |
716 | for (i = 0; i < num_skills_found; i++) |
713 | for (i = 0; i < num_skills_found; i++) { |
717 | msg << " C<" << skills [i] << ">\n"; |
714 | object *tmp = skills[i]; |
|
|
715 | |
|
|
716 | /* Basically want to fill this out to 30 spaces with periods */ |
|
|
717 | snprintf (buf, sizeof (buf), "%s%s", &tmp->name, periods); |
|
|
718 | |
|
|
719 | msg << " C<"; |
|
|
720 | |
|
|
721 | if (settings.permanent_exp_ratio) |
|
|
722 | msg.printf ("%slvl:%3d (xp:%" PRId64 "/%" PRId64 "/%d%%)", |
|
|
723 | buf, tmp->level, tmp->stats.exp, level_exp (tmp->level + 1, op->expmul), |
|
|
724 | clipped_percent (tmp->perm_exp, tmp->stats.exp)); |
|
|
725 | else |
|
|
726 | msg.printf ("%slvl:%3d (xp:%" PRId64 "/%" PRId64 ")", |
|
|
727 | buf, tmp->level, tmp->stats.exp, level_exp (tmp->level + 1, op->expmul)); |
|
|
728 | |
|
|
729 | msg << ">\n"; |
|
|
730 | } |
718 | |
731 | |
719 | msg << "\nYou can handle " << op->level / 5 + 5 << " weapon improvements.\r"; |
732 | msg << "\nYou can handle " << op->level / 5 + 5 << " weapon improvements.\r"; |
720 | |
733 | |
721 | cp = determine_god (op); |
734 | cp = determine_god (op); |
722 | msg << "You worship " << (cp ? cp : "no god at current time") << ".\r"; |
735 | msg << "You worship " << (cp ? cp : "no god at current time") << ".\r"; |
… | |
… | |
884 | } |
897 | } |
885 | } |
898 | } |
886 | |
899 | |
887 | if (!op->apply (pl->combat_ob)) |
900 | if (!op->apply (pl->combat_ob)) |
888 | return 0; |
901 | return 0; |
|
|
902 | |
|
|
903 | if (!op->chosen_skill) |
|
|
904 | { |
|
|
905 | LOG (llevError, "do_skill_attack: weapon has no skill (%s)", pl->combat_ob->debug_desc ()); |
|
|
906 | new_draw_info (NDI_RED | NDI_REPLY, 0, op, "You hit a bug in the server, please contact an admin!"); |
|
|
907 | return 0; |
|
|
908 | } |
889 | } |
909 | } |
890 | |
910 | |
891 | /* lose invisiblity/hiding status for running attacks */ |
911 | /* lose invisiblity/hiding status for running attacks */ |
892 | if (pl->tmp_invis) |
912 | if (pl->tmp_invis) |
893 | { |
913 | { |