Crossfire Server, Branch 1.12  R12190
skill_util.c
Go to the documentation of this file.
00001 /*
00002  * static char *rcsid_skill_util_c =
00003  *   "$Id: skill_util.c 11578 2009-02-23 22:02:27Z lalo $";
00004  */
00005 /*
00006     CrossFire, A Multiplayer game for X-windows
00007 
00008     Copyright (C) 2006 Mark Wedel & Crossfire Development Team
00009     Copyright (C) 1992 Frank Tore Johansen
00010 
00011     This program is free software; you can redistribute it and/or modify
00012     it under the terms of the GNU General Public License as published by
00013     the Free Software Foundation; either version 2 of the License, or
00014     (at your option) any later version.
00015 
00016     This program is distributed in the hope that it will be useful,
00017     but WITHOUT ANY WARRANTY; without even the implied warranty of
00018     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
00019     GNU General Public License for more details.
00020 
00021     You should have received a copy of the GNU General Public License
00022     along with this program; if not, write to the Free Software
00023     Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
00024 
00025     The author can be reached via e-mail to crossfire-devel@real-time.com
00026 */
00027 
00046 /* define the following for skills utility debuging */
00047 /* #define SKILL_UTIL_DEBUG */
00048 
00049 #define WANT_UNARMED_SKILLS
00050 
00051 #include <global.h>
00052 #include <object.h>
00053 #ifndef __CEXTRACT__
00054 #include <sproto.h>
00055 #endif
00056 #include <living.h>     /* for defs of STR,CON,DEX,etc. -b.t.*/
00057 #include <spells.h>
00058 
00059 static int attack_hth(object *pl, int dir, const char *string, object *skill);
00060 static int attack_melee_weapon(object *op, int dir, const char *string, object *skill);
00061 
00065 const char *skill_names[NUM_SKILLS];
00066 
00071 void init_skills(void) {
00072     int i;
00073     archetype *at;
00074 
00075     for (i = 0; i < NUM_SKILLS; i++)
00076         skill_names[i] = NULL;
00077 
00078     for (at = first_archetype; at != NULL; at = at->next) {
00079         if (at->clone.type == SKILL) {
00080             if (at->clone.subtype >= sizeof(skill_names)/sizeof(*skill_names)) {
00081                 LOG(llevError, "init_skills: invalid skill subtype %d for skill %s\n", at->clone.subtype, at->clone.skill);
00082             } else if (skill_names[at->clone.subtype] != NULL) {
00083                 LOG(llevError, "init_skills: multiple skill using same subtype %d, %s, %s\n",
00084                     at->clone.subtype, skill_names[at->clone.subtype], at->clone.skill);
00085             } else {
00086                 skill_names[at->clone.subtype] = add_refcount(at->clone.skill);
00087             }
00088         }
00089     }
00090 
00091     /* This isn't really an error if there is no skill subtype set, but
00092      * checking for this may catch some user errors.
00093      * On the other hand, it'll crash later on, which is not nice. Thus giving a dummy name.
00094      */
00095     for (i = 1; i < NUM_SKILLS; i++) {
00096         if (!skill_names[i]) {
00097             LOG(llevError, "init_skills: skill subtype %d doesn't have a name?\n", i);
00098             skill_names[i] = add_string("dummy skill");
00099         }
00100     }
00101 }
00102 
00112 void link_player_skills(object *op) {
00113     object *tmp;
00114 
00115     for (tmp = op->inv; tmp; tmp = tmp->below) {
00116         if (tmp->type == SKILL) {
00117             /* This is really a warning, hence no else below */
00118             if (op->contr->last_skill_ob[tmp->subtype] && op->contr->last_skill_ob[tmp->subtype] != tmp) {
00119                 LOG(llevError, "Multiple skills with the same subtype? %s, %s\n", op->contr->last_skill_ob[tmp->subtype]->skill, tmp->skill);
00120             }
00121             if (tmp->subtype >= NUM_SKILLS) {
00122                 LOG(llevError, "Invalid subtype number %d (range 0-%d)\n", tmp->subtype, NUM_SKILLS);
00123             } else {
00124                 op->contr->last_skill_ob[tmp->subtype] = tmp;
00125                 op->contr->last_skill_exp[tmp->subtype] = -1;
00126             }
00127         }
00128     }
00129 }
00130 
00153 static object *adjust_skill_tool(object *who, object *skill, object *skill_tool) {
00154     if (!skill && !skill_tool)
00155         return NULL;
00156 
00157     /* If this is a skill that can be used without a tool and no tool found, return it */
00158     if (skill && QUERY_FLAG(skill, FLAG_CAN_USE_SKILL) && (!skill_tool || QUERY_FLAG(skill_tool, FLAG_APPLIED) || strcmp(skill->skill, "clawing")  == 0))
00159         return skill;
00160 
00161     /* Player has a tool to use the skill.  If not applied, apply it -
00162      * if not successful, return skill if can be used.  If they do have the skill tool
00163      * but not the skill itself, give it to them.
00164      */
00165     if (skill_tool) {
00166         if (!QUERY_FLAG(skill_tool, FLAG_APPLIED)) {
00167             if (apply_special(who, skill_tool, 0)) {
00168                 if (skill && QUERY_FLAG(skill, FLAG_CAN_USE_SKILL))
00169                     return skill;
00170                 else
00171                     return NULL;
00172             }
00173         }
00174         if (!skill) {
00175             skill = give_skill_by_name(who, skill_tool->skill);
00176             link_player_skills(who);
00177         }
00178         return skill;
00179     }
00180     if (skill && QUERY_FLAG(skill, FLAG_CAN_USE_SKILL))
00181         return skill;
00182     else
00183         return NULL;
00184 }
00185 
00207 object *find_skill_by_name(object *who, const char *name) {
00208     object *skill = NULL, *skill_tool = NULL, *tmp;
00209 
00210     if (!name)
00211         return NULL;
00212 
00213     /* We make sure the length of the string in the object is greater
00214      * in length than the passed string. Eg, if we have a skill called
00215      * 'hi', we don't want to match if the user passed 'high'
00216      */
00217     for (tmp = who->inv; tmp != NULL; tmp = tmp->below) {
00218         if (tmp->type == SKILL
00219         && !strncasecmp(name, tmp->skill, strlen(name))
00220         && strlen(tmp->skill) >= strlen(name))
00221             skill = tmp;
00222 
00223         /* Try to find appropriate skilltool.  If the player has one already
00224          * applied, we try to keep using that one.
00225          */
00226         else if (tmp->type == SKILL_TOOL
00227         && !strncasecmp(name, tmp->skill, strlen(name))
00228         && strlen(tmp->skill) >= strlen(name)) {
00229             if (QUERY_FLAG(tmp, FLAG_APPLIED))
00230                 skill_tool = tmp;
00231             else if (!skill_tool || !QUERY_FLAG(skill_tool, FLAG_APPLIED))
00232                 skill_tool = tmp;
00233         }
00234     }
00235 
00236     return adjust_skill_tool(who, skill, skill_tool);
00237 }
00238 
00257 object *find_skill_by_number(object *who, int skillno) {
00258     object *skill = NULL, *skill_tool = NULL, *tmp;
00259 
00260     if (skillno < 1 || skillno >= NUM_SKILLS)
00261         return NULL;
00262 
00263     for (tmp = who->inv; tmp != NULL; tmp = tmp->below) {
00264         if (tmp->type == SKILL && tmp->subtype == skillno)
00265             skill = tmp;
00266 
00267         /* Try to find appropriate skilltool.  If the player has one already
00268          * applied, we try to keep using that one.
00269          */
00270         else if (tmp->type == SKILL_TOOL && tmp->subtype == skillno) {
00271             if (QUERY_FLAG(tmp, FLAG_APPLIED))
00272                 skill_tool = tmp;
00273             else if (!skill_tool || !QUERY_FLAG(skill_tool, FLAG_APPLIED))
00274                 skill_tool = tmp;
00275         }
00276     }
00277 
00278     return adjust_skill_tool(who, skill, skill_tool);
00279 }
00280 
00301 int change_skill(object *who, object *new_skill, int flag) {
00302     int old_range;
00303 
00304     if (who->type != PLAYER)
00305         return 0;
00306 
00307     old_range = who->contr->shoottype;
00308 
00309     if (who->chosen_skill && who->chosen_skill == new_skill) {
00310         /* optimization for changing skill to current skill */
00311         if (who->type == PLAYER && !(flag&0x1))
00312             who->contr->shoottype = range_skill;
00313         return 1;
00314     }
00315 
00316     if (!new_skill || who->chosen_skill)
00317         if (who->chosen_skill)
00318             apply_special(who, who->chosen_skill, AP_UNAPPLY|(flag&AP_NOPRINT));
00319 
00320     /* Only goal in this case was to unapply a skill */
00321     if (!new_skill)
00322         return 0;
00323 
00324     if (apply_special(who, new_skill, AP_APPLY|(flag&AP_NOPRINT))) {
00325         return 0;
00326     }
00327     if (flag&0x1)
00328         who->contr->shoottype = old_range;
00329 
00330     return 1;
00331 }
00332 
00340 void clear_skill(object *who) {
00341     who->chosen_skill = NULL;
00342     CLEAR_FLAG(who, FLAG_READY_SKILL);
00343     if (who->type == PLAYER) {
00344         who->contr->ranges[range_skill] = NULL;
00345         if (who->contr->shoottype == range_skill)
00346             who->contr->shoottype = range_none;
00347     }
00348 }
00349 
00373 int do_skill(object *op, object *part, object *skill, int dir, const char *string) {
00374     int success = 0, exp = 0;
00375     object *tmp;
00376 
00377     if (!skill)
00378         return 0;
00379 
00380     /* The code below presumes that the skill points to the object that
00381      * holds the exp, level, etc of the skill.  So if this is a player
00382      * go and try to find the actual real skill pointer, and if the
00383      * the player doesn't have a bucket for that, create one.
00384      */
00385     if (skill->type != SKILL && op->type == PLAYER) {
00386         for (tmp = op->inv; tmp != NULL; tmp = tmp->below) {
00387             if (tmp->type == SKILL && tmp->skill == skill->skill)
00388                 break;
00389         }
00390         if (!tmp)
00391             tmp = give_skill_by_name(op, skill->skill);
00392         skill = tmp;
00393     }
00394 
00395     if (skill->anim_suffix)
00396         apply_anim_suffix(op, skill->anim_suffix);
00397 
00398     switch (skill->subtype) {
00399     case SK_LEVITATION:
00400         /* Not 100% sure if this will work with new movement code -
00401          * the levitation skill has move_type for flying, so when
00402          * equipped, that should transfer to player, when not,
00403          * shouldn't.
00404          */
00405         if (QUERY_FLAG(skill, FLAG_APPLIED)) {
00406             CLEAR_FLAG(skill, FLAG_APPLIED);
00407             draw_ext_info(NDI_UNIQUE, 0, op, MSG_TYPE_SKILL, MSG_TYPE_SKILL_SUCCESS,
00408                           "You come to earth.", NULL);
00409         } else {
00410             SET_FLAG(skill, FLAG_APPLIED);
00411             draw_ext_info(NDI_UNIQUE, 0, op, MSG_TYPE_SKILL, MSG_TYPE_SKILL_SUCCESS,
00412                           "You rise into the air!.", NULL);
00413         }
00414         fix_object(op);
00415         success = 1;
00416         break;
00417 
00418     case SK_STEALING:
00419         exp = success = steal(op, dir, skill);
00420         break;
00421 
00422     case SK_LOCKPICKING:
00423         exp = success = pick_lock(op, dir, skill);
00424         break;
00425 
00426     case SK_HIDING:
00427         exp = success = hide(op, skill);
00428         break;
00429 
00430     case SK_JUMPING:
00431         success = jump(op, dir, skill);
00432         break;
00433 
00434     case SK_INSCRIPTION:
00435         exp = success = write_on_item(op, string, skill);
00436         break;
00437 
00438     case SK_MEDITATION:
00439         meditate(op, skill);
00440         success = 1;
00441         break;
00442         /* note that the following 'attack' skills gain exp through hit_player() */
00443 
00444     case SK_KARATE:
00445         (void)attack_hth(op, dir, "karate-chopped", skill);
00446         break;
00447 
00448     case SK_PUNCHING:
00449         (void)attack_hth(op, dir, "punched", skill);
00450         break;
00451 
00452     case SK_FLAME_TOUCH:
00453         (void)attack_hth(op, dir, "flamed", skill);
00454         break;
00455 
00456     case SK_CLAWING:
00457         (void)attack_hth(op, dir, "clawed", skill);
00458         break;
00459 
00460     case SK_WRAITH_FEED:
00461         (void)attack_hth(op, dir, "fed upon", skill);
00462         break;
00463 
00464     case SK_ONE_HANDED_WEAPON:
00465     case SK_TWO_HANDED_WEAPON:
00466         (void)attack_melee_weapon(op, dir,  NULL, skill);
00467         break;
00468 
00469     case SK_FIND_TRAPS:
00470         exp = success = find_traps(op, skill);
00471         break;
00472 
00473     case SK_SINGING:
00474         exp = success = singing(op, dir, skill);
00475         break;
00476 
00477     case SK_ORATORY:
00478         exp = success = use_oratory(op, dir, skill);
00479         break;
00480 
00481     case SK_SMITHERY:
00482     case SK_BOWYER:
00483     case SK_JEWELER:
00484     case SK_ALCHEMY:
00485     case SK_THAUMATURGY:
00486     case SK_LITERACY:
00487     case SK_WOODSMAN:
00488         if (use_alchemy(op) == 0)
00489             exp = success = skill_ident(op, skill);
00490         break;
00491 
00492     case SK_DET_MAGIC:
00493     case SK_DET_CURSE:
00494         exp = success = skill_ident(op, skill);
00495         break;
00496 
00497     case SK_DISARM_TRAPS:
00498         exp = success = remove_trap(op, skill);
00499         break;
00500 
00501     case SK_THROWING:
00502         success = skill_throw(op, part, dir, string, skill);
00503         break;
00504 
00505     case SK_SET_TRAP:
00506         draw_ext_info(NDI_UNIQUE, 0, op, MSG_TYPE_SKILL, MSG_TYPE_SKILL_ERROR,
00507                       "This skill is not currently implemented.", NULL);
00508         break;
00509 
00510     case SK_USE_MAGIC_ITEM:
00511     case SK_MISSILE_WEAPON:
00512         draw_ext_info(NDI_UNIQUE, 0, op, MSG_TYPE_SKILL, MSG_TYPE_SKILL_ERROR,
00513                       "There is no special attack for this skill.", NULL);
00514         break;
00515 
00516     case SK_PRAYING:
00517         success = pray(op, skill);
00518         break;
00519 
00520     case SK_BARGAINING:
00521         success = describe_shop(op);
00522         break;
00523 
00524     case SK_SORCERY:
00525     case SK_EVOCATION:
00526     case SK_PYROMANCY:
00527     case SK_SUMMONING:
00528     case SK_CLIMBING:
00529         draw_ext_info(NDI_UNIQUE, 0, op, MSG_TYPE_SKILL, MSG_TYPE_SKILL_ERROR,
00530                       "This skill is already in effect.", NULL);
00531         break;
00532 
00533     case SK_HARVESTING:
00534         success = do_harvest(op, dir, skill);
00535         break;
00536 
00537     default: {
00538             char name[MAX_BUF];
00539 
00540             query_name(op, name, MAX_BUF);
00541             LOG(llevDebug, "%s attempted to use unknown skill: %d\n", name, op->chosen_skill->stats.sp);
00542             break;
00543         }
00544     }
00545 
00546     /* For players we now update the speed_left from using the skill.
00547      * Monsters have no skill use time because of the random nature in
00548      * which use_monster_skill is called already simulates this.
00549      * If certain skills should take more/less time, that should be
00550      * in the code for the skill itself.
00551      */
00552 
00553     if (op->type == PLAYER)
00554         op->speed_left -= 1.0;
00555 
00556     /* this is a good place to add experience for successfull use of skills.
00557      * Note that add_exp() will figure out player/monster experience
00558      * gain problems.
00559      */
00560 
00561     if (success && exp)
00562         change_exp(op, exp, skill->skill, SK_SUBTRACT_SKILL_EXP);
00563 
00564     return success;
00565 }
00566 
00599 sint64 calc_skill_exp(object *who, object *op, object *skill) {
00600     sint64 op_exp = 0;
00601     int op_lvl = 0;
00602     float base, value, lvl_mult = 0.0;
00603 
00604     if (!skill)
00605         skill = who;
00606 
00607     /* Oct 95 - where we have an object, I expanded our treatment
00608      * to 3 cases:
00609      * non-living magic obj, runes and everything else.
00610      *
00611      * If an object is not alive and magical we set the base exp higher to
00612      * help out exp awards for skill_ident skills. Also, if
00613      * an item is type RUNE, we give out exp based on stats.Cha
00614      * and level (this was the old system) -b.t.
00615      */
00616 
00617     if (!op) {  /* no item/creature */
00618         op_lvl = MAX(who->map->difficulty, 1);
00619         op_exp = 0;
00620     } else if (op->type == RUNE || op->type == TRAP) { /* all traps. If stats.Cha > 1 we use that
00621      * for the amount of experience */
00622         op_exp = op->stats.Cha > 1 ? op->stats.Cha : op->stats.exp;
00623         op_lvl = op->level;
00624     } else {   /* all other items/living creatures */
00625         op_exp = op->stats.exp;
00626         op_lvl = op->level;
00627         if (!QUERY_FLAG(op, FLAG_ALIVE)) { /* for ident/make items */
00628             op_lvl += 5*abs(op->magic);
00629         }
00630     }
00631 
00632     if (op_lvl < 1)
00633         op_lvl = 1;
00634 
00635     if (who->type != PLAYER) {            /* for monsters only */
00636         return ((sint64)(op_exp*0.1)+1);  /* we add one to insure positive value is returned */
00637     } else {                            /* for players */
00638         base = op_exp;
00639         /* if skill really is a skill, then we can look at the skill archetype for
00640          * bse reward value (exp) and level multiplier factor.
00641          */
00642         if (skill->type == SKILL) {
00643             base += skill->arch->clone.stats.exp;
00644             if (settings.simple_exp) {
00645                 if (skill->arch->clone.level)
00646                     lvl_mult = (float)skill->arch->clone.level/100.0;
00647                 else
00648                     lvl_mult = 1.0; /* no adjustment */
00649             } else {
00650                 if (skill->level)
00651                     lvl_mult = ((float)skill->arch->clone.level*(float)op_lvl)/((float)skill->level*100.0);
00652                 else
00653                     lvl_mult = 1.0;
00654             }
00655         } else {
00656             /* Don't divide by zero here! */
00657             lvl_mult = (float)op_lvl/(float)(skill->level ? skill->level : 1);
00658         }
00659     }
00660 
00661     /* assemble the exp total, and return value */
00662 
00663     value =  base*lvl_mult;
00664     if (value < 1)
00665         value = 1; /* Always give at least 1 exp point */
00666 
00667 #ifdef SKILL_UTIL_DEBUG
00668     LOG(llevDebug, "calc_skill_exp(): who: %s(lvl:%d)  op:%s(lvl:%d)\n", who->name, skill->level, op->name, op_lvl);
00669 #endif
00670     return ((sint64)value);
00671 }
00672 
00690 int learn_skill(object *pl, object *scroll) {
00691     object *tmp;
00692 
00693     if (!scroll->skill) {
00694         LOG(llevError, "skill scroll %s does not have skill pointer set.\n", scroll->name);
00695         return 2;
00696     }
00697 
00698     /* can't use find_skill_by_name because we want skills the player knows
00699      * but can't use natively.
00700      */
00701 
00702     for (tmp = pl->inv; tmp != NULL; tmp = tmp->below)
00703         if (tmp->type == SKILL && !strncasecmp(scroll->skill, tmp->skill, strlen(scroll->skill)))
00704             break;
00705 
00706     /* player already knows it */
00707     if (tmp && QUERY_FLAG(tmp, FLAG_CAN_USE_SKILL))
00708         return 0;
00709 
00710     /* now a random change to learn, based on player Int.
00711      * give bonus based on level - otherwise stupid characters
00712      * might never be able to learn anything.
00713      */
00714     if (random_roll(0, 99, pl, PREFER_LOW) > (learn_spell[pl->stats.Int]+(pl->level/5)))
00715         return 2; /* failure :< */
00716 
00717     if (!tmp)
00718         tmp = give_skill_by_name(pl, scroll->skill);
00719 
00720     if (!tmp) {
00721         LOG(llevError, "skill scroll %s does not have valid skill name (%s).\n", scroll->name, scroll->skill);
00722         return 2;
00723     }
00724 
00725     SET_FLAG(tmp, FLAG_CAN_USE_SKILL);
00726     link_player_skills(pl);
00727     return 1;
00728 }
00729 
00741 static int clipped_percent(sint64 a, sint64 b) {
00742     int rv;
00743 
00744     if (b <= 0)
00745         return 0;
00746 
00747     rv = (int)((100.0f*((float)a)/((float)b))+0.5f);
00748 
00749     if (rv < 0)
00750         return 0;
00751     else if (rv > 100)
00752         return 100;
00753 
00754     return rv;
00755 }
00756 
00773 void show_skills(object *op, const char *search) {
00774     object *tmp = NULL;
00775     char buf[MAX_BUF];
00776     const char *cp;
00777     int i, num_skills_found = 0;
00778     static const char *const periods = "........................................";
00779     /* Need to have a pointer and use strdup for qsort to work properly */
00780     char skills[NUM_SKILLS][MAX_BUF];
00781 
00782     for (tmp = op->inv; tmp != NULL; tmp = tmp->below) {
00783         if (tmp->type == SKILL) {
00784             if (search && strstr(tmp->name, search) == NULL)
00785                 continue;
00786             /* Basically want to fill this out to 40 spaces with periods */
00787             snprintf(buf, sizeof(buf), "%s%s", tmp->name, periods);
00788             buf[40] = 0;
00789 
00790             if (settings.permanent_exp_ratio) {
00791                 snprintf(skills[num_skills_found++], MAX_BUF, "%slvl:%3d (xp:%"FMT64"/%"FMT64"/%d%%)",
00792                          buf, tmp->level,
00793                          tmp->stats.exp,
00794                          level_exp(tmp->level+1, op->expmul),
00795                          clipped_percent(tmp->perm_exp, tmp->stats.exp));
00796             } else {
00797                 snprintf(skills[num_skills_found++], MAX_BUF, "%slvl:%3d (xp:%"FMT64"/%"FMT64")",
00798                          buf, tmp->level,
00799                          tmp->stats.exp,
00800                          level_exp(tmp->level+1, op->expmul));
00801             }
00802             /* I don't know why some characters get a bunch of skills, but
00803              * it sometimes happens (maybe a leftover from bugier earlier code
00804              * and those character are still about).  In any case, lets handle
00805              * it so it doesn't crash the server - otherwise, one character may
00806              * crash the server numerous times.
00807              */
00808             if (num_skills_found >= NUM_SKILLS) {
00809                 draw_ext_info(NDI_RED, 0, op, MSG_TYPE_SKILL, MSG_TYPE_SKILL_ERROR,
00810                               "Your character has too many skills. "
00811                               "Something isn't right - contact the server admin", NULL);
00812                 break;
00813             }
00814         }
00815     }
00816 
00817     draw_ext_info(NDI_UNIQUE, 0, op, MSG_TYPE_SKILL, MSG_TYPE_SKILL_LIST,
00818                   "Player skills:", NULL);
00819 
00820     if (num_skills_found > 1)
00821         qsort(skills, num_skills_found, MAX_BUF, (int (*)(const void *, const void *))strcmp);
00822 
00823     for (i = 0; i < num_skills_found; i++) {
00824         draw_ext_info(NDI_UNIQUE, 0, op, MSG_TYPE_SKILL, MSG_TYPE_SKILL_LIST,
00825                       skills[i], skills[i]);
00826     }
00827 
00828     draw_ext_info_format(NDI_UNIQUE, 0, op, MSG_TYPE_SKILL, MSG_TYPE_SKILL_LIST,
00829                          "You can handle %d weapon improvements.",
00830                          "You can handle %d weapon improvements.",
00831                          op->level/5+5);
00832 
00833     cp = determine_god(op);
00834     if (strcmp(cp, "none") == 0)
00835         cp = NULL;
00836     draw_ext_info_format(NDI_UNIQUE, 0, op, MSG_TYPE_SKILL, MSG_TYPE_SKILL_LIST,
00837                          "You worship %s.",
00838                          "You worship %s.",
00839                          cp ? cp : "no god at current time");
00840 
00841     draw_ext_info_format(NDI_UNIQUE, 0, op, MSG_TYPE_SKILL, MSG_TYPE_SKILL_LIST,
00842                          "Your equipped item power is %d out of %d\n",
00843                          "Your equipped item power is %d out of %d\n",
00844                          op->contr->item_power, op->level);
00845 }
00846 
00863 int use_skill(object *op, const char *string) {
00864     object *skop;
00865     size_t len;
00866 
00867     if (!string)
00868         return 0;
00869 
00870     for (skop = op->inv; skop != NULL; skop = skop->below) {
00871         if (skop->type == SKILL
00872         && QUERY_FLAG(skop, FLAG_CAN_USE_SKILL)
00873         && !strncasecmp(string, skop->skill, MIN(strlen(string), strlen(skop->skill))))
00874             break;
00875         else if (skop->type == SKILL_TOOL
00876         && !strncasecmp(string, skop->skill, MIN(strlen(string), strlen(skop->skill))))
00877             break;
00878     }
00879     if (!skop) {
00880         draw_ext_info_format(NDI_UNIQUE, 0, op, MSG_TYPE_SKILL, MSG_TYPE_SKILL_MISSING,
00881                              "Unable to find skill %s",
00882                              "Unable to find skill %s",
00883                              string);
00884         return 0;
00885     }
00886 
00887     len = strlen(skop->skill);
00888 
00889     /* All this logic goes and skips over the skill name to find any
00890      * options given to the skill.  Its pretty simple - if there
00891      * are extra parameters (as deteremined by string length), we
00892      * want to skip over any leading spaces.
00893      */
00894     if (len >= strlen(string)) {
00895         string = NULL;
00896     } else {
00897         string += len;
00898         while (*string == 0x20)
00899             string++;
00900         if (strlen(string) == 0)
00901             string = NULL;
00902     }
00903 
00904 #ifdef SKILL_UTIL_DEBUG
00905     LOG(llevDebug, "use_skill() got skill: %s\n", sknum > -1 ? skills[sknum].name : "none");
00906 #endif
00907 
00908     /* Change to the new skill, then execute it. */
00909     if (do_skill(op, op, skop, op->facing, string))
00910         return 1;
00911 
00912     return 0;
00913 }
00914 
00937 static object *find_best_player_hth_skill(object *op) {
00938     object *tmp, *best_skill = NULL;
00939     int dragon = is_dragon_pl(op), last_skill = sizeof(unarmed_skills), i;
00940 
00941     /* Dragons are a special case - gros 25th July 2006 */
00942     if (dragon) {
00943         tmp = find_skill_by_number(op, SK_CLAWING);
00944         if (tmp) /* I suppose it should always be true - but maybe there's
00945                   * draconic toothache ? :) */
00946             return tmp;
00947     }
00948     for (tmp = op->inv; tmp; tmp = tmp->below) {
00949         if (tmp->type == SKILL) {
00950 
00951             /* The order in the array is preferred order.  So basically,
00952              * we just cut down the number to search - eg, if we find a skill
00953              * early on in flame touch, then we only need to look into the unarmed_array
00954              * to the entry before flame touch - don't care about the entries afterward,
00955              * because they are infrerior skills.
00956              * if we end up finding the best skill (i==0) might as well return
00957              * right away - can't get any better than that.
00958              */
00959             for (i = 0; i < last_skill; i++) {
00960                 if (tmp->subtype == unarmed_skills[i] && QUERY_FLAG(tmp, FLAG_CAN_USE_SKILL)) {
00961                     best_skill = tmp;
00962                     last_skill = i;
00963                     if (i == 0)
00964                         return best_skill;
00965                 }
00966             }
00967         }
00968     }
00969     return best_skill;
00970 }
00971 
00987 static int do_skill_attack(object *tmp, object *op, const char *string, object *skill) {
00988     int success;
00989 
00990     /* For Players only: if there is no ready weapon, and no "attack" skill
00991      * is readied either then try to find a skill for the player to use.
00992      * it is presumed that if skill is set, it is a valid attack skill (eg,
00993      * the caller should have set it appropriately).  We still want to pass
00994      * through that code if skill is set to change to the skill.
00995      */
00996     if (op->type == PLAYER) {
00997         if (!QUERY_FLAG(op, FLAG_READY_WEAPON)) {
00998             size_t i;
00999 
01000             if (!skill) {
01001                 /* See if the players chosen skill is a combat skill, and use
01002                  * it if appropriate.
01003                  */
01004                 if (op->chosen_skill) {
01005                     for (i = 0; i < sizeof(unarmed_skills); i++)
01006                         if (op->chosen_skill->subtype == unarmed_skills[i]) {
01007                             skill = op->chosen_skill;
01008                             break;
01009                         }
01010                 }
01011                 /* If we didn't find a skill above, look harder for a good skill */
01012                 if (!skill) {
01013                     skill = find_best_player_hth_skill(op);
01014 
01015                     if (!skill) {
01016                         draw_ext_info(NDI_BLACK, 0, op,
01017                                       MSG_TYPE_SKILL, MSG_TYPE_SKILL_MISSING,
01018                                       "You have no unarmed combat skills!", NULL);
01019                         return 0;
01020                     }
01021                 }
01022             }
01023             if (skill != op->chosen_skill) {
01024                 /* now try to ready the new skill */
01025                 if (!change_skill(op, skill, 1)) { /* oh oh, trouble! */
01026                     draw_ext_info_format(NDI_UNIQUE, 0, tmp,
01027                                          MSG_TYPE_SKILL, MSG_TYPE_SKILL_ERROR,
01028                                          "Couldn't change to skill %s",
01029                                          "Couldn't change to skill %s",
01030                                          skill->name);
01031                     return 0;
01032                 }
01033             }
01034         } else {
01035             /* Seen some crashes below where current_weapon is not set,
01036              * even though the flag says it is.  So if current weapon isn't set,
01037              * do some work in trying to find the object to use.
01038              */
01039             if (!op->current_weapon) {
01040                 object *tmp;
01041 
01042                 LOG(llevError, "Player %s does not have current weapon set but flag_ready_weapon is set\n", op->name);
01043                 for (tmp = op->inv; tmp; tmp = tmp->below)
01044                     if (tmp->type == WEAPON && QUERY_FLAG(tmp, FLAG_APPLIED))
01045                         break;
01046 
01047                 if (!tmp) {
01048                     LOG(llevError, "Could not find applied weapon on %s\n", op->name);
01049                     op->current_weapon = NULL;
01050                     return 0;
01051                 } else {
01052                     char weapon[MAX_BUF];
01053 
01054                     query_name(tmp, weapon, MAX_BUF);
01055                     op->current_weapon = tmp;
01056                 }
01057             }
01058 
01059             /* Has ready weapon - make sure chosen_skill is set up properly */
01060             if (!op->chosen_skill || op->current_weapon->skill != op->chosen_skill->skill) {
01061                 change_skill(op, find_skill_by_name(op, op->current_weapon->skill), 1);
01062             }
01063         }
01064     }
01065 
01066     /* lose invisiblity/hiding status for running attacks */
01067 
01068     if (op->type == PLAYER && op->contr->tmp_invis) {
01069         op->contr->tmp_invis = 0;
01070         op->invisible = 0;
01071         op->hide = 0;
01072         update_object(op, UP_OBJ_FACE);
01073     }
01074 
01075     success = attack_ob(tmp, op);
01076 
01077     /* print appropriate  messages to the player */
01078 
01079     if (success && string != NULL && tmp && !QUERY_FLAG(tmp, FLAG_FREED)) {
01080         char op_name[MAX_BUF];
01081         if(op->type==PLAYER) {
01082         query_name(tmp, op_name, MAX_BUF);
01083             draw_ext_info_format(NDI_UNIQUE, 0,op,
01084                          MSG_TYPE_ATTACK, MSG_TYPE_ATTACK_DID_HIT,
01085                          "You %s %s!",
01086                          "You %s %s!",
01087                          string,op_name);
01088     }
01089         else if(tmp->type==PLAYER) {
01090         query_name(op, op_name, MAX_BUF);
01091             draw_ext_info_format(NDI_UNIQUE, 0,tmp,
01092                          MSG_TYPE_VICTIM, MSG_TYPE_VICTIM_WAS_HIT,
01093                          "%s %s you!",
01094                          "%s %s you!",
01095                          op_name,string);
01096     }
01097     }
01098     return success;
01099 }
01100 
01126 int skill_attack(object *tmp, object *pl, int dir, const char *string, object *skill) {
01127     sint16 tx, ty;
01128     mapstruct *m;
01129     int mflags;
01130 
01131     if (!dir)
01132         dir = pl->facing;
01133     tx = freearr_x[dir];
01134     ty = freearr_y[dir];
01135 
01136     /* If we don't yet have an opponent, find if one exists, and attack.
01137      * Legal opponents are the same as outlined in move_player_attack()
01138      */
01139 
01140     if (tmp == NULL) {
01141         m = pl->map;
01142         tx = pl->x+freearr_x[dir];
01143         ty = pl->y+freearr_y[dir];
01144 
01145         mflags = get_map_flags(m, &m, tx, ty, &tx, &ty);
01146         if (mflags&P_OUT_OF_MAP)
01147             return 0;
01148 
01149         /* space must be blocked for there to be anything interesting to do */
01150         if (!(mflags&P_IS_ALIVE)
01151         && !OB_TYPE_MOVE_BLOCK(pl, GET_MAP_MOVE_BLOCK(m, tx, ty))) {
01152             return 0;
01153         }
01154 
01155         for (tmp = GET_MAP_OB(m, tx, ty); tmp; tmp = tmp->above)
01156             if ((QUERY_FLAG(tmp, FLAG_ALIVE) && tmp->stats.hp >= 0)
01157             || QUERY_FLAG(tmp, FLAG_CAN_ROLL)
01158             || tmp->type == LOCKED_DOOR) {
01159                 /* Don't attack party members */
01160                 if ((pl->type == PLAYER && tmp->type == PLAYER)
01161                 && (pl->contr->party != NULL && pl->contr->party == tmp->contr->party))
01162                     return 0;
01163                 break;
01164             }
01165     }
01166     if (!tmp) {
01167         if (pl->type == PLAYER)
01168             draw_ext_info(NDI_UNIQUE, 0, pl, MSG_TYPE_SKILL, MSG_TYPE_SKILL_FAILURE,
01169                           "There is nothing to attack!", NULL);
01170         return 0;
01171     }
01172 
01173     return do_skill_attack(tmp, pl, string, skill);
01174 }
01175 
01196 static int attack_hth(object *pl, int dir, const char *string, object *skill) {
01197     object *enemy = NULL, *weapon;
01198 
01199     if (QUERY_FLAG(pl, FLAG_READY_WEAPON))
01200         for (weapon = pl->inv; weapon; weapon = weapon->below) {
01201             if (weapon->type == WEAPON && QUERY_FLAG(weapon, FLAG_APPLIED)) {
01202                 if (apply_special(pl, weapon, AP_UNAPPLY|AP_NOPRINT)) {
01203                     char weaponname[MAX_BUF];
01204 
01205                     query_name(weapon, weaponname, MAX_BUF);
01206                     draw_ext_info_format(NDI_UNIQUE, 0, pl,
01207                                          MSG_TYPE_SKILL, MSG_TYPE_SKILL_ERROR,
01208                                          "You are unable to unwield %s in order to attack with %s.",
01209                                          "You are unable to unwield %s in order to attack with %s.",
01210                                          weaponname, skill->name);
01211                     return 0;
01212                 } else {
01213                     draw_ext_info(NDI_UNIQUE, 0, pl, MSG_TYPE_SKILL, MSG_TYPE_SKILL_ERROR,
01214                                   "You unwield your weapon in order to attack.", NULL);
01215                     break;
01216                 }
01217             }
01218         }
01219     return skill_attack(enemy, pl, dir, string, skill);
01220 }
01221 
01244 static int attack_melee_weapon(object *op, int dir, const char *string, object *skill) {
01245     if (!QUERY_FLAG(op, FLAG_READY_WEAPON)) {
01246         if (op->type == PLAYER)
01247             draw_ext_info(NDI_UNIQUE, 0, op, MSG_TYPE_SKILL, MSG_TYPE_SKILL_ERROR,
01248                           "You have no ready weapon to attack with!", NULL);
01249         return 0;
01250     }
01251     return skill_attack(NULL, op, dir, string, skill);
01252 }