Crossfire Server, Branch 1.12
R12190
|
00001 /* 00002 * static char *rcsid_skills_c = 00003 * "$Id: skills.c 18483 2012-10-29 19:00:44Z akirschbaum $"; 00004 */ 00005 /* 00006 CrossFire, A Multiplayer game for X-windows 00007 00008 Copyright (C) 2003-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 authors can be reached via e-mail to crossfire-devel@real-time.com 00026 */ 00027 00033 #include <global.h> 00034 #include <object.h> 00035 #ifndef __CEXTRACT__ 00036 #include <sproto.h> 00037 #endif 00038 #include <living.h> 00039 #include <skills.h> 00040 #include <spells.h> 00041 #include <book.h> 00042 00057 static int adj_stealchance(object *op, object *victim, int roll) { 00058 object *equip; 00059 00060 if (!op || !victim || !roll) 00061 return -1; 00062 00063 /* Only prohibit stealing if the player does not have a free 00064 * hand available and in fact does have hands. 00065 */ 00066 if (op->type == PLAYER 00067 && op->body_used[BODY_ARMS] <= 0 00068 && op->body_info[BODY_ARMS]) { 00069 draw_ext_info(NDI_UNIQUE, 0, op, MSG_TYPE_SKILL, MSG_TYPE_SKILL_FAILURE, 00070 "But you have no free hands to steal with!", NULL); 00071 return -1; 00072 } 00073 00074 /* ADJUSTMENTS */ 00075 00076 /* Its harder to steal from hostile beings! */ 00077 if (!QUERY_FLAG(victim, FLAG_UNAGGRESSIVE)) 00078 roll = roll/2; 00079 00080 /* Easier to steal from sleeping beings, or if the thief is 00081 * unseen */ 00082 if (QUERY_FLAG(victim, FLAG_SLEEP)) 00083 roll = roll*3; 00084 else if (op->invisible) 00085 roll = roll*2; 00086 00087 /* check stealing 'encumberance'. Having this equipment applied makes 00088 * it quite a bit harder to steal. 00089 */ 00090 for (equip = op->inv; equip; equip = equip->below) { 00091 if (equip->type == WEAPON && QUERY_FLAG(equip, FLAG_APPLIED)) { 00092 roll -= equip->weight/10000; 00093 } 00094 if (equip->type == BOW && QUERY_FLAG(equip, FLAG_APPLIED)) 00095 roll -= equip->weight/5000; 00096 if (equip->type == SHIELD && QUERY_FLAG(equip, FLAG_APPLIED)) { 00097 roll -= equip->weight/2000; 00098 } 00099 if (equip->type == ARMOUR && QUERY_FLAG(equip, FLAG_APPLIED)) 00100 roll -= equip->weight/5000; 00101 if (equip->type == GLOVES && QUERY_FLAG(equip, FLAG_APPLIED)) 00102 roll -= equip->weight/100; 00103 } 00104 if (roll < 0) 00105 roll = 0; 00106 return roll; 00107 } 00108 00127 static int attempt_steal(object *op, object *who, object *skill) { 00128 object *success = NULL, *tmp = NULL, *next; 00129 int roll = 0, chance = 0, stats_value; 00130 rv_vector rv; 00131 char name[MAX_BUF]; 00132 00133 stats_value = ((who->stats.Dex+who->stats.Int)*3)/2; 00134 00135 /* if the victim is aware of a thief in the area (FLAG_NO_STEAL set on them) 00136 * they will try to prevent stealing if they can. Only unseen theives will 00137 * have much chance of success. 00138 */ 00139 if (op->type != PLAYER && QUERY_FLAG(op, FLAG_NO_STEAL)) { 00140 if (can_detect_enemy(op, who, &rv)) { 00141 npc_call_help(op); 00142 CLEAR_FLAG(op, FLAG_UNAGGRESSIVE); 00143 draw_ext_info(NDI_UNIQUE, 0, who, MSG_TYPE_SKILL, MSG_TYPE_SKILL_FAILURE, 00144 "Your attempt is prevented!", NULL); 00145 return 0; 00146 } else /* help npc to detect thief next time by raising its wisdom */ 00147 op->stats.Wis += (op->stats.Int/5)+1; 00148 if (op->stats.Wis > MAX_STAT) 00149 op->stats.Wis = MAX_STAT; 00150 } 00151 if (op->type == PLAYER && QUERY_FLAG(op, FLAG_WIZ)) { 00152 draw_ext_info(NDI_UNIQUE, 0, who, MSG_TYPE_SKILL, MSG_TYPE_SKILL_FAILURE, 00153 "You can't steal from the dungeon master!", NULL); 00154 return 0; 00155 } 00156 if (op->type == PLAYER && who->type == PLAYER && settings.no_player_stealing) { 00157 draw_ext_info(NDI_UNIQUE, 0, who, MSG_TYPE_SKILL, MSG_TYPE_SKILL_FAILURE, 00158 "You can't steal from other players!", NULL); 00159 return 0; 00160 } 00161 00162 00163 /* Ok then, go thru their inventory, stealing */ 00164 for (tmp = op->inv; tmp != NULL; tmp = next) { 00165 next = tmp->below; 00166 00167 /* you can't steal worn items, starting items, wiz stuff, 00168 * innate abilities, or items w/o a type. Generally 00169 * speaking, the invisibility flag prevents experience or 00170 * abilities from being stolen since these types are currently 00171 * always invisible objects. I was implicit here so as to prevent 00172 * future possible problems. -b.t. 00173 * Flesh items generated w/ fix_flesh_item should have FLAG_NO_STEAL 00174 * already -b.t. 00175 */ 00176 00177 if (QUERY_FLAG(tmp, FLAG_WAS_WIZ) 00178 || QUERY_FLAG(tmp, FLAG_APPLIED) 00179 || !(tmp->type) 00180 || tmp->type == EXPERIENCE 00181 || tmp->type == SPELL 00182 || QUERY_FLAG(tmp, FLAG_STARTEQUIP) 00183 || QUERY_FLAG(tmp, FLAG_NO_STEAL) 00184 || tmp->invisible) 00185 continue; 00186 00187 /* Okay, try stealing this item. Dependent on dexterity of thief, 00188 * skill level, see the adj_stealroll fctn for more detail. 00189 */ 00190 00191 roll = die_roll(2, 100, who, PREFER_LOW)/2; /* weighted 1-100 */ 00192 00193 if ((chance = adj_stealchance(who, op, (stats_value+skill->level*10-op->level*3))) == -1) 00194 return 0; 00195 else if (roll < chance) { 00196 tag_t tmp_count = tmp->count; 00197 00198 pick_up(who, tmp); 00199 /* need to see if the player actually stole this item - 00200 * if it is in the players inv, assume it is. This prevents 00201 * abuses where the player can not carry the item, so just 00202 * keeps stealing it over and over. 00203 */ 00204 if (was_destroyed(tmp, tmp_count) || tmp->env != op) { 00205 /* for players, play_sound: steals item */ 00206 success = tmp; 00207 CLEAR_FLAG(tmp, FLAG_INV_LOCKED); 00208 } 00209 break; 00210 } 00211 } /* for loop looking for an item */ 00212 00213 if (!tmp) { 00214 query_name(op, name, MAX_BUF); 00215 draw_ext_info_format(NDI_UNIQUE, 0, who, MSG_TYPE_SKILL, MSG_TYPE_SKILL_FAILURE, 00216 "%s%s has nothing you can steal!", 00217 "%s%s has nothing you can steal!", 00218 op->type == PLAYER ? "" : "The ", name); 00219 return 0; 00220 } 00221 00222 /* If you arent high enough level, you might get something BUT 00223 * the victim will notice your stealing attempt. Ditto if you 00224 * attempt to steal something heavy off them, they're bound to notice 00225 */ 00226 00227 if ((roll >= skill->level) 00228 || !chance 00229 || (tmp && tmp->weight > (250*(random_roll(0, stats_value+skill->level*10-1, who, PREFER_LOW))))) { 00230 /* victim figures out where the thief is! */ 00231 if (who->hide) 00232 make_visible(who); 00233 00234 if (op->type != PLAYER) { 00235 /* The unaggressives look after themselves 8) */ 00236 if (who->type == PLAYER) { 00237 npc_call_help(op); 00238 query_name(op, name, MAX_BUF); 00239 draw_ext_info_format(NDI_UNIQUE, 0, who, MSG_TYPE_SKILL, MSG_TYPE_SKILL_FAILURE, 00240 "%s notices your attempted pilfering!", 00241 "%s notices your attempted pilfering!", 00242 name); 00243 } 00244 CLEAR_FLAG(op, FLAG_UNAGGRESSIVE); 00245 /* all remaining npc items are guarded now. Set flag NO_STEAL 00246 * on the victim. 00247 */ 00248 SET_FLAG(op, FLAG_NO_STEAL); 00249 } else { /* stealing from another player */ 00250 char buf[MAX_BUF]; 00251 00252 /* Notify the other player */ 00253 if (success && who->stats.Int > random_roll(0, 19, op, PREFER_LOW)) { 00254 query_name(success, name, MAX_BUF); 00255 snprintf(buf, sizeof(buf), "Your %s is missing!", name); 00256 } else { 00257 snprintf(buf, sizeof(buf), "Your pack feels strangely lighter."); 00258 } 00259 draw_ext_info(NDI_UNIQUE, 0, op, MSG_TYPE_ITEM, MSG_TYPE_ITEM_REMOVE, 00260 buf, buf); 00261 if (!success) { 00262 if (who->invisible) { 00263 snprintf(buf, sizeof(buf), "you feel itchy fingers getting at your pack."); 00264 } else { 00265 query_name(who, name, MAX_BUF); 00266 snprintf(buf, sizeof(buf), "%s looks very shifty.", name); 00267 } 00268 draw_ext_info(NDI_UNIQUE, 0, op, MSG_TYPE_VICTIM, MSG_TYPE_VICTIM_STEAL, 00269 buf, buf); 00270 } 00271 } /* else stealing from another player */ 00272 /* play_sound("stop! thief!"); kindofthing */ 00273 } /* if you weren't 100% successful */ 00274 return success ? 1 : 0; 00275 } 00276 00277 00290 int steal(object *op, int dir, object *skill) { 00291 object *tmp, *next; 00292 sint16 x, y; 00293 mapstruct *m; 00294 int mflags; 00295 00296 x = op->x+freearr_x[dir]; 00297 y = op->y+freearr_y[dir]; 00298 00299 if (dir == 0) { 00300 /* Can't steal from ourself! */ 00301 return 0; 00302 } 00303 00304 m = op->map; 00305 mflags = get_map_flags(m, &m, x, y, &x, &y); 00306 /* Out of map - can't do it. If nothing alive on this space, 00307 * don't need to look any further. 00308 */ 00309 if ((mflags&P_OUT_OF_MAP) || !(mflags&P_IS_ALIVE)) 00310 return 0; 00311 00312 /* If player can't move onto the space, can't steal from it. */ 00313 if (OB_TYPE_MOVE_BLOCK(op, GET_MAP_MOVE_BLOCK(m, x, y))) 00314 return 0; 00315 00316 /* Find the topmost object at this spot */ 00317 for (tmp = GET_MAP_OB(m, x, y); tmp != NULL && tmp->above != NULL; tmp = tmp->above) 00318 ; 00319 00320 /* For all the stacked objects at this point, attempt a steal */ 00321 for (; tmp != NULL; tmp = next) { 00322 next = tmp->below; 00323 /* Minor hack--for multi square beings - make sure we get 00324 * the 'head' coz 'tail' objects have no inventory! - b.t. 00325 */ 00326 if (tmp->head) 00327 tmp = tmp->head; 00328 00329 if (tmp->type != PLAYER && !QUERY_FLAG(tmp, FLAG_MONSTER)) 00330 continue; 00331 00332 /* do not reveal hidden DMs */ 00333 if (tmp->type == PLAYER && QUERY_FLAG(tmp, FLAG_WIZ) && tmp->contr->hidden) 00334 continue; 00335 if (attempt_steal(tmp, op, skill)) { 00336 if (tmp->type == PLAYER) /* no xp for stealing from another player */ 00337 return 0; 00338 00339 /* no xp for stealing from pets (of players) */ 00340 if (QUERY_FLAG(tmp, FLAG_FRIENDLY) && tmp->attack_movement == PETMOVE) { 00341 object *owner = get_owner(tmp); 00342 if (owner != NULL && owner->type == PLAYER) 00343 return 0; 00344 } 00345 00346 return (calc_skill_exp(op, tmp, skill)); 00347 } 00348 } 00349 return 0; 00350 } 00351 00366 static int attempt_pick_lock(object *door, object *pl, object *skill) { 00367 int difficulty = pl->map->difficulty ? pl->map->difficulty : 0; 00368 int success = 0, number; /* did we get anything? */ 00369 00370 /* Try to pick the lock on this item (doors only for now). 00371 * Dependent on dexterity/skill SK_level of the player and 00372 * the map level difficulty. 00373 */ 00374 number = (die_roll(2, 40, pl, PREFER_LOW)-2)/2; 00375 if (number < (pl->stats.Dex+skill->level-difficulty)) { 00376 remove_door(door); 00377 success = 1; 00378 } else if (door->inv && (door->inv->type == RUNE || door->inv->type == TRAP)) { /* set off any traps? */ 00379 spring_trap(door->inv, pl); 00380 } 00381 return success; 00382 } 00383 00384 00400 int pick_lock(object *pl, int dir, object *skill) { 00401 object *tmp; 00402 int x = pl->x+freearr_x[dir]; 00403 int y = pl->y+freearr_y[dir]; 00404 00405 if (!dir) 00406 dir = pl->facing; 00407 00408 /* For all the stacked objects at this point find a door*/ 00409 if (out_of_map(pl->map, x, y)) { 00410 draw_ext_info(NDI_UNIQUE, 0, pl, MSG_TYPE_SKILL, MSG_TYPE_SKILL_ERROR, 00411 "There is no lock there.", NULL); 00412 return 0; 00413 } 00414 00415 for (tmp = GET_MAP_OB(pl->map, x, y); tmp; tmp = tmp->above) 00416 if (tmp->type == DOOR || tmp->type == LOCKED_DOOR) 00417 break; 00418 00419 if (!tmp) { 00420 draw_ext_info(NDI_UNIQUE, 0, pl, MSG_TYPE_SKILL, MSG_TYPE_SKILL_ERROR, 00421 "There is no lock there.", NULL); 00422 return 0; 00423 } 00424 if (tmp->type == LOCKED_DOOR) { 00425 draw_ext_info(NDI_UNIQUE, 0, pl, MSG_TYPE_SKILL, MSG_TYPE_SKILL_ERROR, 00426 "You can't pick that lock!", NULL); 00427 return 0; 00428 } 00429 00430 if (!tmp->move_block) { 00431 draw_ext_info(NDI_UNIQUE, 0, pl, MSG_TYPE_SKILL, MSG_TYPE_SKILL_ERROR, 00432 "The door has no lock!", NULL); 00433 return 0; 00434 } 00435 00436 if (attempt_pick_lock(tmp, pl, skill)) { 00437 draw_ext_info(NDI_UNIQUE, 0, pl, MSG_TYPE_SKILL, MSG_TYPE_SKILL_SUCCESS, 00438 "You pick the lock.", NULL); 00439 return calc_skill_exp(pl, NULL, skill); 00440 } else { 00441 draw_ext_info(NDI_UNIQUE, 0, pl, MSG_TYPE_SKILL, MSG_TYPE_SKILL_FAILURE, 00442 "You fail to pick the lock.", NULL); 00443 return 0; 00444 } 00445 } 00446 00447 00467 static int attempt_hide(object *op, object *skill) { 00468 int number, difficulty = op->map->difficulty; 00469 int terrain = hideability(op); 00470 00471 if (terrain < -10) /* not enough cover here */ 00472 return 0; 00473 00474 /* Hiding success and duration dependant on skill level, 00475 * op->stats.Dex, map difficulty and terrain. 00476 */ 00477 00478 number = (die_roll(2, 25, op, PREFER_LOW)-2)/2; 00479 if (!stand_near_hostile(op) && (number < (op->stats.Dex+skill->level+terrain-difficulty))) { 00480 op->invisible += 100; /* set the level of 'hiddeness' */ 00481 if (op->type == PLAYER) 00482 op->contr->tmp_invis = 1; 00483 op->hide = 1; 00484 return 1; 00485 } 00486 return 0; 00487 } 00488 00498 int hide(object *op, object *skill) { 00499 /* the preliminaries -- Can we really hide now? */ 00500 /* this keeps monsters from using invisibilty spells and hiding */ 00501 00502 if (QUERY_FLAG(op, FLAG_MAKE_INVIS)) { 00503 draw_ext_info(NDI_UNIQUE, 0, op, MSG_TYPE_SKILL, MSG_TYPE_SKILL_ERROR, 00504 "You don't need to hide while invisible!", NULL); 00505 return 0; 00506 } else if (!op->hide && op->invisible > 0 && op->type == PLAYER) { 00507 draw_ext_info(NDI_UNIQUE, 0, op, MSG_TYPE_ATTRIBUTE, MSG_TYPE_ATTRIBUTE_GOOD_EFFECT_END, 00508 "Your attempt to hide breaks the invisibility spell!", NULL); 00509 make_visible(op); 00510 } 00511 00512 if (op->invisible > (50*skill->level)) { 00513 draw_ext_info(NDI_UNIQUE, 0, op, MSG_TYPE_SKILL, MSG_TYPE_SKILL_ERROR, 00514 "You are as hidden as you can get.", NULL); 00515 return 0; 00516 } 00517 00518 if (attempt_hide(op, skill)) { 00519 draw_ext_info(NDI_UNIQUE, 0, op, MSG_TYPE_SKILL, MSG_TYPE_SKILL_SUCCESS, 00520 "You hide in the shadows.", NULL); 00521 update_object(op, UP_OBJ_FACE); 00522 return calc_skill_exp(op, NULL, skill); 00523 } 00524 draw_ext_info(NDI_UNIQUE, 0, op, MSG_TYPE_SKILL, MSG_TYPE_SKILL_FAILURE, 00525 "You fail to conceal yourself.", NULL); 00526 return 0; 00527 } 00528 00529 00536 static void stop_jump(object *pl) { 00537 fix_object(pl); 00538 insert_ob_in_map(pl, pl->map, pl, 0); 00539 } 00540 00554 static int attempt_jump(object *pl, int dir, int spaces, object *skill) { 00555 object *tmp; 00556 int i, exp = 0, dx = freearr_x[dir], dy = freearr_y[dir], mflags; 00557 sint16 x, y; 00558 mapstruct *m; 00559 00560 /* Jump loop. Go through spaces opject wants to jump. Halt the 00561 * jump if a wall or creature is in the way. We set FLY_LOW 00562 * temporarily to allow player to aviod exits/archs that are not 00563 * move_on/off fly_low. This will also prevent pickup of objects 00564 * while jumping over them. 00565 */ 00566 00567 remove_ob(pl); 00568 00569 /* 00570 * I don't think this is actually needed - all the movement 00571 * code is handled in this function, and I don't see anyplace 00572 * that cares about the move_type being flying. 00573 */ 00574 pl->move_type |= MOVE_FLY_LOW; 00575 00576 for (i = 0; i <= spaces; i++) { 00577 x = pl->x+dx; 00578 y = pl->y+dy; 00579 m = pl->map; 00580 00581 mflags = get_map_flags(m, &m, x, y, &x, &y); 00582 00583 if (mflags&P_OUT_OF_MAP) { 00584 (void)stop_jump(pl); 00585 return 0; 00586 } 00587 if (OB_TYPE_MOVE_BLOCK(pl, GET_MAP_MOVE_BLOCK(m, x, y))) { 00588 draw_ext_info(NDI_UNIQUE, 0, pl, MSG_TYPE_SKILL, MSG_TYPE_SKILL_FAILURE, 00589 "Your jump is blocked.", NULL); 00590 stop_jump(pl); 00591 return 0; 00592 } 00593 00594 for (tmp = GET_MAP_OB(m, x, y); tmp; tmp = tmp->above) { 00595 /* Jump into creature */ 00596 if (QUERY_FLAG(tmp, FLAG_MONSTER) 00597 || (tmp->type == PLAYER && (!QUERY_FLAG(tmp, FLAG_WIZ) || !tmp->contr->hidden))) { 00598 draw_ext_info_format(NDI_UNIQUE, 0, pl, MSG_TYPE_SKILL, MSG_TYPE_SKILL_SUCCESS, 00599 "You jump into %s%s.", 00600 "You jump into %s%s.", 00601 tmp->type == PLAYER ? "" : "the ", tmp->name); 00602 00603 if (tmp->type != PLAYER 00604 || (pl->type == PLAYER && pl->contr->party == NULL) 00605 || (pl->type == PLAYER && tmp->type == PLAYER && pl->contr->party != tmp->contr->party)) 00606 exp = skill_attack(tmp, pl, pl->facing, "kicked", skill); /* pl makes an attack */ 00607 00608 stop_jump(pl); 00609 return exp; /* note that calc_skill_exp() is already called by skill_attack() */ 00610 } 00611 /* If the space has fly on set (no matter what the space is), 00612 * we should get the effects - after all, the player is 00613 * effectively flying. 00614 */ 00615 if (tmp->move_on&MOVE_FLY_LOW) { 00616 pl->x = x; 00617 pl->y = y; 00618 pl->map = m; 00619 if (pl->contr) 00620 esrv_map_scroll(&pl->contr->socket, dx, dy); 00621 stop_jump(pl); 00622 return calc_skill_exp(pl, NULL, skill); 00623 } 00624 } 00625 pl->x = x; 00626 pl->y = y; 00627 pl->map = m; 00628 if (pl->contr) 00629 esrv_map_scroll(&pl->contr->socket, dx, dy); 00630 } 00631 stop_jump(pl); 00632 return calc_skill_exp(pl, NULL, skill); 00633 } 00634 00651 int jump(object *pl, int dir, object *skill) { 00652 int spaces = 0, stats; 00653 int str = pl->stats.Str; 00654 int dex = pl->stats.Dex; 00655 00656 dex = dex ? dex : 15; 00657 str = str ? str : 10; 00658 00659 stats = str*str*str*dex*skill->level; 00660 00661 if (pl->carrying != 0) /* don't want div by zero !! */ 00662 spaces = (int)(stats/pl->carrying); 00663 else 00664 spaces = 2; /* pl has no objects - gets the far jump */ 00665 00666 if (spaces > 2) 00667 spaces = 2; 00668 else if (spaces == 0) { 00669 draw_ext_info(NDI_UNIQUE, 0, pl, MSG_TYPE_SKILL, MSG_TYPE_SKILL_FAILURE, 00670 "You are carrying too much weight to jump.", NULL); 00671 return 0; 00672 } 00673 return attempt_jump(pl, dir, spaces, skill); 00674 } 00675 00686 static int do_skill_detect_curse(object *pl, object *skill) { 00687 object *tmp; 00688 int success = 0; 00689 00690 for (tmp = pl->inv; tmp; tmp = tmp->below) 00691 if (!tmp->invisible 00692 && !QUERY_FLAG(tmp, FLAG_IDENTIFIED) 00693 && !QUERY_FLAG(tmp, FLAG_KNOWN_CURSED) 00694 && (QUERY_FLAG(tmp, FLAG_CURSED) || QUERY_FLAG(tmp, FLAG_DAMNED)) 00695 && tmp->item_power < skill->level) { 00696 SET_FLAG(tmp, FLAG_KNOWN_CURSED); 00697 esrv_update_item(UPD_FLAGS, pl, tmp); 00698 success += calc_skill_exp(pl, tmp, skill); 00699 } 00700 00701 /* Check ground, too, but only objects the player could pick up. Cauldrons are exceptions, 00702 * you definitely want to know if they are cursed */ 00703 for (tmp = GET_MAP_OB(pl->map, pl->x, pl->y); tmp; tmp = tmp->above) 00704 if ((can_pick(pl, tmp) || QUERY_FLAG(tmp, FLAG_IS_CAULDRON)) 00705 && !QUERY_FLAG(tmp, FLAG_IDENTIFIED) 00706 && !QUERY_FLAG(tmp, FLAG_KNOWN_CURSED) 00707 && (QUERY_FLAG(tmp, FLAG_CURSED) || QUERY_FLAG(tmp, FLAG_DAMNED)) 00708 && tmp->item_power < skill->level) { 00709 SET_FLAG(tmp, FLAG_KNOWN_CURSED); 00710 esrv_update_item(UPD_FLAGS, pl, tmp); 00711 success += calc_skill_exp(pl, tmp, skill); 00712 } 00713 00714 return success; 00715 } 00716 00727 static int do_skill_detect_magic(object *pl, object *skill) { 00728 object *tmp; 00729 int success = 0; 00730 00731 for (tmp = pl->inv; tmp; tmp = tmp->below) 00732 if (!tmp->invisible 00733 && !QUERY_FLAG(tmp, FLAG_IDENTIFIED) 00734 && !QUERY_FLAG(tmp, FLAG_KNOWN_MAGICAL) 00735 && (is_magical(tmp)) 00736 && tmp->item_power < skill->level) { 00737 SET_FLAG(tmp, FLAG_KNOWN_MAGICAL); 00738 esrv_update_item(UPD_FLAGS, pl, tmp); 00739 success += calc_skill_exp(pl, tmp, skill); 00740 } 00741 00742 /* Check ground, too, but like above, only if the object can be picked up*/ 00743 for (tmp = GET_MAP_OB(pl->map, pl->x, pl->y); tmp; tmp = tmp->above) 00744 if (can_pick(pl, tmp) 00745 && !QUERY_FLAG(tmp, FLAG_IDENTIFIED) 00746 && !QUERY_FLAG(tmp, FLAG_KNOWN_MAGICAL) 00747 && (is_magical(tmp)) && tmp->item_power < skill->level) { 00748 SET_FLAG(tmp, FLAG_KNOWN_MAGICAL); 00749 esrv_update_item(UPD_FLAGS, pl, tmp); 00750 success += calc_skill_exp(pl, tmp, skill); 00751 } 00752 00753 return success; 00754 } 00755 00771 static int do_skill_ident2(object *tmp, object *pl, int obj_class, object *skill) { 00772 int success = 0, chance, ip; 00773 int skill_value = skill->level*pl->stats.Int ? pl->stats.Int : 10; 00774 00775 if (!QUERY_FLAG(tmp, FLAG_IDENTIFIED) 00776 && !QUERY_FLAG(tmp, FLAG_NO_SKILL_IDENT) 00777 && need_identify(tmp) 00778 && !tmp->invisible 00779 && tmp->type == obj_class) { 00780 ip = tmp->magic; 00781 if (tmp->item_power > ip) 00782 ip = tmp->item_power; 00783 00784 chance = die_roll(3, 10, pl, PREFER_LOW)-3+rndm(0, (tmp->magic ? tmp->magic*5 : 1)-1); 00785 if (skill_value >= chance) { 00786 identify(tmp); 00787 if (pl->type == PLAYER) { 00788 char desc[MAX_BUF]; 00789 00790 draw_ext_info_format(NDI_UNIQUE, 0, pl, MSG_TYPE_SKILL, MSG_TYPE_SKILL_SUCCESS, 00791 "You identify %s.", 00792 "You identify %s.", 00793 ob_describe(tmp, pl, desc, sizeof(desc))); 00794 if (tmp->msg) { 00795 draw_ext_info_format(NDI_UNIQUE, 0, pl, MSG_TYPE_ITEM, MSG_TYPE_ITEM_INFO, 00796 "The item has a story:\n%s", 00797 "The item has a story:\n%s", 00798 tmp->msg); 00799 00800 } 00801 } 00802 success += calc_skill_exp(pl, tmp, skill); 00803 } else 00804 SET_FLAG(tmp, FLAG_NO_SKILL_IDENT); 00805 } 00806 return success; 00807 } 00808 00821 static int do_skill_ident(object *pl, int obj_class, object *skill) { 00822 object *tmp; 00823 int success = 0, area, i; 00824 00825 /* check the player */ 00826 for (tmp = pl->inv; tmp; tmp = tmp->below) 00827 success += do_skill_ident2(tmp, pl, obj_class, skill); 00828 00829 /* check the ground */ 00830 /* Altered to allow ident skills to increase in area with 00831 * experience. -- Aaron Baugher 00832 */ 00833 00834 if (skill->level > 64) { /* Adjust these levels? */ 00835 area = 49; 00836 } else if (skill->level > 16) { 00837 area = 25; 00838 } else if (skill->level > 4) { 00839 area = 9; 00840 } else { 00841 area = 1; 00842 } 00843 00844 for (i = 0; i < area; i++) { 00845 sint16 x = pl->x+freearr_x[i]; 00846 sint16 y = pl->y+freearr_y[i]; 00847 mapstruct *m = pl->map; 00848 int mflags; 00849 00850 mflags = get_map_flags(m, &m, x, y, &x, &y); 00851 if (mflags&P_OUT_OF_MAP) 00852 continue; 00853 00854 if (can_see_monsterP(m, pl->x, pl->y, i)) { 00855 for (tmp = GET_MAP_OB(m, x, y); tmp; tmp = tmp->above) { 00856 success += do_skill_ident2(tmp, pl, obj_class, skill); 00857 } 00858 } 00859 } 00860 return success; 00861 } 00862 00872 int skill_ident(object *pl, object *skill) { 00873 int success = 0; 00874 00875 if (pl->type != PLAYER) 00876 return 0; /* only players will skill-identify */ 00877 00878 draw_ext_info(NDI_UNIQUE, 0, pl, MSG_TYPE_SKILL, MSG_TYPE_SKILL_SUCCESS, 00879 "You look at the objects nearby...", NULL); 00880 00881 switch (skill->subtype) { 00882 case SK_SMITHERY: 00883 success += do_skill_ident(pl, WEAPON, skill) 00884 +do_skill_ident(pl, ARMOUR, skill) 00885 +do_skill_ident(pl, BRACERS, skill) 00886 +do_skill_ident(pl, CLOAK, skill) 00887 +do_skill_ident(pl, BOOTS, skill) 00888 +do_skill_ident(pl, SHIELD, skill) 00889 +do_skill_ident(pl, GIRDLE, skill) 00890 +do_skill_ident(pl, HELMET, skill) 00891 +do_skill_ident(pl, GLOVES, skill); 00892 break; 00893 00894 case SK_BOWYER: 00895 success += do_skill_ident(pl, BOW, skill) 00896 +do_skill_ident(pl, ARROW, skill); 00897 break; 00898 00899 case SK_ALCHEMY: 00900 success += do_skill_ident(pl, POTION, skill) 00901 +do_skill_ident(pl, POISON, skill) 00902 +do_skill_ident(pl, CONTAINER, skill) 00903 +do_skill_ident(pl, DRINK, skill) 00904 +do_skill_ident(pl, INORGANIC, skill); 00905 break; 00906 00907 case SK_WOODSMAN: 00908 success += do_skill_ident(pl, FOOD, skill) 00909 +do_skill_ident(pl, DRINK, skill) 00910 +do_skill_ident(pl, FLESH, skill); 00911 break; 00912 00913 case SK_JEWELER: 00914 success += do_skill_ident(pl, GEM, skill) 00915 +do_skill_ident(pl, RING, skill) 00916 +do_skill_ident(pl, AMULET, skill); 00917 break; 00918 00919 case SK_LITERACY: 00920 success += do_skill_ident(pl, SPELLBOOK, skill) 00921 +do_skill_ident(pl, SCROLL, skill) 00922 +do_skill_ident(pl, BOOK, skill); 00923 break; 00924 00925 case SK_THAUMATURGY: 00926 success += do_skill_ident(pl, WAND, skill) 00927 +do_skill_ident(pl, ROD, skill) 00928 +do_skill_ident(pl, HORN, skill); 00929 break; 00930 00931 case SK_DET_CURSE: 00932 success = do_skill_detect_curse(pl, skill); 00933 if (success) 00934 draw_ext_info(NDI_UNIQUE, 0, pl, MSG_TYPE_SKILL, MSG_TYPE_SKILL_SUCCESS, 00935 "...and discover cursed items!", NULL); 00936 break; 00937 00938 case SK_DET_MAGIC: 00939 success = do_skill_detect_magic(pl, skill); 00940 if (success) 00941 draw_ext_info(NDI_UNIQUE, 0, pl, MSG_TYPE_SKILL, MSG_TYPE_SKILL_SUCCESS, 00942 "...and discover items imbued with mystic forces!", NULL); 00943 break; 00944 00945 default: 00946 LOG(llevError, "Error: bad call to skill_ident()\n"); 00947 return 0; 00948 break; 00949 } 00950 if (!success) { 00951 draw_ext_info(NDI_UNIQUE, 0, pl, MSG_TYPE_SKILL, MSG_TYPE_SKILL_SUCCESS, 00952 "...and learn nothing more.", NULL); 00953 } 00954 return success; 00955 } 00956 00957 00976 int use_oratory(object *pl, int dir, object *skill) { 00977 sint16 x = pl->x+freearr_x[dir], y = pl->y+freearr_y[dir]; 00978 int mflags, chance; 00979 object *tmp; 00980 mapstruct *m; 00981 char name[MAX_BUF]; 00982 00983 if (pl->type != PLAYER) 00984 return 0; /* only players use this skill */ 00985 m = pl->map; 00986 mflags = get_map_flags(m, &m, x, y, &x, &y); 00987 if (mflags&P_OUT_OF_MAP) 00988 return 0; 00989 00990 /* Save some processing - we have the flag already anyways 00991 */ 00992 if (!(mflags&P_IS_ALIVE)) { 00993 draw_ext_info(NDI_UNIQUE, 0, pl, MSG_TYPE_SKILL, MSG_TYPE_SKILL_FAILURE, 00994 "There is nothing to orate to.", NULL); 00995 return 0; 00996 } 00997 00998 for (tmp = GET_MAP_OB(m, x, y); tmp; tmp = tmp->above) { 00999 /* can't persuade players - return because there is nothing else 01000 * on that space to charm. Same for multi space monsters and 01001 * special monsters - we don't allow them to be charmed, and there 01002 * is no reason to do further processing since they should be the 01003 * only monster on the space. 01004 */ 01005 if (tmp->type == PLAYER) 01006 return 0; 01007 if (tmp->more || tmp->head) 01008 return 0; 01009 if (tmp->msg) 01010 return 0; 01011 01012 if (QUERY_FLAG(tmp, FLAG_MONSTER)) 01013 break; 01014 } 01015 01016 if (!tmp) { 01017 draw_ext_info(NDI_UNIQUE, 0, pl, MSG_TYPE_SKILL, MSG_TYPE_SKILL_FAILURE, 01018 "There is nothing to orate to.", NULL); 01019 return 0; 01020 } 01021 01022 query_name(tmp, name, MAX_BUF); 01023 draw_ext_info_format(NDI_UNIQUE, 0, pl, MSG_TYPE_SKILL, MSG_TYPE_SKILL_SUCCESS, 01024 "You orate to the %s.", 01025 "You orate to the %s.", 01026 name); 01027 01028 /* the following conditions limit who may be 'charmed' */ 01029 01030 /* it's hostile! */ 01031 if (!QUERY_FLAG(tmp, FLAG_UNAGGRESSIVE) && !QUERY_FLAG(tmp, FLAG_FRIENDLY)) { 01032 query_name(tmp, name, MAX_BUF); 01033 draw_ext_info_format(NDI_UNIQUE, 0, pl, MSG_TYPE_SKILL, MSG_TYPE_SKILL_FAILURE, 01034 "Too bad the %s isn't listening!", 01035 "Too bad the %s isn't listening!", 01036 name); 01037 return 0; 01038 } 01039 01040 /* it's already allied! */ 01041 if (QUERY_FLAG(tmp, FLAG_FRIENDLY) && (tmp->attack_movement == PETMOVE)) { 01042 if (get_owner(tmp) == pl) { 01043 draw_ext_info(NDI_UNIQUE, 0, pl, MSG_TYPE_SKILL, MSG_TYPE_SKILL_SUCCESS, 01044 "Your follower loves your speech.", NULL); 01045 return 0; 01046 } else if (skill->level > tmp->level) { 01047 /* you steal the follower. Perhaps we should really look at the 01048 * level of the owner above? 01049 */ 01050 set_owner(tmp, pl); 01051 query_name(tmp, name, MAX_BUF); 01052 draw_ext_info_format(NDI_UNIQUE, 0, pl, MSG_TYPE_SKILL, MSG_TYPE_SKILL_SUCCESS, 01053 "You convince the %s to follow you instead!", 01054 "You convince the %s to follow you instead!", 01055 name); 01056 01057 /* Abuse fix - don't give exp since this can otherwise 01058 * be used by a couple players to gets lots of exp. 01059 */ 01060 return 0; 01061 } else { 01062 /* In this case, you can't steal it from the other player */ 01063 return 0; 01064 } 01065 } /* Creature was already a pet of someone */ 01066 01067 chance = skill->level*2+(pl->stats.Cha-2*tmp->stats.Int)/2; 01068 01069 /* Ok, got a 'sucker' lets try to make them a follower */ 01070 if (chance > 0 && tmp->level < (random_roll(0, chance-1, pl, PREFER_HIGH)-1)) { 01071 query_name(tmp, name, MAX_BUF); 01072 draw_ext_info_format(NDI_UNIQUE, 0, pl, MSG_TYPE_SKILL, MSG_TYPE_SKILL_SUCCESS, 01073 "You convince the %s to become your follower.", 01074 "You convince the %s to become your follower.", 01075 name); 01076 01077 set_owner(tmp, pl); 01078 tmp->stats.exp = 0; 01079 add_friendly_object(tmp); 01080 SET_FLAG(tmp, FLAG_FRIENDLY); 01081 tmp->attack_movement = PETMOVE; 01082 FREE_AND_COPY(tmp->skill, skill->skill); 01083 return calc_skill_exp(pl, tmp, skill); 01084 } 01085 /* Charm failed. Creature may be angry now */ 01086 else if ((skill->level+((pl->stats.Cha-10)/2)) < random_roll(1, 2*tmp->level, pl, PREFER_LOW)) { 01087 query_name(tmp, name, MAX_BUF); 01088 draw_ext_info_format(NDI_UNIQUE, 0, pl, MSG_TYPE_SKILL, MSG_TYPE_SKILL_FAILURE, 01089 "Your speech angers the %s!", 01090 "Your speech angers the %s!", 01091 name); 01092 01093 if (QUERY_FLAG(tmp, FLAG_FRIENDLY)) { 01094 CLEAR_FLAG(tmp, FLAG_FRIENDLY); 01095 remove_friendly_object(tmp); 01096 tmp->attack_movement = 0; /* needed? */ 01097 } 01098 CLEAR_FLAG(tmp, FLAG_UNAGGRESSIVE); 01099 } 01100 return 0; /* Fall through - if we get here, we didn't charm anything */ 01101 } 01102 01123 int singing(object *pl, int dir, object *skill) { 01124 int i, exp = 0, chance, mflags; 01125 object *tmp; 01126 mapstruct *m; 01127 sint16 x, y; 01128 char name[MAX_BUF]; 01129 01130 if (pl->type != PLAYER) 01131 return 0; /* only players use this skill */ 01132 01133 draw_ext_info_format(NDI_UNIQUE, 0, pl, MSG_TYPE_SKILL, MSG_TYPE_SKILL_SUCCESS, 01134 "You sing", NULL); 01135 for (i = dir; i < (dir+MIN(skill->level, SIZEOFFREE)); i++) { 01136 x = pl->x+freearr_x[i]; 01137 y = pl->y+freearr_y[i]; 01138 m = pl->map; 01139 01140 mflags = get_map_flags(m, &m, x, y, &x, &y); 01141 if (mflags&P_OUT_OF_MAP) 01142 continue; 01143 if (!(mflags&P_IS_ALIVE)) 01144 continue; 01145 01146 for (tmp = GET_MAP_OB(m, x, y); tmp; tmp = tmp->above) { 01147 if (QUERY_FLAG(tmp, FLAG_MONSTER)) 01148 break; 01149 /* can't affect players */ 01150 if (tmp->type == PLAYER) 01151 break; 01152 } 01153 01154 /* Whole bunch of checks to see if this is a type of monster that would 01155 * listen to singing. 01156 */ 01157 if (tmp 01158 && QUERY_FLAG(tmp, FLAG_MONSTER) 01159 && !QUERY_FLAG(tmp, FLAG_NO_STEAL) /* Been charmed or abused before */ 01160 && !QUERY_FLAG(tmp, FLAG_SPLITTING) /* no ears */ 01161 && !QUERY_FLAG(tmp, FLAG_HITBACK) /* was here before */ 01162 && (tmp->level <= skill->level) 01163 && (!tmp->head) 01164 && !QUERY_FLAG(tmp, FLAG_UNDEAD) 01165 && !QUERY_FLAG(tmp, FLAG_UNAGGRESSIVE) /* already calm */ 01166 && !QUERY_FLAG(tmp, FLAG_FRIENDLY)) { /* already calm */ 01167 /* stealing isn't really related (although, maybe it should 01168 * be). This is mainly to prevent singing to the same monster 01169 * over and over again and getting exp for it. 01170 */ 01171 chance = skill->level*2+(pl->stats.Cha-5-tmp->stats.Int)/2; 01172 if (chance && tmp->level*2 < random_roll(0, chance-1, pl, PREFER_HIGH)) { 01173 SET_FLAG(tmp, FLAG_UNAGGRESSIVE); 01174 query_name(tmp, name, MAX_BUF); 01175 draw_ext_info_format(NDI_UNIQUE, 0, pl, MSG_TYPE_SKILL, MSG_TYPE_SKILL_SUCCESS, 01176 "You calm down the %s", 01177 "You calm down the %s", 01178 name); 01179 01180 /* Give exp only if they are not aware */ 01181 if (!QUERY_FLAG(tmp, FLAG_NO_STEAL)) 01182 exp += calc_skill_exp(pl, tmp, skill); 01183 SET_FLAG(tmp, FLAG_NO_STEAL); 01184 } else { 01185 query_name(tmp, name, MAX_BUF); 01186 draw_ext_info_format(NDI_UNIQUE, 0, pl, MSG_TYPE_SKILL, MSG_TYPE_SKILL_FAILURE, 01187 "Too bad the %s isn't listening!", 01188 "Too bad the %s isn't listening!", 01189 name); 01190 SET_FLAG(tmp, FLAG_NO_STEAL); 01191 } 01192 } 01193 } 01194 return exp; 01195 } 01196 01207 int find_traps(object *pl, object *skill) { 01208 object *tmp, *tmp2; 01209 int i, expsum = 0, mflags; 01210 sint16 x, y; 01211 mapstruct *m; 01212 01213 /* First we search all around us for runes and traps, which are 01214 * all type RUNE 01215 */ 01216 01217 for (i = 0; i < 9; i++) { 01218 x = pl->x+freearr_x[i]; 01219 y = pl->y+freearr_y[i]; 01220 m = pl->map; 01221 01222 mflags = get_map_flags(m, &m, x, y, &x, &y); 01223 if (mflags&P_OUT_OF_MAP) 01224 continue; 01225 01226 /* Check everything in the square for trapness */ 01227 for (tmp = GET_MAP_OB(m, x, y); tmp != NULL; tmp = tmp->above) { 01228 /* And now we'd better do an inventory traversal of each 01229 * of these objects' inventory 01230 * We can narrow this down a bit - no reason to search through 01231 * the players inventory or monsters for that matter. 01232 */ 01233 if (tmp->type != PLAYER && !QUERY_FLAG(tmp, FLAG_MONSTER)) { 01234 for (tmp2 = tmp->inv; tmp2 != NULL; tmp2 = tmp2->below) 01235 if (tmp2->type == RUNE || tmp2->type == TRAP) 01236 if (trap_see(pl, tmp2)) { 01237 trap_show(tmp2, tmp); 01238 if (tmp2->stats.Cha > 1) { 01239 if (!tmp2->owner || tmp2->owner->type != PLAYER) 01240 expsum += calc_skill_exp(pl, tmp2, skill); 01241 01242 tmp2->stats.Cha = 1; /* unhide the trap */ 01243 } 01244 } 01245 } 01246 if ((tmp->type == RUNE || tmp->type == TRAP) && trap_see(pl, tmp)) { 01247 trap_show(tmp, tmp); 01248 if (tmp->stats.Cha > 1) { 01249 if (!tmp->owner || tmp->owner->type != PLAYER) 01250 expsum += calc_skill_exp(pl, tmp, skill); 01251 tmp->stats.Cha = 1; /* unhide the trap */ 01252 } 01253 } 01254 } 01255 } 01256 draw_ext_info(NDI_BLACK, 0, pl, MSG_TYPE_SKILL, MSG_TYPE_SKILL_SUCCESS, 01257 "You search the area.", NULL); 01258 return expsum; 01259 } 01260 01272 int remove_trap(object *op, object *skill) { 01273 object *tmp, *tmp2; 01274 int i, success = 0, mflags; 01275 mapstruct *m; 01276 sint16 x, y; 01277 01278 for (i = 0; i < 9; i++) { 01279 x = op->x+freearr_x[i]; 01280 y = op->y+freearr_y[i]; 01281 m = op->map; 01282 01283 mflags = get_map_flags(m, &m, x, y, &x, &y); 01284 if (mflags&P_OUT_OF_MAP) 01285 continue; 01286 01287 /* Check everything in the square for trapness */ 01288 for (tmp = GET_MAP_OB(m, x, y); tmp != NULL; tmp = tmp->above) { 01289 /* And now we'd better do an inventory traversal of each 01290 * of these objects inventory. Like above, only 01291 * do this for interesting objects. 01292 */ 01293 01294 if (tmp->type != PLAYER && !QUERY_FLAG(tmp, FLAG_MONSTER)) { 01295 for (tmp2 = tmp->inv; tmp2 != NULL; tmp2 = tmp2->below) 01296 if ((tmp2->type == RUNE || tmp2->type == TRAP) && tmp2->stats.Cha <= 1) { 01297 trap_show(tmp2, tmp); 01298 if (trap_disarm(op, tmp2, 1, skill) && (!tmp2->owner || tmp2->owner->type != PLAYER)) { 01299 tmp->stats.exp = tmp->stats.Cha*tmp->level; 01300 success += calc_skill_exp(op, tmp2, skill); 01301 } else { 01302 /* Can't continue to disarm after failure */ 01303 return success; 01304 } 01305 } 01306 } 01307 if ((tmp->type == RUNE || tmp->type == TRAP) && tmp->stats.Cha <= 1) { 01308 trap_show(tmp, tmp); 01309 if (trap_disarm(op, tmp, 1, skill) && (!tmp->owner || tmp->owner->type != PLAYER)) { 01310 tmp->stats.exp = tmp->stats.Cha*tmp->level; 01311 success += calc_skill_exp(op, tmp, skill); 01312 } else { 01313 /* Can't continue to disarm after failure */ 01314 return success; 01315 } 01316 } 01317 } 01318 } 01319 return success; 01320 } 01321 01322 01341 int pray(object *pl, object *skill) { 01342 char buf[MAX_BUF]; 01343 object *tmp; 01344 01345 if (pl->type != PLAYER) 01346 return 0; 01347 01348 snprintf(buf, sizeof(buf), "You pray."); 01349 01350 /* Check all objects - we could stop at floor objects, 01351 * but if someone buries an altar, I don't see a problem with 01352 * going through all the objects, and it shouldn't be much slower 01353 * than extra checks on object attributes. 01354 */ 01355 for (tmp = pl->below; tmp != NULL; tmp = tmp->below) { 01356 /* Only if the altar actually belongs to someone do you get special benefits */ 01357 if (tmp && tmp->type == HOLY_ALTAR && tmp->other_arch) { 01358 snprintf(buf, sizeof(buf), "You pray over the %s.", tmp->name); 01359 pray_at_altar(pl, tmp, skill); 01360 break; /* Only pray at one altar */ 01361 } 01362 } 01363 01364 draw_ext_info(NDI_BLACK, 0, pl, MSG_TYPE_SKILL, MSG_TYPE_SKILL_SUCCESS, 01365 buf, buf); 01366 01367 if (pl->stats.grace < pl->stats.maxgrace) { 01368 pl->stats.grace++; 01369 pl->last_grace = -1; 01370 } 01371 return 0; 01372 } 01373 01390 void meditate(object *pl, object *skill) { 01391 object *tmp; 01392 01393 if (pl->type != PLAYER) 01394 return; /* players only */ 01395 01396 /* check if pl has removed encumbering armour and weapons */ 01397 if (QUERY_FLAG(pl, FLAG_READY_WEAPON) && (skill->level < 6)) { 01398 draw_ext_info(NDI_UNIQUE, 0, pl, MSG_TYPE_SKILL, MSG_TYPE_SKILL_ERROR, 01399 "You can't concentrate while wielding a weapon!", NULL); 01400 return; 01401 } else { 01402 for (tmp = pl->inv; tmp; tmp = tmp->below) 01403 if (((tmp->type == ARMOUR && skill->level < 12) 01404 || (tmp->type == HELMET && skill->level < 10) 01405 || (tmp->type == SHIELD && skill->level < 6) 01406 || (tmp->type == BOOTS && skill->level < 4) 01407 || (tmp->type == GLOVES && skill->level < 2)) 01408 && QUERY_FLAG(tmp, FLAG_APPLIED)) { 01409 draw_ext_info(NDI_UNIQUE, 0, pl, MSG_TYPE_SKILL, MSG_TYPE_SKILL_ERROR, 01410 "You can't concentrate while wearing so much armour!", NULL); 01411 return; 01412 } 01413 } 01414 01415 /* ok let's meditate! Spell points are regained first, then once 01416 * they are maxed we get back hp. Actual incrementing of values 01417 * is handled by the do_some_living() (in player.c). This way magical 01418 * bonuses for healing/sp regeneration are included properly 01419 * No matter what, we will eat up some playing time trying to 01420 * meditate. (see 'factor' variable for what sets the amount of time) 01421 */ 01422 01423 draw_ext_info(NDI_BLACK, 0, pl, MSG_TYPE_SKILL, MSG_TYPE_SKILL_SUCCESS, 01424 "You meditate.", NULL); 01425 01426 if (pl->stats.sp < pl->stats.maxsp) { 01427 pl->stats.sp++; 01428 pl->last_sp = -1; 01429 } else if (pl->stats.hp < pl->stats.maxhp) { 01430 pl->stats.hp++; 01431 pl->last_heal = -1; 01432 } 01433 } 01434 01451 static int write_note(object *pl, object *item, const char *msg, object *skill) { 01452 char buf[BOOK_BUF]; 01453 object *newBook = NULL; 01454 01455 /* a pair of sanity checks */ 01456 if (!item || item->type != BOOK) 01457 return 0; 01458 01459 if (!msg) { 01460 draw_ext_info_format(NDI_UNIQUE, 0, pl, MSG_TYPE_SKILL, MSG_TYPE_SKILL_ERROR, 01461 "No message to write!\nUsage: use_skill %s <message>", 01462 "No message to write!\nUsage: use_skill %s <message>", 01463 skill->skill); 01464 return 0; 01465 } 01466 if (strcasestr_local(msg, "endmsg")) { 01467 draw_ext_info(NDI_UNIQUE, 0, pl, MSG_TYPE_SKILL, MSG_TYPE_SKILL_ERROR, 01468 "Trying to cheat now are we?", NULL); 01469 return 0; 01470 } 01471 01472 /* Lauwenmark: Handle for plugin book writing (trigger) event */ 01473 if (execute_event(item, EVENT_TRIGGER, pl, NULL, msg, SCRIPT_FIX_ALL) != 0) 01474 return strlen(msg); 01475 01476 buf[0] = 0; 01477 if (!book_overflow(item->msg, msg, BOOK_BUF)) { /* add msg string to book */ 01478 if (item->msg) 01479 snprintf(buf, sizeof(buf), "%s%s\n", item->msg, msg); 01480 else 01481 snprintf(buf, sizeof(buf), "%s\n", msg); 01482 01483 if (item->nrof > 1) { 01484 newBook = get_object(); 01485 copy_object(item, newBook); 01486 decrease_ob(item); 01487 newBook->nrof = 1; 01488 if (newBook->msg) 01489 free_string(newBook->msg); 01490 newBook->msg = add_string(buf); 01491 newBook = insert_ob_in_ob(newBook, pl); 01492 } else { 01493 if (item->msg) 01494 free_string(item->msg); 01495 item->msg = add_string(buf); 01496 /* This shouldn't be necessary - the object hasn't changed in any 01497 * visible way 01498 */ 01499 /* esrv_send_item(pl, item);*/ 01500 } 01501 query_short_name(item, buf, BOOK_BUF); 01502 draw_ext_info_format(NDI_UNIQUE, 0, pl, MSG_TYPE_SKILL, MSG_TYPE_SKILL_SUCCESS, 01503 "You write in the %s.", 01504 "You write in the %s.", 01505 buf); 01506 return strlen(msg); 01507 } else { 01508 query_short_name(item, buf, BOOK_BUF); 01509 draw_ext_info_format(NDI_UNIQUE, 0, pl, MSG_TYPE_SKILL, MSG_TYPE_SKILL_FAILURE, 01510 "Your message won't fit in the %s!", 01511 "Your message won't fit in the %s!", 01512 buf); 01513 } 01514 return 0; 01515 } 01516 01532 static int write_scroll(object *pl, object *scroll, object *skill) { 01533 int success = 0, confused = 0, grace_cost = 0; 01534 object *newscroll, *chosen_spell, *tmp; 01535 01536 /* this is a sanity check */ 01537 if (scroll->type != SCROLL) { 01538 draw_ext_info(NDI_UNIQUE, 0, pl, MSG_TYPE_SKILL, MSG_TYPE_SKILL_ERROR, 01539 "A spell can only be inscribed into a scroll!", NULL); 01540 return 0; 01541 } 01542 01543 /* Check if we are ready to attempt inscription */ 01544 chosen_spell = pl->contr->ranges[range_magic]; 01545 if (!chosen_spell) { 01546 draw_ext_info(NDI_UNIQUE, 0, pl, MSG_TYPE_SKILL, MSG_TYPE_SKILL_ERROR, 01547 "You need a spell readied in order to inscribe!", NULL); 01548 return 0; 01549 } 01550 /* grace can become negative, we don't want a sp spell to block writing. */ 01551 grace_cost = SP_level_spellpoint_cost(pl, chosen_spell, SPELL_GRACE); 01552 if (grace_cost > 0 && grace_cost > pl->stats.grace) { 01553 draw_ext_info_format(NDI_UNIQUE, 0, pl, MSG_TYPE_SKILL, MSG_TYPE_SKILL_FAILURE, 01554 "You don't have enough grace to write a scroll of %s.", 01555 "You don't have enough grace to write a scroll of %s.", 01556 chosen_spell->name); 01557 return 0; 01558 } 01559 if (SP_level_spellpoint_cost(pl, chosen_spell, SPELL_MANA) > pl->stats.sp) { 01560 draw_ext_info_format(NDI_UNIQUE, 0, pl, MSG_TYPE_SKILL, MSG_TYPE_SKILL_FAILURE, 01561 "You don't have enough mana to write a scroll of %s.", 01562 "You don't have enough mana to write a scroll of %s.", 01563 chosen_spell->name); 01564 return 0; 01565 } 01566 /* Prevent an abuse: write a spell you're denied with, then cast it from the 01567 * written scroll - gros, 28th July 2006 01568 */ 01569 if (chosen_spell->path_attuned&pl->path_denied && settings.allow_denied_spells_writing == 0) { 01570 char name[MAX_BUF]; 01571 01572 query_name(chosen_spell, name, MAX_BUF); 01573 draw_ext_info_format(NDI_UNIQUE, 0, pl, MSG_TYPE_SKILL, MSG_TYPE_SKILL_FAILURE, 01574 "The simple idea of writing a scroll of %s makes you sick !", 01575 "The simple idea of writing a scroll of %s makes you sick !", 01576 name); 01577 return 0; 01578 } 01579 01580 /* if there is a spell already on the scroll then player could easily 01581 * accidently read it while trying to write the new one. give player 01582 * a 50% chance to overwrite spell at their own level 01583 */ 01584 if ((scroll->stats.sp || scroll->inv) 01585 && random_roll(0, scroll->level*2, pl, PREFER_LOW) > skill->level) { 01586 draw_ext_info(NDI_UNIQUE, 0, pl, MSG_TYPE_SKILL, MSG_TYPE_SKILL_FAILURE, 01587 "Oops! You accidently read it while trying to write on it.", NULL); 01588 manual_apply(pl, scroll, 0); 01589 return 0; 01590 } 01591 01592 if (execute_event(scroll, EVENT_TRIGGER, pl, chosen_spell, NULL, 0) != 0) 01593 return 0; 01594 01595 /* ok, we are ready to try inscription */ 01596 if (QUERY_FLAG(pl, FLAG_CONFUSED)) 01597 confused = 1; 01598 01599 /* Lost mana/grace no matter what */ 01600 pl->stats.grace -= SP_level_spellpoint_cost(pl, chosen_spell, SPELL_GRACE); 01601 pl->stats.sp -= SP_level_spellpoint_cost(pl, chosen_spell, SPELL_MANA); 01602 01603 if (random_roll(0, chosen_spell->level*4-1, pl, PREFER_LOW) < skill->level) { 01604 if (scroll->nrof > 1) { 01605 newscroll = get_object(); 01606 copy_object(scroll, newscroll); 01607 decrease_ob(scroll); 01608 newscroll->nrof = 1; 01609 } else { 01610 newscroll = scroll; 01611 } 01612 01613 if (!confused) { 01614 newscroll->level = MAX(skill->level, chosen_spell->level); 01615 draw_ext_info(NDI_UNIQUE, 0, pl, MSG_TYPE_SKILL, MSG_TYPE_SKILL_SUCCESS, 01616 "You succeed in writing a new scroll.", NULL); 01617 } else { 01618 chosen_spell = find_random_spell_in_ob(pl, NULL); 01619 if (!chosen_spell) 01620 return 0; 01621 01622 newscroll->level = MAX(skill->level, chosen_spell->level); 01623 draw_ext_info(NDI_UNIQUE, 0, pl, MSG_TYPE_SKILL, MSG_TYPE_SKILL_FAILURE, 01624 "In your confused state, you write down some odd spell.", NULL); 01625 } 01626 01627 if (newscroll->inv) { 01628 object *ninv; 01629 01630 ninv = newscroll->inv; 01631 remove_ob(ninv); 01632 free_object(ninv); 01633 } 01634 tmp = get_object(); 01635 copy_object(chosen_spell, tmp); 01636 insert_ob_in_ob(tmp, newscroll); 01637 /* This is needed so casting from the scroll correctly works with moving_ball types, which 01638 check attunements. */ 01639 newscroll->path_attuned = tmp->path_repelled; 01640 01641 /* Same code as from treasure.c - so they can better merge. 01642 * if players want to sell them, so be it. 01643 */ 01644 newscroll->value = newscroll->arch->clone.value*newscroll->inv->value*(newscroll->level+50)/(newscroll->inv->level+50); 01645 newscroll->stats.exp = newscroll->value/5; 01646 01647 /* wait until finished manipulating the scroll before inserting it */ 01648 if (newscroll == scroll) { 01649 /* Remove to correctly merge with other items which may exist in inventory */ 01650 remove_ob(newscroll); 01651 } 01652 newscroll = insert_ob_in_ob(newscroll, pl); 01653 success = calc_skill_exp(pl, newscroll, skill); 01654 if (!confused) 01655 success *= 2; 01656 success = success*skill->level; 01657 return success; 01658 01659 } else { /* Inscription has failed */ 01660 01661 if (chosen_spell->level > skill->level || confused) { /*backfire!*/ 01662 draw_ext_info(NDI_UNIQUE, 0, pl, MSG_TYPE_SKILL, MSG_TYPE_SKILL_FAILURE, 01663 "Ouch! Your attempt to write a new scroll strains your mind!", NULL); 01664 if (random_roll(0, 1, pl, PREFER_LOW) == 1) 01665 drain_specific_stat(pl, 4); 01666 else { 01667 confuse_living(pl, pl, 99); 01668 return (-30*chosen_spell->level); 01669 } 01670 } else if (random_roll(0, pl->stats.Int-1, pl, PREFER_HIGH) < 15) { 01671 draw_ext_info(NDI_UNIQUE, 0, pl, MSG_TYPE_SKILL, MSG_TYPE_SKILL_FAILURE, 01672 "Your attempt to write a new scroll rattles your mind!", NULL); 01673 confuse_living(pl, pl, 99); 01674 } else 01675 draw_ext_info(NDI_UNIQUE, 0, pl, MSG_TYPE_SKILL, MSG_TYPE_SKILL_FAILURE, 01676 "You fail to write a new scroll.", NULL); 01677 } 01678 return 0; 01679 } 01680 01695 int write_on_item(object *pl, const char *params, object *skill) { 01696 object *item; 01697 const char *string = params; 01698 int msgtype; 01699 archetype *skat; 01700 01701 if (pl->type != PLAYER) 01702 return 0; 01703 01704 if (!params) { 01705 params = ""; 01706 string = params; 01707 } 01708 skat = get_archetype_by_type_subtype(SKILL, SK_LITERACY); 01709 01710 /* Need to be able to read before we can write! */ 01711 if (!find_skill_by_name(pl, skat->clone.skill)) { 01712 draw_ext_info(NDI_UNIQUE, 0, pl, MSG_TYPE_SKILL, MSG_TYPE_SKILL_MISSING, 01713 "You must learn to read before you can write!", NULL); 01714 return 0; 01715 } 01716 01717 if (QUERY_FLAG(pl, FLAG_BLIND) && !QUERY_FLAG(pl, FLAG_WIZ)) { 01718 draw_ext_info(NDI_UNIQUE, 0, pl, MSG_TYPE_SKILL, MSG_TYPE_SKILL_ERROR, 01719 "You are unable to write while blind.", NULL); 01720 return 0; 01721 } 01722 01723 01724 /* if there is a message then it goes in a book and no message means 01725 * write active spell into the scroll 01726 */ 01727 msgtype = (string[0] != '\0') ? BOOK : SCROLL; 01728 01729 /* find an item of correct type to write on */ 01730 if (!(item = find_marked_object(pl))) { 01731 draw_ext_info(NDI_UNIQUE, 0, pl, MSG_TYPE_SKILL, MSG_TYPE_SKILL_ERROR, 01732 "You don't have any marked item to write on.", NULL); 01733 return 0; 01734 } 01735 01736 if (QUERY_FLAG(item, FLAG_UNPAID)) { 01737 draw_ext_info(NDI_UNIQUE, 0, pl, MSG_TYPE_SKILL, MSG_TYPE_SKILL_ERROR, 01738 "You had better pay for that before you write on it.", NULL); 01739 return 0; 01740 } 01741 if (msgtype != item->type) { 01742 draw_ext_info_format(NDI_UNIQUE, 0, pl, MSG_TYPE_SKILL, MSG_TYPE_SKILL_ERROR, 01743 "You have no %s to write %s", 01744 "You have no %s to write %s", 01745 msgtype == BOOK ? "book" : "scroll", 01746 msgtype == BOOK ? "on" : "your spell down"); 01747 return 0; 01748 } 01749 01750 if (msgtype == SCROLL) { 01751 return write_scroll(pl, item, skill); 01752 } else if (msgtype == BOOK) { 01753 return write_note(pl, item, string, skill); 01754 } 01755 return 0; 01756 } 01757 01758 01759 01777 static object *find_throw_ob(object *op, const char *request) { 01778 object *tmp; 01779 char name[MAX_BUF]; 01780 01781 if (!op) { /* safety */ 01782 LOG(llevError, "find_throw_ob(): confused! have a NULL thrower!\n"); 01783 return (object *)NULL; 01784 } 01785 01786 /* prefer marked item */ 01787 tmp = find_marked_object(op); 01788 if (tmp != NULL) { 01789 /* can't toss invisible or inv-locked items */ 01790 if (tmp->invisible || QUERY_FLAG(tmp, FLAG_INV_LOCKED)) { 01791 tmp = NULL; 01792 } 01793 } 01794 01795 /* look through the inventory */ 01796 if (tmp == NULL) { 01797 for (tmp = op->inv; tmp != NULL; tmp = tmp->below) { 01798 /* can't toss invisible or inv-locked items */ 01799 if (tmp->invisible || QUERY_FLAG(tmp, FLAG_INV_LOCKED)) 01800 continue; 01801 query_name(tmp, name, MAX_BUF); 01802 if (!request 01803 || !strcmp(name, request) 01804 || !strcmp(tmp->name, request)) 01805 break; 01806 } 01807 } 01808 01809 /* this should prevent us from throwing away 01810 * cursed items, worn armour, etc. Only weapons 01811 * can be thrown from 'hand'. 01812 */ 01813 if (!tmp) 01814 return NULL; 01815 01816 if (QUERY_FLAG(tmp, FLAG_APPLIED)) { 01817 if (tmp->type != WEAPON) { 01818 query_name(tmp, name, MAX_BUF); 01819 draw_ext_info_format(NDI_UNIQUE, 0, op, MSG_TYPE_SKILL, MSG_TYPE_SKILL_ERROR, 01820 "You can't throw %s.", 01821 "You can't throw %s.", 01822 name); 01823 tmp = NULL; 01824 } else if (QUERY_FLAG(tmp, FLAG_CURSED) || QUERY_FLAG(tmp, FLAG_DAMNED)) { 01825 query_name(tmp, name, MAX_BUF); 01826 draw_ext_info_format(NDI_UNIQUE, 0, op, 01827 MSG_TYPE_SKILL, MSG_TYPE_SKILL_FAILURE, 01828 "The %s sticks to your hand!", 01829 "The %s sticks to your hand!", 01830 name); 01831 tmp = NULL; 01832 } else { 01833 if (apply_special(op, tmp, AP_UNAPPLY|AP_NO_MERGE)) { 01834 LOG(llevError, "BUG: find_throw_ob(): couldn't unapply\n"); 01835 tmp = NULL; 01836 } 01837 } 01838 } else if (QUERY_FLAG(tmp, FLAG_UNPAID)) { 01839 query_name(tmp, name, MAX_BUF); 01840 draw_ext_info_format(NDI_UNIQUE, 0, op, MSG_TYPE_SKILL, MSG_TYPE_SKILL_ERROR, 01841 "You should pay for the %s first.", 01842 "You should pay for the %s first.", 01843 name); 01844 tmp = NULL; 01845 } 01846 01847 if (tmp && QUERY_FLAG(tmp, FLAG_INV_LOCKED)) { 01848 LOG(llevError, "BUG: find_throw_ob(): object is locked\n"); 01849 tmp = NULL; 01850 } 01851 return tmp; 01852 } 01853 01865 static object *make_throw_ob(object *orig) { 01866 object *toss_item; 01867 01868 if (!orig) 01869 return NULL; 01870 01871 toss_item = get_object(); 01872 if (QUERY_FLAG(orig, FLAG_APPLIED)) { 01873 LOG(llevError, "BUG: make_throw_ob(): ob is applied\n"); 01874 /* insufficient workaround, but better than nothing */ 01875 CLEAR_FLAG(orig, FLAG_APPLIED); 01876 } 01877 copy_object(orig, toss_item); 01878 toss_item->type = THROWN_OBJ; 01879 CLEAR_FLAG(toss_item, FLAG_CHANGING); 01880 toss_item->stats.dam = 0; /* default damage */ 01881 insert_ob_in_ob(orig, toss_item); 01882 return toss_item; 01883 } 01884 01885 01906 static int do_throw(object *op, object *part, object *toss_item, int dir, object *skill) { 01907 object *throw_ob = toss_item, *left = NULL; 01908 tag_t left_tag; 01909 int eff_str = 0, maxc, str = op->stats.Str, dam = 0; 01910 int pause_f, weight_f = 0, mflags; 01911 float str_factor = 1.0, load_factor = 1.0, item_factor = 1.0; 01912 mapstruct *m; 01913 sint16 sx, sy; 01914 tag_t tag; 01915 char name[MAX_BUF]; 01916 01917 if (throw_ob == NULL) { 01918 if (op->type == PLAYER) { 01919 draw_ext_info(NDI_UNIQUE, 0, op, MSG_TYPE_SKILL, MSG_TYPE_SKILL_ERROR, 01920 "You have nothing to throw.", NULL); 01921 } 01922 return 0; 01923 } 01924 if (QUERY_FLAG(throw_ob, FLAG_STARTEQUIP)) { 01925 if (op->type == PLAYER) { 01926 draw_ext_info(NDI_UNIQUE, 0, op, MSG_TYPE_SKILL, MSG_TYPE_SKILL_ERROR, 01927 "The gods won't let you throw that.", NULL); 01928 } 01929 return 0; 01930 } 01931 01932 /* Because throwing effectiveness must be reduced by the 01933 * encumbrance of the thrower and weight of the object. THus, 01934 * we use the concept of 'effective strength' as defined below. 01935 */ 01936 01937 /* if str exceeds MAX_STAT (30, eg giants), lets assign a str_factor > 1 */ 01938 if (str > MAX_STAT) { 01939 str_factor = (float)str/(float)MAX_STAT; 01940 str = MAX_STAT; 01941 } 01942 01943 /* the more we carry, the less we can throw. Limit only on players */ 01944 /* This logic is basically grabbed right out of fix_object() */ 01945 maxc=max_carry[str] * 1000; 01946 if (op->type == PLAYER && op->carrying > maxc) 01947 load_factor = (float)maxc/(float) op->carrying; 01948 01949 /* lighter items are thrown harder, farther, faster */ 01950 if (throw_ob->weight > 0) 01951 item_factor = (float) maxc/(float) (3.0 * throw_ob->weight); 01952 else { /* 0 or negative weight?!? Odd object, can't throw it */ 01953 query_name(throw_ob, name, MAX_BUF); 01954 draw_ext_info_format(NDI_UNIQUE, 0, op, MSG_TYPE_SKILL, MSG_TYPE_SKILL_ERROR, 01955 "You can't throw %s.", 01956 "You can't throw %s.", 01957 name); 01958 return 0; 01959 } 01960 01961 eff_str = str*MIN(load_factor, 1.0); 01962 eff_str = (float)eff_str*item_factor*str_factor; 01963 01964 /* alas, arrays limit us to a value of MAX_STAT (30). Use str_factor to 01965 * account for super-strong throwers. */ 01966 if (eff_str > MAX_STAT) 01967 eff_str = MAX_STAT; 01968 01969 #ifdef DEBUG_THROW 01970 LOG(llevDebug, "%s carries %d, eff_str=%d\n", op->name, op->carrying, eff_str); 01971 LOG(llevDebug, " max_c=%d, item_f=%f, load_f=%f, str=%d\n", (weight_limit[op->stats.Str]*FREE_PLAYER_LOAD_PERCENT), item_factor, load_factor, op->stats.Str); 01972 LOG(llevDebug, " str_factor=%f\n", str_factor); 01973 LOG(llevDebug, " item %s weight= %d\n", throw_ob->name, throw_ob->weight); 01974 #endif 01975 01976 /* 3 things here prevent a throw, you aimed at your feet, you 01977 * have no effective throwing strength, or you threw at something 01978 * that flying objects can't get through. 01979 */ 01980 mflags = get_map_flags(part->map, &m, part->x+freearr_x[dir], part->y+freearr_y[dir], &sx, &sy); 01981 01982 if (!dir 01983 || (eff_str <= 1) 01984 || (mflags&P_OUT_OF_MAP) 01985 || (GET_MAP_MOVE_BLOCK(m, sx, sy)&MOVE_FLY_LOW)) { 01986 01987 /* bounces off 'wall', and drops to feet */ 01988 remove_ob(throw_ob); 01989 throw_ob->x = part->x; throw_ob->y = part->y; 01990 insert_ob_in_map(throw_ob, part->map, op, 0); 01991 if (op->type == PLAYER) { 01992 if (eff_str <= 1) { 01993 query_name(throw_ob, name, MAX_BUF); 01994 draw_ext_info_format(NDI_UNIQUE, 0, op, MSG_TYPE_SKILL, MSG_TYPE_SKILL_FAILURE, 01995 "Your load is so heavy you drop %s to the ground.", 01996 "Your load is so heavy you drop %s to the ground.", 01997 name); 01998 } else if (!dir) { 01999 query_name(throw_ob, name, MAX_BUF); 02000 draw_ext_info_format(NDI_UNIQUE, 0, op, MSG_TYPE_SKILL, MSG_TYPE_SKILL_FAILURE, 02001 "You throw %s at the ground.", 02002 "You throw %s at the ground.", 02003 name); 02004 } else 02005 draw_ext_info(NDI_UNIQUE, 0, op, MSG_TYPE_SKILL, MSG_TYPE_SKILL_FAILURE, 02006 "Something is in the way.", NULL); 02007 } 02008 return 0; 02009 } /* if object can't be thrown */ 02010 02011 left = throw_ob; /* these are throwing objects left to the player */ 02012 /* BUG? The value in left_tag doesn't seem to be used. */ 02013 left_tag = left->count; 02014 02015 /* sometimes get_split_ob can't split an object (because op->nrof==0?) 02016 * and returns NULL. We must use 'left' then 02017 */ 02018 02019 if ((throw_ob = get_split_ob(throw_ob, 1, NULL, 0)) == NULL) { 02020 throw_ob = left; 02021 remove_ob(left); 02022 } 02023 02024 /* special case: throwing powdery substances like dust, dirt */ 02025 if (throw_ob->type == POTION && throw_ob->subtype == POT_DUST) { 02026 cast_dust(op, throw_ob, dir); 02027 return 1; 02028 } 02029 02030 /* Make a thrown object -- insert real object in a 'carrier' object. 02031 * If unsuccessfull at making the "thrown_obj", we just reinsert 02032 * the original object back into inventory and exit 02033 */ 02034 if ((toss_item = make_throw_ob(throw_ob))) { 02035 throw_ob = toss_item; 02036 if (throw_ob->skill) 02037 free_string(throw_ob->skill); 02038 throw_ob->skill = add_string(skill->skill); 02039 } else { 02040 insert_ob_in_ob(throw_ob, op); 02041 return 0; 02042 } 02043 02044 set_owner(throw_ob, op); 02045 /* At some point in the attack code, the actual real object (op->inv) 02046 * becomes the hitter. As such, we need to make sure that has a proper 02047 * owner value so exp goes to the right place. 02048 */ 02049 set_owner(throw_ob->inv, op); 02050 throw_ob->direction = dir; 02051 throw_ob->x = part->x; 02052 throw_ob->y = part->y; 02053 02054 /* the damage bonus from the force of the throw */ 02055 dam = str_factor*dam_bonus[eff_str]; 02056 02057 /* Now, lets adjust the properties of the thrown_ob. */ 02058 02059 /* how far to fly */ 02060 throw_ob->last_sp = (eff_str*3)/5; 02061 02062 /* speed */ 02063 throw_ob->speed = (speed_bonus[eff_str]+1.0)/1.5; 02064 throw_ob->speed = MIN(1.0, throw_ob->speed); /* no faster than an arrow! */ 02065 02066 /* item damage. Eff_str and item weight influence damage done */ 02067 weight_f = MIN(throw_ob->weight/2000, MAX_STAT); 02068 throw_ob->stats.dam += (dam/3)+dam_bonus[weight_f]+(throw_ob->weight/15000)-2; 02069 02070 /* chance of breaking. Proportional to force used and weight of item */ 02071 throw_ob->stats.food = (dam/2)+(throw_ob->weight/60000); 02072 02073 /* replace 25 with a call to clone.arch wc? messes up w/ NPC */ 02074 throw_ob->stats.wc = 25-dex_bonus[op->stats.Dex]-thaco_bonus[eff_str]-skill->level; 02075 02076 02077 /* the properties of objects which are meant to be thrown (ie dart, 02078 * throwing knife, etc) will differ from ordinary items. Lets tailor 02079 * this stuff in here. 02080 */ 02081 02082 if (QUERY_FLAG(throw_ob->inv, FLAG_IS_THROWN)) { 02083 throw_ob->last_sp += eff_str/3; /* fly a little further */ 02084 throw_ob->stats.dam += throw_ob->inv->stats.dam+throw_ob->magic+2; 02085 throw_ob->stats.wc -= throw_ob->magic+throw_ob->inv->stats.wc; 02086 /* only throw objects get directional faces */ 02087 if (GET_ANIM_ID(throw_ob) && NUM_ANIMATIONS(throw_ob)) 02088 SET_ANIMATION(throw_ob, dir); 02089 } else { 02090 /* some materials will adjust properties.. */ 02091 if (throw_ob->material&M_LEATHER) { 02092 throw_ob->stats.dam -= 1; 02093 throw_ob->stats.food -= 10; 02094 } 02095 if (throw_ob->material&M_GLASS) 02096 throw_ob->stats.food += 60; 02097 02098 if (throw_ob->material&M_ORGANIC) { 02099 throw_ob->stats.dam -= 3; 02100 throw_ob->stats.food += 55; 02101 } 02102 if (throw_ob->material&M_PAPER||throw_ob->material&M_CLOTH) { 02103 throw_ob->stats.dam -= 5; 02104 throw_ob->speed *= 0.8; 02105 throw_ob->stats.wc += 3; 02106 throw_ob->stats.food -= 30; 02107 } 02108 /* light obj have more wind resistance, fly slower*/ 02109 if (throw_ob->weight > 500) 02110 throw_ob->speed *= 0.8; 02111 if (throw_ob->weight > 50) 02112 throw_ob->speed *= 0.5; 02113 02114 } /* else tailor thrown object */ 02115 02116 /* some limits, and safeties (needed?) */ 02117 if (throw_ob->stats.dam < 0) 02118 throw_ob->stats.dam = 0; 02119 if (throw_ob->last_sp > eff_str) 02120 throw_ob->last_sp = eff_str; 02121 if (throw_ob->stats.food < 0) 02122 throw_ob->stats.food = 0; 02123 if (throw_ob->stats.food > 100) 02124 throw_ob->stats.food = 100; 02125 if (throw_ob->stats.wc > 30) 02126 throw_ob->stats.wc = 30; 02127 02128 /* how long to pause the thrower. Higher values mean less pause */ 02129 pause_f = ((2*eff_str)/3)+20+skill->level; 02130 02131 /* Put a lower limit on this */ 02132 if (pause_f < 10) 02133 pause_f = 10; 02134 if (pause_f > 100) 02135 pause_f = 100; 02136 02137 /* Changed in 0.94.2 - the calculation before was really goofy. 02138 * In short summary, a throw can take anywhere between speed 5 and 02139 * speed 0.5 02140 */ 02141 op->speed_left -= 50/pause_f; 02142 02143 update_ob_speed(throw_ob); 02144 throw_ob->speed_left = 0; 02145 throw_ob->map = part->map; 02146 02147 throw_ob->move_type = MOVE_FLY_LOW; 02148 throw_ob->move_on = MOVE_FLY_LOW|MOVE_WALK; 02149 02150 /* Lauwenmark - Now we can call the associated script_throw event (if any) */ 02151 execute_event(throw_ob, EVENT_THROW, op, NULL, NULL, SCRIPT_FIX_ACTIVATOR); 02152 #ifdef DEBUG_THROW 02153 LOG(llevDebug, " pause_f=%d \n", pause_f); 02154 LOG(llevDebug, " %s stats: wc=%d dam=%d dist=%d spd=%f break=%d\n", throw_ob->name, throw_ob->stats.wc, throw_ob->stats.dam, throw_ob->last_sp, throw_ob->speed, throw_ob->stats.food); 02155 LOG(llevDebug, "inserting tossitem (%d) into map\n", throw_ob->count); 02156 #endif 02157 tag = throw_ob->count; 02158 insert_ob_in_map(throw_ob, part->map, op, 0); 02159 if (!was_destroyed(throw_ob, tag)) 02160 ob_process(throw_ob); 02161 return 1; 02162 } 02163 02181 int skill_throw(object *op, object *part, int dir, const char *params, object *skill) { 02182 object *throw_ob; 02183 02184 if (op->type == PLAYER) 02185 throw_ob = find_throw_ob(op, params); 02186 else 02187 throw_ob = find_mon_throw_ob(op); 02188 02189 return do_throw(op, part, throw_ob, dir, skill); 02190 }