Crossfire Server, Branch 1.12
R12190
|
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 }