Crossfire Server, Branch 1.12  R12190
skills.c
Go to the documentation of this file.
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 }