Crossfire Server, Branch 1.12  R12190
attack.c
Go to the documentation of this file.
00001 /*
00002  * static char *rcsid_attack_c =
00003  *   "$Id: attack.c 11578 2009-02-23 22:02:27Z lalo $";
00004  */
00005 /*
00006     CrossFire, A Multiplayer game for X-windows
00007 
00008     Copyright (C) 2002-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 
00035 #include <assert.h>
00036 #include <global.h>
00037 #include <living.h>
00038 #include <material.h>
00039 #include <skills.h>
00040 
00041 #ifndef __CEXTRACT__
00042 #include <sproto.h>
00043 #endif
00044 
00045 #include <sounds.h>
00046 
00047 /*#define ATTACK_DEBUG*/
00048 
00049 static void slow_living(object *op, object *hitter, int dam);
00050 static void deathstrike_living(object *op, object *hitter, int *dam);
00051 static int adj_attackroll(object *hitter, object *target);
00052 static int is_aimed_missile(object *op);
00053 static int did_make_save_item(object *op, int type, object *originator);
00054 static void poison_living(object *op, object *hitter, int dam);
00055 
00063 static void cancellation(object *op) {
00064     object *tmp;
00065 
00066     if (op->invisible)
00067         return;
00068 
00069     if (QUERY_FLAG(op, FLAG_ALIVE) || op->type == CONTAINER  || op->type == THROWN_OBJ) {
00070         /* Recur through the inventory */
00071         for (tmp = op->inv; tmp != NULL; tmp = tmp->below)
00072             if (!did_make_save_item(tmp, AT_CANCELLATION, op))
00073                 cancellation(tmp);
00074     } else if (FABS(op->magic) <= (rndm(0, 5))) {
00075         /* Nullify this object. This code could probably be more complete */
00076         /* in what abilities it should cancel */
00077         op->magic = 0;
00078         CLEAR_FLAG(op, FLAG_DAMNED);
00079         CLEAR_FLAG(op, FLAG_CURSED);
00080         CLEAR_FLAG(op, FLAG_KNOWN_MAGICAL);
00081         CLEAR_FLAG(op, FLAG_KNOWN_CURSED);
00082         if (op->env && op->env->type == PLAYER) {
00083             esrv_update_item(UPD_FLAGS, op->env, op);
00084         }
00085     }
00086 }
00087 
00103 static int did_make_save_item(object *op, int type, object *originator) {
00104     int i, roll, saves = 0, attacks = 0, number;
00105     materialtype_t *mt;
00106 
00107     if (op->materialname == NULL) {
00108         for (mt = materialt; mt != NULL && mt->next != NULL; mt = mt->next) {
00109             if (op->material&mt->material)
00110                 break;
00111         }
00112     } else
00113         mt = name_to_material(op->materialname);
00114     if (mt == NULL)
00115         return TRUE;
00116     roll = rndm(1, 20);
00117 
00118     /* the attacktypes have no meaning for object saves
00119      * If the type is only magic, don't adjust type - basically, if
00120      * pure magic is hitting an object, it should save.  However, if it
00121      * is magic teamed with something else, then strip out the
00122      * magic type, and instead let the fire, cold, or whatever component
00123      * destroy the item.  Otherwise, you get the case of poisoncloud
00124      * destroying objects because it has magic attacktype.
00125      */
00126     if (type != AT_MAGIC)
00127         type &= ~(AT_CONFUSION|AT_DRAIN|AT_GHOSTHIT|AT_POISON|AT_SLOW|
00128                   AT_PARALYZE|AT_TURN_UNDEAD|AT_FEAR|AT_DEPLETE|AT_DEATH|
00129                   AT_COUNTERSPELL|AT_HOLYWORD|AT_BLIND|AT_LIFE_STEALING|
00130                   AT_MAGIC);
00131 
00132     if (type == 0)
00133         return TRUE;
00134     if (roll == 20)
00135         return TRUE;
00136     if (roll == 1)
00137         return FALSE;
00138 
00139     for (number = 0; number < NROFATTACKS; number++) {
00140         i = 1<<number;
00141         if (!(i&type))
00142             continue;
00143         attacks++;
00144         if (op->resist[number] == 100)
00145             saves++;
00146         else if (roll >= mt->save[number]-op->magic-op->resist[number]/100)
00147             saves++;
00148         else if ((20-mt->save[number])/3 > originator->stats.dam)
00149             saves++;
00150     }
00151 
00152     if (saves == attacks || attacks == 0)
00153         return TRUE;
00154     if ((saves == 0) || (rndm(1, attacks) > saves))
00155         return FALSE;
00156     return TRUE;
00157 }
00158 
00171 void save_throw_object(object *op, uint32 type, object *originator) {
00172     if (!did_make_save_item(op, type, originator)) {
00173         object *env = op->env;
00174         object *inv;
00175         int x = op->x, y = op->y;
00176         mapstruct *m = op->map;
00177 
00178         op = stop_item(op);
00179         if (op == NULL)
00180             return;
00181 
00182         /* Set off runes in the inventory of the object being destroyed. */
00183         inv = op->inv;
00184         while (inv != NULL) {
00185             if (inv->type == RUNE)
00186                 spring_trap(inv, originator);
00187             inv = inv->below;
00188         }
00189 
00190         /* Hacked the following so that type LIGHTER will work.
00191          * Also, objects which are potenital "lights" that are hit by
00192          * flame/elect attacks will be set to glow. "lights" are any
00193          * object with +/- glow_radius and an "other_arch" to change to.
00194          * (and please note that we cant fail our save and reach this
00195          * function if the object doesnt contain a material that can burn.
00196          * So forget lighting magical swords on fire with this!) -b.t.
00197          */
00198         if (type&(AT_FIRE|AT_ELECTRICITY)
00199         && op->other_arch
00200         && QUERY_FLAG(op, FLAG_IS_LIGHTABLE)) {
00201             const char *arch = op->other_arch->name;
00202 
00203             op = decrease_ob_nr(op, 1);
00204             if (op)
00205                 fix_stopped_item(op, m, originator);
00206             if ((op = create_archetype(arch)) != NULL) {
00207                 if (env) {
00208                     op->x = env->x,
00209                     op->y = env->y;
00210                     insert_ob_in_ob(op, env);
00211                 } else {
00212                     op->x = x,
00213                     op->y = y;
00214                     insert_ob_in_map(op, m, originator, 0);
00215                 }
00216             }
00217             return;
00218         }
00219         if (type&AT_CANCELLATION) {         /* Cancellation. */
00220             cancellation(op);
00221             fix_stopped_item(op, m, originator);
00222             return;
00223         }
00224         if (op->nrof > 1) {
00225             op = decrease_ob_nr(op, rndm(0, op->nrof-1));
00226             if (op)
00227                 fix_stopped_item(op, m, originator);
00228         } else {
00229             if (!QUERY_FLAG(op, FLAG_REMOVED))
00230                 remove_ob(op);
00231             free_object(op);
00232         }
00233         if (type&(AT_FIRE|AT_ELECTRICITY)) {
00234             if (env) {
00235                 op = create_archetype("burnout");
00236                 op->x = env->x,
00237                 op->y = env->y;
00238                 insert_ob_in_ob(op, env);
00239             } else {
00240                 replace_insert_ob_in_map("burnout", originator);
00241             }
00242         }
00243         return;
00244     }
00245     /* The value of 50 is arbitrary. */
00246     if (type&AT_COLD
00247     && (op->resist[ATNR_COLD] < 50)
00248     && !QUERY_FLAG(op, FLAG_NO_PICK)
00249     && (RANDOM()&2)) {
00250         object *tmp;
00251         archetype *at = find_archetype("icecube");
00252 
00253         if (at == NULL)
00254             return;
00255         op = stop_item(op);
00256         if (op == NULL)
00257             return;
00258         if ((tmp = present_arch(at, op->map, op->x, op->y)) == NULL) {
00259             tmp = arch_to_object(at);
00260             tmp->x = op->x,
00261             tmp->y = op->y;
00262             /* This was in the old (pre new movement code) -
00263              * icecubes have slow_move set to 1 - don't want
00264              * that for ones we create.
00265              */
00266             tmp->move_slow_penalty = 0;
00267             tmp->move_slow = 0;
00268             insert_ob_in_map(tmp, op->map, originator, 0);
00269         }
00270         if (!QUERY_FLAG(op, FLAG_REMOVED))
00271             remove_ob(op);
00272         (void)insert_ob_in_ob(op, tmp);
00273         return;
00274     }
00275 }
00276 
00292 int hit_map(object *op, int dir, uint32 type, int full_hit) {
00293     object *tmp, *next;
00294     mapstruct *map;
00295     sint16 x, y;
00296     int retflag = 0;  /* added this flag..  will return 1 if it hits a monster */
00297 
00298     tag_t op_tag, next_tag = 0;
00299 
00300     if (QUERY_FLAG(op, FLAG_FREED)) {
00301         LOG(llevError, "BUG: hit_map(): free object\n");
00302         return 0;
00303     }
00304 
00305     if (QUERY_FLAG(op, FLAG_REMOVED) || op->env != NULL) {
00306         LOG(llevError, "BUG: hit_map(): hitter (arch %s, name %s) not on a map\n", op->arch->name, op->name);
00307         return 0;
00308     }
00309 
00310     if (!op->map) {
00311         LOG(llevError, "BUG: hit_map(): %s has no map\n", op->name);
00312         return 0;
00313     }
00314 
00315     if (op->head)
00316         op = op->head;
00317 
00318     op_tag = op->count;
00319 
00320     map = op->map;
00321     x = op->x+freearr_x[dir];
00322     y = op->y+freearr_y[dir];
00323     if (get_map_flags(map, &map, x, y, &x, &y)&P_OUT_OF_MAP)
00324         return 0;
00325 
00326     /* peterm:  a few special cases for special attacktypes --counterspell
00327      * must be out here because it strikes things which are not alive
00328      */
00329 
00330     if (type&AT_COUNTERSPELL) {
00331         counterspell(op, dir);  /* see spell_effect.c */
00332 
00333         /* If the only attacktype is counterspell or magic, don't need
00334          * to do any further processing.
00335          */
00336         if (!(type&~(AT_COUNTERSPELL|AT_MAGIC))) {
00337             return 0;
00338         }
00339         type &= ~AT_COUNTERSPELL;
00340     }
00341 
00342     if (type&AT_CHAOS) {
00343         shuffle_attack(op, 1);  /*1 flag tells it to change the face */
00344         update_object(op, UP_OBJ_FACE);
00345         type &= ~AT_CHAOS;
00346     }
00347 
00348     next = GET_MAP_OB(map, x, y);
00349     if (next)
00350         next_tag = next->count;
00351 
00352     while (next) {
00353         if (was_destroyed(next, next_tag)) {
00354             /* There may still be objects that were above 'next', but there is no
00355              * simple way to find out short of copying all object references and
00356              * tags into a temporary array before we start processing the first
00357              * object.  That's why we just abort.
00358              *
00359              * This happens whenever attack spells (like fire) hit a pile
00360              * of objects. This is not a bug - nor an error. The errormessage
00361              * below was spamming the logs for absolutely no reason.
00362              */
00363             /* LOG (llevDebug, "hit_map(): next object destroyed\n"); */
00364             break;
00365         }
00366         tmp = next;
00367         next = tmp->above;
00368         if (next)
00369             next_tag = next->count;
00370 
00371         if (QUERY_FLAG(tmp, FLAG_FREED)) {
00372             LOG(llevError, "BUG: hit_map(): found freed object\n");
00373             break;
00374         }
00375 
00376         /* Something could have happened to 'tmp' while 'tmp->below' was processed.
00377          * For example, 'tmp' was put in an icecube.
00378          * This is one of the few cases where on_same_map should not be used.
00379          */
00380         if (tmp->map != map || tmp->x != x || tmp->y != y)
00381             continue;
00382 
00383         if (tmp->head)
00384             tmp = tmp->head;
00385 
00386         /* Need to hit everyone in the transport with this spell */
00387         if (tmp->type == TRANSPORT) {
00388             object *pl;
00389 
00390             for (pl = tmp->inv; pl; pl = pl->below) {
00391                 if (pl->type == PLAYER)
00392                     hit_player(pl, op->stats.dam, op, type, full_hit);
00393             }
00394         }
00395 
00396         if (QUERY_FLAG(tmp, FLAG_ALIVE)) {
00397             hit_player(tmp, op->stats.dam, op, type, full_hit);
00398             retflag |= 1;
00399             if (was_destroyed(op, op_tag))
00400                 break;
00401         }
00402         /* Here we are potentially destroying an object.  If the object has
00403          * NO_PASS set, it is also immune - you can't destroy walls.  Note
00404          * that weak walls have is_alive set, which prevent objects from
00405          * passing over/through them.  We don't care what type of movement
00406          * the wall blocks - if it blocks any type of movement, can't be
00407          * destroyed right now.
00408          */
00409         else if ((tmp->material || tmp->materialname) && op->stats.dam > 0 && !tmp->move_block) {
00410             save_throw_object(tmp, type, op);
00411             if (was_destroyed(op, op_tag))
00412                 break;
00413         }
00414     }
00415     return 0;
00416 }
00417 
00433 static void attack_message(int dam, int type, object *op, object *hitter) {
00434     char buf[MAX_BUF], buf1[MAX_BUF], buf2[MAX_BUF];
00435     int i, found = 0;
00436     mapstruct *map;
00437     object *next, *tmp;
00438 
00439     /* put in a few special messages for some of the common attacktypes
00440      *  a player might have.  For example, fire, electric, cold, etc
00441      *  [garbled 20010919]
00442      */
00443 
00444     if (dam == 9998 && op->type == DOOR) {
00445         sprintf(buf1, "unlock %s", op->name);
00446         sprintf(buf2, " unlocks");
00447         found++;
00448     }
00449     if (dam < 0) {
00450         sprintf(buf1, "hit %s", op->name);
00451         sprintf(buf2, " hits");
00452         found++;
00453     } else if (dam == 0) {
00454         sprintf(buf1, "missed %s", op->name);
00455         sprintf(buf2, " misses");
00456         found++;
00457     } else if ((hitter->type == DISEASE
00458         || hitter->type == SYMPTOM
00459         || hitter->type == POISONING
00460         || (type&AT_POISON && IS_LIVE(op))) && !found) {
00461         for (i = 0; i < MAXATTACKMESS && attack_mess[ATM_SUFFER][i].level != -1; i++)
00462             if (dam < attack_mess[ATM_SUFFER][i].level
00463             || attack_mess[ATM_SUFFER][i+1].level == -1) {
00464                 sprintf(buf1, "%s %s%s", attack_mess[ATM_SUFFER][i].buf1, op->name, attack_mess[ATM_SUFFER][i].buf2);
00465                 sprintf(buf2, "%s", attack_mess[ATM_SUFFER][i].buf3);
00466                 found++;
00467                 break;
00468             }
00469     } else if (op->type == DOOR && !found) {
00470         for (i = 0; i < MAXATTACKMESS && attack_mess[ATM_DOOR][i].level != -1; i++)
00471             if (dam < attack_mess[ATM_DOOR][i].level
00472             || attack_mess[ATM_DOOR][i+1].level == -1) {
00473                 sprintf(buf1, "%s %s%s", attack_mess[ATM_DOOR][i].buf1, op->name, attack_mess[ATM_DOOR][i].buf2);
00474                 sprintf(buf2, "%s", attack_mess[ATM_DOOR][i].buf3);
00475                 found++;
00476                 break;
00477             }
00478     } else if (hitter->type == PLAYER && IS_LIVE(op)) {
00479         if (USING_SKILL(hitter, SK_KARATE)) {
00480             for (i = 0; i < MAXATTACKMESS && attack_mess[ATM_KARATE][i].level != -1; i++)
00481                 if (dam < attack_mess[ATM_KARATE][i].level
00482                 || attack_mess[ATM_KARATE][i+1].level == -1) {
00483                     sprintf(buf1, "%s %s%s", attack_mess[ATM_KARATE][i].buf1, op->name, attack_mess[ATM_KARATE][i].buf2);
00484                     sprintf(buf2, "%s", attack_mess[ATM_KARATE][i].buf3);
00485                     found++;
00486                     break;
00487                 }
00488         } else if (USING_SKILL(hitter, SK_CLAWING)) {
00489             for (i = 0; i < MAXATTACKMESS && attack_mess[ATM_CLAW][i].level != -1; i++)
00490                 if (dam < attack_mess[ATM_CLAW][i].level
00491                 || attack_mess[ATM_CLAW][i+1].level == -1) {
00492                     sprintf(buf1, "%s %s%s", attack_mess[ATM_CLAW][i].buf1, op->name, attack_mess[ATM_CLAW][i].buf2);
00493                     sprintf(buf2, "%s", attack_mess[ATM_CLAW][i].buf3);
00494                     found++;
00495                     break;
00496                 }
00497         } else if (USING_SKILL(hitter, SK_PUNCHING)) {
00498             for (i = 0; i < MAXATTACKMESS && attack_mess[ATM_PUNCH][i].level != -1; i++)
00499                 if (dam < attack_mess[ATM_PUNCH][i].level
00500                 || attack_mess[ATM_PUNCH][i+1].level == -1) {
00501                     sprintf(buf1, "%s %s%s", attack_mess[ATM_PUNCH][i].buf1, op->name, attack_mess[ATM_PUNCH][i].buf2);
00502                     sprintf(buf2, "%s", attack_mess[ATM_PUNCH][i].buf3);
00503                     found++;
00504                     break;
00505                 }
00506         } else if (USING_SKILL(hitter, SK_WRAITH_FEED)) {
00507             for (i = 0; i < MAXATTACKMESS && attack_mess[ATM_WRAITH_FEED][i].level != -1; i++)
00508                 if (dam < attack_mess[ATM_WRAITH_FEED][i].level) {
00509                     sprintf(buf1, "%s %s%s", attack_mess[ATM_WRAITH_FEED][i].buf1, op->name, attack_mess[ATM_WRAITH_FEED][i].buf2);
00510                     sprintf(buf2, "%s", attack_mess[ATM_WRAITH_FEED][i].buf3);
00511                     found++;
00512                     break;
00513                 }
00514         }
00515     }
00516     if (found) {
00517         /* done */
00518     } else if (IS_ARROW(hitter) && (type == AT_PHYSICAL || type == AT_MAGIC)) {
00519         sprintf(buf1, "hit"); /* just in case */
00520         for (i = 0; i < MAXATTACKMESS; i++)
00521             if (dam < attack_mess[ATM_ARROW][i].level
00522             || attack_mess[ATM_ARROW][i+1].level == -1) {
00523                 sprintf(buf2, "%s", attack_mess[ATM_ARROW][i].buf3);
00524                 found++;
00525                 break;
00526             }
00527     } else if (type&AT_DRAIN && IS_LIVE(op)) {
00528         /* drain is first, because some items have multiple attypes */
00529         for (i = 0; i < MAXATTACKMESS && attack_mess[ATM_DRAIN][i].level != -1; i++)
00530             if (dam < attack_mess[ATM_DRAIN][i].level
00531             || attack_mess[ATM_DRAIN][i+1].level == -1) {
00532                 sprintf(buf1, "%s %s%s", attack_mess[ATM_DRAIN][i].buf1, op->name, attack_mess[ATM_DRAIN][i].buf2);
00533                 sprintf(buf2, "%s", attack_mess[ATM_DRAIN][i].buf3);
00534                 found++;
00535                 break;
00536             }
00537     } else if (type&AT_ELECTRICITY && IS_LIVE(op)) {
00538         for (i = 0; i < MAXATTACKMESS && attack_mess[ATM_ELEC][i].level != -1; i++)
00539             if (dam < attack_mess[ATM_ELEC][i].level
00540             || attack_mess[ATM_ELEC][i+1].level == -1) {
00541                 sprintf(buf1, "%s %s%s", attack_mess[ATM_ELEC][i].buf1, op->name, attack_mess[ATM_ELEC][i].buf2);
00542                 sprintf(buf2, "%s", attack_mess[ATM_ELEC][i].buf3);
00543                 found++;
00544                 break;
00545             }
00546     } else if (type&AT_COLD && IS_LIVE(op)) {
00547         for (i = 0; i < MAXATTACKMESS && attack_mess[ATM_COLD][i].level != -1; i++)
00548             if (dam < attack_mess[ATM_COLD][i].level
00549             || attack_mess[ATM_COLD][i+1].level == -1) {
00550                 sprintf(buf1, "%s %s%s", attack_mess[ATM_COLD][i].buf1, op->name, attack_mess[ATM_COLD][i].buf2);
00551                 sprintf(buf2, "%s", attack_mess[ATM_COLD][i].buf3);
00552                 found++;
00553                 break;
00554             }
00555     } else if (type&AT_FIRE) {
00556         for (i = 0; i < MAXATTACKMESS && attack_mess[ATM_FIRE][i].level != -1; i++)
00557             if (dam < attack_mess[ATM_FIRE][i].level
00558             || attack_mess[ATM_FIRE][i+1].level == -1) {
00559                 sprintf(buf1, "%s %s%s", attack_mess[ATM_FIRE][i].buf1, op->name, attack_mess[ATM_FIRE][i].buf2);
00560                 sprintf(buf2, "%s", attack_mess[ATM_FIRE][i].buf3);
00561                 found++;
00562                 break;
00563             }
00564     } else if (hitter->current_weapon != NULL) {
00565         int mtype;
00566 
00567         switch (hitter->current_weapon->weapontype) {
00568         case WEAP_HIT: mtype = ATM_BASIC; break;
00569         case WEAP_SLASH: mtype = ATM_SLASH; break;
00570         case WEAP_PIERCE: mtype = ATM_PIERCE; break;
00571         case WEAP_CLEAVE: mtype = ATM_CLEAVE; break;
00572         case WEAP_SLICE: mtype = ATM_SLICE; break;
00573         case WEAP_STAB: mtype = ATM_STAB; break;
00574         case WEAP_WHIP: mtype = ATM_WHIP; break;
00575         case WEAP_CRUSH: mtype = ATM_CRUSH; break;
00576         case WEAP_BLUD: mtype = ATM_BLUD; break;
00577         default: mtype = ATM_BASIC; break;
00578         }
00579         for (i = 0; i < MAXATTACKMESS && attack_mess[mtype][i].level != -1; i++)
00580             if (dam < attack_mess[mtype][i].level
00581             || attack_mess[mtype][i+1].level == -1) {
00582                 sprintf(buf1, "%s %s%s", attack_mess[mtype][i].buf1, op->name, attack_mess[mtype][i].buf2);
00583                 sprintf(buf2, "%s", attack_mess[mtype][i].buf3);
00584                 found++;
00585                 break;
00586             }
00587     } else {
00588         for (i = 0; i < MAXATTACKMESS && attack_mess[ATM_BASIC][i].level != -1; i++)
00589             if (dam < attack_mess[ATM_BASIC][i].level
00590             || attack_mess[ATM_BASIC][i+1].level == -1) {
00591                 sprintf(buf1, "%s %s%s", attack_mess[ATM_BASIC][i].buf1, op->name, attack_mess[ATM_BASIC][i].buf2);
00592                 sprintf(buf2, "%s", attack_mess[ATM_BASIC][i].buf3);
00593                 found++;
00594                 break;
00595             }
00596     }
00597 
00598     if (!found) {
00599         sprintf(buf1, "hit");
00600         sprintf(buf2, "hits");
00601     }
00602 
00603     /* bail out if a monster is casting spells */
00604     if (!(hitter->type == PLAYER || (get_owner(hitter) != NULL && hitter->owner->type == PLAYER)))
00605         return;
00606 
00607     /* scale down magic considerably. */
00608     if (type&AT_MAGIC && rndm(0, 5))
00609         return;
00610 
00611     /* Did a player hurt another player?  Inform both! */
00612     /* only show half the player->player combat messages */
00613     if (op->type == PLAYER
00614     && rndm(0, 1)
00615     && (get_owner(hitter) == NULL ? hitter->type : hitter->owner->type) == PLAYER) {
00616         if (get_owner(hitter) != NULL)
00617             sprintf(buf, "%s's %s %s you.", hitter->owner->name, hitter->name, buf2);
00618         else {
00619             sprintf(buf, "%s%s you.", hitter->name, buf2);
00620             if (dam != 0) {
00621                 if (hitter->chosen_skill)
00622                     play_sound_player_only(op->contr, SOUND_TYPE_HIT_BY, op, 0, hitter->chosen_skill->name);
00623                 else if (dam < 10)
00624                     play_sound_player_only(op->contr, SOUND_TYPE_HIT_BY, op, 0, "low");
00625                 else if (dam < 20)
00626                     play_sound_player_only(op->contr, SOUND_TYPE_HIT_BY, op, 0, "medium");
00627                 else
00628                     play_sound_player_only(op->contr, SOUND_TYPE_HIT_BY, op, 0, "high");
00629             }
00630         }
00631         draw_ext_info(NDI_BLACK, 0, op, MSG_TYPE_VICTIM, MSG_TYPE_VICTIM_WAS_HIT, buf, NULL);
00632     } /* end of player hitting player */
00633 
00634     /* scale down these messages too */
00635     if(hitter->type == PLAYER && rndm(0, 2) == 0) {
00636         sprintf(buf, "You %s.", buf1);
00637         if (dam != 0) {
00638             if (hitter->chosen_skill)
00639                 play_sound_player_only(hitter->contr, SOUND_TYPE_HIT, hitter, 0, hitter->chosen_skill->name);
00640             else if (dam < 10)
00641                 play_sound_player_only(hitter->contr, SOUND_TYPE_HIT, hitter, 0, "low");
00642             else if (dam < 20)
00643                 play_sound_player_only(hitter->contr, SOUND_TYPE_HIT, hitter, 0, "medium");
00644             else
00645                 play_sound_player_only(hitter->contr, SOUND_TYPE_HIT, hitter, 0, "high");
00646         }
00647         draw_ext_info(NDI_BLACK, 0, hitter, MSG_TYPE_ATTACK, MSG_TYPE_ATTACK_DID_HIT,
00648                       buf, NULL);
00649     } else if (get_owner(hitter) != NULL && hitter->owner->type == PLAYER) {
00650         /* look for stacked spells and start reducing the message chances */
00651         if (hitter->type == SPELL_EFFECT
00652         && (hitter->subtype == SP_EXPLOSION || hitter->subtype == SP_BULLET || hitter->subtype == SP_CONE)) {
00653             i = 4;
00654             map = hitter->map;
00655             if (out_of_map(map, hitter->x, hitter->y))
00656                 return;
00657             next = GET_MAP_OB(map, hitter->x, hitter->y);
00658             if (next)
00659                 while (next) {
00660                     if (next->type == SPELL_EFFECT
00661                     && (next->subtype == SP_EXPLOSION || next->subtype == SP_BULLET || next->subtype == SP_CONE))
00662                         i *= 3;
00663                     tmp = next;
00664                     next = tmp->above;
00665                 }
00666             if (i < 0)
00667                 return;
00668             if (rndm(0, i) != 0)
00669                 return;
00670         } else if (rndm(0, 5) != 0)
00671             return;
00672         play_sound_map(SOUND_TYPE_HIT, hitter->owner, 0, "hit");
00673         draw_ext_info_format(NDI_BLACK, 0, hitter->owner, MSG_TYPE_ATTACK, MSG_TYPE_ATTACK_PET_HIT,
00674                              "Your %s%s %s.",
00675                              "Your %s%s %s.",
00676                              hitter->name, buf2, op->name);
00677     }
00678 }
00679 
00691 static int get_attack_mode(object **target, object **hitter,
00692                            int *simple_attack) {
00693     if (QUERY_FLAG(*target, FLAG_FREED) || QUERY_FLAG(*hitter, FLAG_FREED)) {
00694         LOG(llevError, "BUG: get_attack_mode(): freed object\n");
00695         return 1;
00696     }
00697     if ((*target)->head)
00698         *target = (*target)->head;
00699     if ((*hitter)->head)
00700         *hitter = (*hitter)->head;
00701     if ((*hitter)->env != NULL || (*target)->env != NULL) {
00702         *simple_attack = 1;
00703         return 0;
00704     }
00705     if (QUERY_FLAG(*target, FLAG_REMOVED)
00706     || QUERY_FLAG(*hitter, FLAG_REMOVED)
00707     || (*hitter)->map == NULL
00708     || !on_same_map((*hitter), (*target))) {
00709         LOG(llevError, "BUG: hitter (arch %s, name %s) with no relation to target\n", (*hitter)->arch->name, (*hitter)->name);
00710         return 1;
00711     }
00712     *simple_attack = 0;
00713     return 0;
00714 }
00715 
00729 static int abort_attack(object *target, object *hitter, int simple_attack) {
00730     int new_mode;
00731 
00732     if (hitter->env == target || target->env == hitter)
00733         new_mode = 1;
00734     else if (QUERY_FLAG(hitter, FLAG_REMOVED)
00735     || QUERY_FLAG(target, FLAG_REMOVED)
00736     || hitter->map == NULL || !on_same_map(hitter, target))
00737         return 1;
00738     else
00739         new_mode = 0;
00740     return new_mode != simple_attack;
00741 }
00742 
00743 static void thrown_item_effect(object *, object *);
00744 
00760 static int attack_ob_simple(object *op, object *hitter, int base_dam,
00761                             int base_wc) {
00762     int simple_attack, roll, dam = 0;
00763     uint32 type;
00764     const char *op_name = NULL;
00765     tag_t op_tag, hitter_tag;
00766 
00767     if (get_attack_mode(&op, &hitter, &simple_attack))
00768         goto error;
00769 
00770     /* Lauwenmark: Handle for plugin attack event */
00771     if (execute_event(op, EVENT_ATTACK, hitter, hitter->current_weapon ? hitter->current_weapon : hitter, NULL, SCRIPT_FIX_ALL) != 0)
00772         return 0;
00773 
00774     /* Lauwenmark: This is used to handle script_weapons with weapons.
00775      * Only used for players.
00776      */
00777     if (hitter->type == PLAYER) {
00778         if (hitter->current_weapon != NULL) {
00779             /* Lauwenmark: Handle for plugin attack event */
00780             if (execute_event(hitter->current_weapon, EVENT_ATTACK,
00781                               hitter, op, NULL, SCRIPT_FIX_ALL) != 0)
00782                 return 0;
00783             if (hitter->current_weapon->anim_suffix)
00784                 apply_anim_suffix(hitter, hitter->current_weapon->anim_suffix);
00785         }
00786     }
00787     op_tag = op->count;
00788     hitter_tag = hitter->count;
00789     /*
00790      * A little check to make it more difficult to dance forward and back
00791      * to avoid ever being hit by monsters.
00792      */
00793     if (!simple_attack && QUERY_FLAG(op, FLAG_MONSTER)
00794         && op->speed_left > -(FABS(op->speed))*0.3) {
00795         /* Decrease speed BEFORE calling process_object.  Otherwise, an
00796          * infinite loop occurs, with process_object calling move_monster,
00797          * which then gets here again.  By decreasing the speed before
00798          * we call process_object, the 'if' statement above will fail.
00799          */
00800         op->speed_left--;
00801         process_object(op);
00802         if (was_destroyed(op, op_tag)
00803         || was_destroyed(hitter, hitter_tag)
00804         || abort_attack(op, hitter, simple_attack))
00805             goto error;
00806     }
00807 
00808     add_refcount(op_name = op->name);
00809 
00810     roll = random_roll(1, 20, hitter, PREFER_HIGH);
00811 
00812     /* Adjust roll for various situations. */
00813     if (!simple_attack)
00814         roll += adj_attackroll(hitter, op);
00815 
00816     /* See if we hit the creature */
00817     if (roll == 20 || op->stats.ac >= base_wc-roll) {
00818         int hitdam = base_dam;
00819         if (settings.casting_time == TRUE) {
00820             if ((hitter->type == PLAYER)&&(hitter->casting_time > -1)) {
00821                 hitter->casting_time = -1;
00822                 draw_ext_info(NDI_UNIQUE, 0, hitter, MSG_TYPE_ATTACK, MSG_TYPE_ATTACK_FUMBLE,
00823                               "You attacked and lost your spell!", NULL);
00824             }
00825             if ((op->casting_time > -1)&&(hitdam > 0)) {
00826                 op->casting_time = -1;
00827                 if (op->type == PLAYER)  {
00828                     draw_ext_info(NDI_UNIQUE, 0, op, MSG_TYPE_ATTACK, MSG_TYPE_ATTACK_FUMBLE,
00829                                   "You were hit and lost your spell!", NULL);
00830                     draw_ext_info_format(NDI_ALL|NDI_UNIQUE, 5, NULL,
00831                                          MSG_TYPE_ATTACK, MSG_TYPE_ATTACK_FUMBLE,
00832                                          "%s was hit by %s and lost a spell.",
00833                                          "%s was hit by %s and lost a spell.",
00834                                          op_name, hitter->name);
00835                 }
00836             }
00837         }
00838         if (!simple_attack) {
00839             /* If you hit something, the victim should *always *wake up.
00840              * Before, invisible hitters could avoid doing this.
00841              * -b.t. */
00842             if (QUERY_FLAG(op, FLAG_SLEEP))
00843                 CLEAR_FLAG(op, FLAG_SLEEP);
00844 
00845             /* If the victim can't see the attacker, it may alert others
00846              * for help. */
00847             if (op->type != PLAYER && !can_see_enemy(op, hitter)
00848                 && !get_owner(op) && rndm(0, op->stats.Int))
00849                 npc_call_help(op);
00850 
00851             /* if you were hidden and hit by a creature, you are discovered*/
00852             if (op->hide && QUERY_FLAG(hitter, FLAG_ALIVE)) {
00853                 make_visible(op);
00854                 if (op->type == PLAYER)
00855                     draw_ext_info(NDI_UNIQUE, 0, op, MSG_TYPE_VICTIM, MSG_TYPE_VICTIM_WAS_HIT,
00856                                   "You were hit by a wild attack. You are no longer hidden!",
00857                                   NULL);
00858             }
00859 
00860             /* thrown items (hitter) will have various effects
00861              * when they hit the victim.  For things like thrown daggers,
00862              * this sets 'hitter' to the actual dagger, and not the
00863              * wrapper object.
00864              */
00865             thrown_item_effect(hitter, op);
00866             if (was_destroyed(hitter, hitter_tag)
00867                 || was_destroyed(op, op_tag)
00868                 || abort_attack(op, hitter, simple_attack))
00869                 goto leave;
00870         }
00871 
00872         /* Need to do at least 1 damage, otherwise there is no point
00873          * to go further and it will cause FPE's below.
00874          */
00875         if (hitdam <= 0)
00876             hitdam = 1;
00877 
00878         type = hitter->attacktype;
00879         if (!type)
00880             type = AT_PHYSICAL;
00881         /* Handle monsters that hit back */
00882         if (!simple_attack && QUERY_FLAG(op, FLAG_HITBACK)
00883             && QUERY_FLAG(hitter, FLAG_ALIVE)) {
00884             if (op->attacktype&AT_ACID && hitter->type == PLAYER)
00885                 draw_ext_info(NDI_UNIQUE, 0, hitter, MSG_TYPE_VICTIM, MSG_TYPE_VICTIM_WAS_HIT,
00886                               "You are splashed by acid!\n", NULL);
00887             hit_player(hitter, random_roll(0, (op->stats.dam), hitter, PREFER_LOW), op, op->attacktype, 1);
00888             if (was_destroyed(op, op_tag)
00889             || was_destroyed(hitter, hitter_tag)
00890             || abort_attack(op, hitter, simple_attack))
00891                 goto leave;
00892         }
00893 
00894         /* In the new attack code, it should handle multiple attack
00895          * types in its area, so remove it from here.
00896          */
00897         dam = hit_player(op, random_roll(1, hitdam, hitter, PREFER_HIGH), hitter, type, 1);
00898         if (was_destroyed(op, op_tag)
00899         || was_destroyed(hitter, hitter_tag)
00900         || abort_attack(op, hitter, simple_attack))
00901             goto leave;
00902     } /* end of if hitter hit op */
00903     /* if we missed, dam=0 */
00904 
00905     /*attack_message(dam, type, op, hitter);*/
00906 
00907     goto leave;
00908 
00909 error:
00910     dam = 1;
00911     goto leave;
00912 
00913 leave:
00914     if (op_name)
00915         free_string(op_name);
00916 
00917     return dam;
00918 }
00919 
00929 int attack_ob(object *op, object *hitter) {
00930 
00931     if (hitter->head)
00932         hitter = hitter->head;
00933     return attack_ob_simple(op, hitter, hitter->stats.dam, hitter->stats.wc);
00934 }
00935 
00946 static int stick_arrow(object *op, object *tmp) {
00947     /* If the missile hit a player, we insert it in their inventory.
00948      * However, if the missile is heavy, we don't do so (assume it falls
00949      * to the ground after a hit).  What a good value for this is up to
00950      * debate - 5000 is 5 kg, so arrows, knives, and other light weapons
00951      * stick around.
00952      */
00953     if (op->weight <= 5000 && tmp->stats.hp >= 0) {
00954         if (tmp->head != NULL)
00955             tmp = tmp->head;
00956         remove_ob(op);
00957         op = insert_ob_in_ob(op, tmp);
00958         return 1;
00959     } else
00960         return 0;
00961 }
00962 
00975 object *hit_with_arrow(object *op, object *victim) {
00976     object *container, *hitter;
00977     int hit_something = 0;
00978     tag_t victim_tag, hitter_tag;
00979     sint16 victim_x, victim_y;
00980     mapstruct *victim_map;
00981     const char *old_skill = NULL;
00982 
00983     /* Disassemble missile */
00984     for (hitter = op->inv; hitter; hitter = hitter->below) {
00985         if (hitter->type == EVENT_CONNECTOR)
00986             continue;
00987         container = op;
00988         /* 11-2007, commented seems buggy
00989         hitter = op->inv;*/
00990         remove_ob(hitter);
00991         if (free_no_drop(hitter))
00992             return NULL;
00993         insert_ob_in_map(hitter, container->map, hitter, INS_NO_MERGE|INS_NO_WALK_ON);
00994         break;
00995         /* Note that we now have an empty THROWN_OBJ on the map.  Code that
00996          * might be called until this THROWN_OBJ is either reassembled or
00997          * removed at the end of this function must be able to deal with empty
00998          * THROWN_OBJs. */
00999     }
01000     if (!hitter) {
01001         container = NULL;
01002         hitter = op;
01003         if (free_no_drop(hitter))
01004             return NULL;
01005     }
01006 
01007     /* Try to hit victim */
01008     victim_x = victim->x;
01009     victim_y = victim->y;
01010     victim_map = victim->map;
01011     victim_tag = victim->count;
01012     hitter_tag = hitter->count;
01013     /* Lauwenmark: Handling plugin attack event for thrown items */
01014     /* FIXME provide also to script the skill? hitter is the throwed
01015        items, but there is no information about the fact it was
01016        thrown
01017     */
01018     if (execute_event(op, EVENT_ATTACK, hitter, victim, NULL, SCRIPT_FIX_ALL) == 0) {
01019         /*
01020          * temporary set the hitter's skill to the one associated with the
01021          * throw wrapper. This is needed to that thrower gets it's xp at the
01022          * correct level. This might proves an awfull hack :/ We should really
01023          * provide attack_ob_simple with the skill to use...
01024          */
01025         if (container != NULL) {
01026             old_skill = hitter->skill;
01027             hitter->skill = add_refcount(container->skill);
01028         }
01029         hit_something = attack_ob_simple(victim, hitter, op->stats.dam, op->stats.wc);
01030     }
01031     /* Arrow attacks door, rune of summoning is triggered, demon is put on
01032      * arrow, move_apply() calls this function, arrow sticks in demon,
01033      * attack_ob_simple() returns, and we've got an arrow that still exists
01034      * but is no longer on the map. Ugh. (Beware: Such things can happen at
01035      * other places as well!)
01036      */
01037     if (was_destroyed(hitter, hitter_tag) || hitter->env != NULL) {
01038         if (container) {
01039             remove_ob(container);
01040             free_object(container);
01041         }
01042         return NULL;
01043     }
01044     if (container != NULL) {
01045         free_string(hitter->skill);
01046         hitter->skill = old_skill;
01047     }
01048     /* Missile hit victim */
01049     /* if the speed is > 10, then this is a fast moving arrow, we go straight
01050      * through the target
01051      */
01052     if (hit_something && op->speed <= 10.0) {
01053         /* Stop arrow */
01054         if (container == NULL) {
01055             hitter = fix_stopped_arrow(hitter);
01056             if (hitter == NULL)
01057                 return NULL;
01058         } else {
01059             remove_ob(container);
01060             free_object(container);
01061         }
01062 
01063         /* Try to stick arrow into victim */
01064         if (!was_destroyed(victim, victim_tag)
01065         && stick_arrow(hitter, victim))
01066             return NULL;
01067 
01068         /* Else try to put arrow on victim's map square
01069         * remove check for P_WALL here.  If the arrow got to this
01070         * space, that is good enough - with the new movement code,
01071         * there is now the potential for lots of spaces where something
01072         * can fly over but not otherwise move over.  What is the correct
01073         * way to handle those otherwise?
01074         */
01075         if (victim_x != hitter->x || victim_y != hitter->y) {
01076             remove_ob(hitter);
01077             hitter->x = victim_x;
01078             hitter->y = victim_y;
01079             insert_ob_in_map(hitter, victim_map, hitter, 0);
01080         } else {
01081             /* Else leave arrow where it is */
01082             merge_ob(hitter, NULL);
01083         }
01084         return NULL;
01085     }
01086 
01087     if (hit_something && op->speed >= 10.0)
01088         op->speed -= 1.0;
01089 
01090     /* Missile missed victim - reassemble missile */
01091     if (container) {
01092         remove_ob(hitter);
01093         insert_ob_in_ob(hitter, container);
01094     }
01095     return op;
01096 }
01104 static void tear_down_wall(object *op) {
01105     int perc = 0;
01106 
01107     if (!op->stats.maxhp) {
01108         LOG(llevError, "TEAR_DOWN wall %s had no maxhp.\n", op->name);
01109         perc = 1;
01110     } else if (!GET_ANIM_ID(op)) {
01111         /* Object has been called - no animations, so remove it */
01112         if (op->stats.hp < 0) {
01113             remove_ob(op); /* Should update LOS */
01114             free_object(op);
01115             /* Don't know why this is here - remove_ob should do it for us */
01116             /*update_position(m, x, y);*/
01117         }
01118         return; /* no animations, so nothing more to do */
01119     }
01120     perc = NUM_ANIMATIONS(op)-((int)NUM_ANIMATIONS(op)*op->stats.hp)/op->stats.maxhp;
01121     if (perc >= (int)NUM_ANIMATIONS(op))
01122         perc = NUM_ANIMATIONS(op)-1;
01123     else if (perc < 1)
01124         perc = 1;
01125     SET_ANIMATION(op, perc);
01126     update_object(op, UP_OBJ_FACE);
01127     if (perc == NUM_ANIMATIONS(op)-1) { /* Reached the last animation */
01128         if (op->face == blank_face) {
01129             /* If the last face is blank, remove the ob */
01130             remove_ob(op); /* Should update LOS */
01131             free_object(op);
01132 
01133             /* remove_ob should call update_position for us */
01134             /*update_position(m, x, y);*/
01135 
01136         } else { /* The last face was not blank, leave an image */
01137             CLEAR_FLAG(op, FLAG_BLOCKSVIEW);
01138             update_all_los(op->map, op->x, op->y);
01139             op->move_block = 0;
01140             CLEAR_FLAG(op, FLAG_ALIVE);
01141         }
01142     }
01143 }
01144 
01152 static void scare_creature(object *target, object *hitter) {
01153     object *owner = get_owner(hitter);
01154 
01155     if (!owner)
01156         owner = hitter;
01157 
01158     SET_FLAG(target, FLAG_SCARED);
01159     if (!target->enemy)
01160         target->enemy = owner;
01161 }
01162 
01179 static int hit_with_one_attacktype(object *op, object *hitter, int dam, uint32 attacknum) {
01180 
01181     int doesnt_slay = 1;
01182     char name_hitter[MAX_BUF], name_op[MAX_BUF];
01183 
01184     /* Catch anyone that may be trying to send us a bitmask instead of the number */
01185     if (attacknum >= NROFATTACKS) {
01186         LOG(llevError, "hit_with_one_attacktype: Invalid attacknumber passed: %u\n", attacknum);
01187         return 0;
01188     }
01189 
01190     if (dam < 0) {
01191         LOG(llevError, "hit_with_one_attacktype called with negative damage: %d\n", dam);
01192         return 0;
01193     }
01194 
01195     if (hitter->current_weapon && hitter->current_weapon->discrete_damage != NULL)
01196         dam = hitter->current_weapon->discrete_damage[attacknum];
01197     else if (hitter->discrete_damage != NULL)
01198         dam = hitter->discrete_damage[attacknum];
01199 
01200     /* AT_INTERNAL is supposed to do exactly dam.  Put a case here so
01201      * people can't mess with that or it otherwise get confused.  */
01202     if (attacknum == ATNR_INTERNAL)
01203         return dam;
01204 
01205     if (hitter->slaying) {
01206         if (((op->race != NULL) && strstr(hitter->slaying, op->race))
01207         || (op->arch && (op->arch->name != NULL) && strstr(op->arch->name, hitter->slaying))) {
01208             doesnt_slay = 0;
01209             dam *= 3;
01210         }
01211     }
01212 
01213     /* Adjust the damage for resistance. Note that neg. values increase damage. */
01214     if (op->resist[attacknum]) {
01215         /* basically:  dam = dam*(100-op->resist[attacknum])/100;
01216          * in case 0>dam>1, we try to "simulate" a float value-effect */
01217         dam *= (100-op->resist[attacknum]);
01218         if (dam >= 100)
01219             dam /= 100;
01220         else
01221             dam = (dam > (random_roll(0, 99, op, PREFER_LOW))) ? 1 : 0;
01222     }
01223 
01224     /* Special hack.  By default, if immune to something, you
01225      * shouldn't need to worry.  However, acid is an exception, since
01226      * it can still damage your items.  Only include attacktypes if
01227      * special processing is needed */
01228 
01229     if ((op->resist[attacknum] >= 100)
01230     && doesnt_slay
01231     && (attacknum != ATNR_ACID))
01232         return 0;
01233 
01234     /* Keep this in order - makes things easier to find */
01235 
01236     switch (attacknum) {
01237     case ATNR_PHYSICAL:
01238         /*  here also check for diseases */
01239         check_physically_infect(op, hitter);
01240         break;
01241 
01242         /* Don't need to do anything for:
01243            magic,
01244            fire,
01245            electricity,
01246            cold */
01247 
01248     case ATNR_CONFUSION:
01249     case ATNR_POISON:
01250     case ATNR_SLOW:
01251     case ATNR_PARALYZE:
01252     case ATNR_FEAR:
01253     case ATNR_CANCELLATION:
01254     case ATNR_DEPLETE:
01255     case ATNR_BLIND: {
01256             /* chance for inflicting a special attack depends on the
01257              * difference between attacker's and defender's level
01258              */
01259             int level_diff = MIN(110, MAX(0, op->level-hitter->level));
01260 
01261             /* First, only creatures/players with speed can be affected.
01262              * Second, just getting hit doesn't mean it always affects
01263              * you.  Third, you still get a saving through against the
01264              * effect.
01265              */
01266             if (op->speed
01267             && (QUERY_FLAG(op, FLAG_MONSTER) || op->type == PLAYER)
01268             && !(rndm(0, (attacknum == ATNR_SLOW ? 6 : 3)-1))
01269             && !did_make_save(op, level_diff, op->resist[attacknum]/10)) {
01270                 /* Player has been hit by something */
01271                 if (attacknum == ATNR_CONFUSION)
01272                     confuse_living(op, hitter, dam);
01273                 else if (attacknum == ATNR_POISON)
01274                     poison_living(op, hitter, dam);
01275                 else if (attacknum == ATNR_SLOW)
01276                     slow_living(op, hitter, dam);
01277                 else if (attacknum == ATNR_PARALYZE)
01278                     paralyze_living(op, hitter, dam);
01279                 else if (attacknum == ATNR_FEAR)
01280                     scare_creature(op, hitter);
01281                 else if (attacknum == ATNR_CANCELLATION)
01282                     cancellation(op);
01283                 else if (attacknum == ATNR_DEPLETE)
01284                     drain_stat(op);
01285                 else if (attacknum == ATNR_BLIND
01286                 && !QUERY_FLAG(op, FLAG_UNDEAD)
01287                 && !QUERY_FLAG(op, FLAG_GENERATOR))
01288                     blind_living(op, hitter, dam);
01289             }
01290             dam = 0; /* These are all effects and don't do real damage */
01291         }
01292         break;
01293 
01294     case ATNR_ACID: {
01295             int flag = 0;
01296 
01297             /* Items only get corroded if you're not on a battleground and
01298              * if your acid resistance is below 50%. */
01299             if (!op_on_battleground(op, NULL, NULL, NULL)
01300             && (op->resist[ATNR_ACID] < 50)) {
01301                 object *tmp;
01302 
01303                 for (tmp = op->inv; tmp != NULL; tmp = tmp->below) {
01304                     if (tmp->invisible)
01305                         continue;
01306                     if (!QUERY_FLAG(tmp, FLAG_APPLIED)
01307                     || (tmp->resist[ATNR_ACID] >= 10))
01308                         /* >= 10% acid res. on itmes will protect these */
01309                         continue;
01310                     if (!(tmp->material&M_IRON))
01311                         continue;
01312                     if (tmp->magic < -4) /* Let's stop at -5 */
01313                         continue;
01314                     if (tmp->type == RING
01315                     /* removed boots and gloves from exclusion list in PR */
01316                     || tmp->type == GIRDLE
01317                     || tmp->type == AMULET
01318                     || tmp->type == WAND
01319                     || tmp->type == ROD
01320                     || tmp->type == HORN)
01321                         continue; /* To avoid some strange effects */
01322 
01323                     /* High damage acid has better chance of corroding objects */
01324                     if (rndm(0, dam+4) > random_roll(0, 39, op, PREFER_HIGH)+2*tmp->magic) {
01325                         if (op->type == PLAYER) {
01326                             /* Make this more visible */
01327                             query_name(hitter, name_hitter, MAX_BUF);
01328                             query_name(tmp, name_op, MAX_BUF);
01329                             draw_ext_info_format(NDI_UNIQUE|NDI_RED, 0, op,
01330                                                  MSG_TYPE_VICTIM, MSG_TYPE_VICTIM_WAS_HIT,
01331                                                  "The %s's acid corrodes your %s!",
01332                                                  "The %s's acid corrodes your %s!",
01333                                                  name_hitter, name_op);
01334                         }
01335                         flag = 1;
01336                         tmp->magic--;
01337                         if (op->type == PLAYER)
01338                             esrv_update_item(UPD_NAME, op, tmp);
01339                     }
01340                 }
01341                 if (flag)
01342                     fix_object(op); /* Something was corroded */
01343             }
01344         }
01345         break;
01346 
01347     case ATNR_DRAIN: {
01348             /* rate is the proportion of exp drained.  High rate means
01349              * not much is drained, low rate means a lot is drained.
01350              */
01351             int rate;
01352 
01353             if (op->resist[ATNR_DRAIN] >= 0)
01354                 rate = 50+op->resist[ATNR_DRAIN]/2;
01355             else
01356                 rate = 5000/(100-op->resist[ATNR_DRAIN]);
01357 
01358             /* full protection has no effect.  Nothing else in this
01359              * function needs to get done, so just return.  */
01360             if (!rate)
01361                 return 0;
01362 
01363             if (op->stats.exp <= rate) {
01364                 if (op->type == GOLEM)
01365                     dam = 999; /* Its force is "sucked" away. 8) */
01366                 else
01367                     /* If we can't drain, lets try to do physical damage */
01368                     dam = hit_with_one_attacktype(op, hitter, dam, ATNR_PHYSICAL);
01369             } else {
01370                 /* Randomly give the hitter some hp */
01371                 if (hitter->stats.hp < hitter->stats.maxhp
01372                 && (op->level > hitter->level)
01373                 && random_roll(0, (op->level-hitter->level+2), hitter, PREFER_HIGH) > 3)
01374                     hitter->stats.hp++;
01375 
01376                 /* Can't do drains on battleground spaces.
01377                  * Move the wiz check up here - before, the hitter wouldn't gain exp
01378                  * exp, but the wiz would still lose exp! If drainee is a wiz,
01379                  * nothing happens.
01380                  * Try to credit the owner.  We try to display player -> player drain
01381                  * attacks, hence all the != PLAYER checks.
01382                  */
01383                 if (!op_on_battleground(hitter, NULL, NULL, NULL) && !QUERY_FLAG(op, FLAG_WAS_WIZ)) {
01384                     object *owner = get_owner(hitter);
01385 
01386                     if (owner && owner != hitter) {
01387                         if (op->type != PLAYER || owner->type != PLAYER)
01388                             change_exp(owner, op->stats.exp/(rate*2),
01389                                        hitter->chosen_skill ? hitter->chosen_skill->skill : NULL, SK_EXP_TOTAL);
01390                     } else if (op->type != PLAYER || hitter->type != PLAYER) {
01391                         change_exp(hitter, op->stats.exp/(rate*2), hitter->chosen_skill ? hitter->chosen_skill->skill : NULL, 0);
01392                     }
01393                     change_exp(op, -op->stats.exp/rate, NULL, 0);
01394                 }
01395                 dam = 1; /* Drain is an effect.  Still return 1 - otherwise, if you have pure
01396                           * drain attack, you won't know that you are actually sucking out EXP,
01397                           * as the messages will say you missed
01398                           */
01399             }
01400         }
01401         break;
01402 
01403     case ATNR_TURN_UNDEAD: {
01404             if (QUERY_FLAG(op, FLAG_UNDEAD)) {
01405                 object *owner = get_owner(hitter) == NULL ? hitter : get_owner(hitter);
01406                 const object *god = find_god(determine_god(owner));
01407                 int div = 1;
01408 
01409                 /* if undead are not an enemy of your god, you turn them
01410                         * at half strength */
01411                 if (!god
01412                 || !god->slaying
01413                 || strstr(god->slaying, undead_name) == NULL)
01414                     div = 2;
01415                 /* Give a bonus if you resist turn undead */
01416                 if (op->level*div < (turn_bonus[owner->stats.Wis]+owner->level+(op->resist[ATNR_TURN_UNDEAD]/100)))
01417                     scare_creature(op, owner);
01418             } else
01419                 dam = 0; /* don't damage non undead - should we damage undead? */
01420         }
01421         break;
01422 
01423     case ATNR_DEATH:
01424         deathstrike_living(op, hitter, &dam);
01425         break;
01426 
01427     case ATNR_CHAOS:
01428         query_name(op, name_op, MAX_BUF);
01429         query_name(hitter, name_hitter, MAX_BUF);
01430         LOG(llevError, "%s was hit by %s with non-specific chaos.\n", name_op, name_hitter);
01431         dam = 0;
01432         break;
01433 
01434     case ATNR_COUNTERSPELL:
01435         query_name(op, name_op, MAX_BUF);
01436         query_name(hitter, name_hitter, MAX_BUF);
01437         LOG(llevError, "%s was hit by %s with counterspell attack.\n", name_op, name_hitter);
01438         dam = 0;
01439         /* This should never happen.  Counterspell is handled
01440          * seperately and filtered out.  If this does happen,
01441          * Counterspell has no effect on anything but spells, so it
01442          * does no damage. */
01443         break;
01444 
01445     case ATNR_HOLYWORD: {
01446             /* This has already been handled by hit_player,
01447              *  no need to check twice  -- DAMN */
01448 
01449             object *owner = get_owner(hitter) == NULL ? hitter : get_owner(hitter);
01450 
01451             /* As with turn undead above, give a bonus on the saving throw */
01452             if ((op->level+(op->resist[ATNR_HOLYWORD]/100)) < owner->level+turn_bonus[owner->stats.Wis])
01453                 scare_creature(op, owner);
01454         }
01455         break;
01456 
01457     case ATNR_LIFE_STEALING: {
01458             int new_hp;
01459             /* this is replacement to drain for players, instead of taking
01460              * exp it takes hp. It is geared for players, probably not
01461              * much use giving it to monsters
01462              *
01463              * life stealing doesn't do a lot of damage, but it gives the
01464              * damage it does do to the player.  Given that,
01465              * it only does 1/30'th normal damage (hence the divide by
01466              * 3000). Wraith get 1/2 of the damage, and therefore divide
01467              * by 200. This number may need tweaking for game balance.
01468              */
01469 
01470             int dam_modifier = is_wraith_pl(hitter) ? 200 : 3000;
01471 
01472             /* You can't steal life from something undead or not alive. */
01473             if (op->type == GOLEM
01474             || (QUERY_FLAG(op, FLAG_UNDEAD))
01475             || !(QUERY_FLAG(op, FLAG_ALIVE))
01476             || (op->type == DOOR))
01477                 return 0;
01478             /* If drain protection is higher than life stealing, use that */
01479             if (op->resist[ATNR_DRAIN] >= op->resist[ATNR_LIFE_STEALING])
01480                 dam = (dam*(100-op->resist[ATNR_DRAIN]))/dam_modifier;
01481             else
01482                 dam = (dam*(100-op->resist[ATNR_LIFE_STEALING]))/dam_modifier;
01483             /* You die at -1 hp, not zero. */
01484             if (dam > (op->stats.hp+1))
01485                 dam = op->stats.hp+1;
01486             new_hp = hitter->stats.hp+dam;
01487             if (new_hp > hitter->stats.maxhp)
01488                 new_hp = hitter->stats.maxhp;
01489             if (new_hp > hitter->stats.hp)
01490                 hitter->stats.hp = new_hp;
01491 
01492             /* Wraith also get food through life stealing */
01493             if (is_wraith_pl(hitter)) {
01494                 if (hitter->stats.food+dam >= 999)
01495                     hitter->stats.food = 999;
01496                 else
01497                     hitter->stats.food += dam;
01498                 fix_object(hitter);
01499             }
01500         }
01501     }
01502     return dam;
01503 }
01504 
01534 static int kill_object(object *op, int dam, object *hitter, int type) {
01535     char buf[MAX_BUF];
01536     const char *skill;
01537     int maxdam = 0;
01538     int battleg = 0;  /* true if op standing on battleground */
01539     int pk = 0;       /* true if op and what controls hitter are both players*/
01540     object *owner = NULL;
01541     object *skop = NULL;
01542     sstring death_animation;
01543 
01544     if (op->stats.hp >= 0)
01545         return -1;
01546 
01547     /* Lauwenmark: Handle for plugin death event */
01548     if (execute_event(op, EVENT_DEATH, hitter, NULL, NULL, SCRIPT_FIX_ALL) != 0)
01549         return 0;
01550     /* Lauwenmark: Handle for the global kill event */
01551     execute_global_event(EVENT_GKILL, op, hitter);
01552 
01553     if ((op->map) && (death_animation = get_ob_key_value(op, "death_animation")) != NULL) {
01554         object *death = create_archetype(death_animation);
01555 
01556         if (death) {
01557             death->map = op->map;
01558             death->x = op->x;
01559             death->y = op->y;
01560             insert_ob_in_map(death, op->map, op, 0);
01561         }
01562     }
01563 
01564     /* maxdam needs to be the amount of damage it took to kill
01565      * this creature.  The function(s) that call us have already
01566      * adjusted the creatures HP total, so that is negative.
01567      */
01568     maxdam = dam+op->stats.hp+1;
01569 
01570     if (QUERY_FLAG(op, FLAG_BLOCKSVIEW))
01571         update_all_los(op->map, op->x, op->y); /* makes sure los will be recalculated */
01572 
01573     if (op->type == DOOR) {
01574         op->speed = 0.1;
01575         update_ob_speed(op);
01576         op->speed_left = -0.05;
01577         return maxdam;
01578     }
01579     if (QUERY_FLAG(op, FLAG_FRIENDLY) && op->type != PLAYER) {
01580         remove_friendly_object(op);
01581         if (get_owner(op) != NULL
01582         && op->owner->type == PLAYER
01583         && op->owner->contr->ranges[range_golem] == op) {
01584             op->owner->contr->ranges[range_golem] = NULL;
01585             op->owner->contr->golem_count = 0;
01586         } else
01587             LOG(llevError, "BUG: hit_player(): Encountered golem without owner.\n");
01588 
01589         remove_ob(op);
01590         free_object(op);
01591         return maxdam;
01592     }
01593 
01594     /* Now lets start dealing with experience we get for killing something */
01595 
01596     owner = get_owner(hitter);
01597     if (owner == NULL)
01598         owner = hitter;
01599 
01600     /* is the victim (op) standing on battleground? */
01601     if (op_on_battleground(op, NULL, NULL, NULL))
01602         battleg = 1;
01603 
01604     /* is this player killing?*/
01605     if (op->type == PLAYER && owner->type == PLAYER)
01606         pk = 1;
01607 
01608     /* Player killed something */
01609     if (owner->type == PLAYER) {
01610         char name_op[MAX_BUF], name_hitter[MAX_BUF];
01611 
01612         query_name(op, name_op, MAX_BUF);
01613         if (hitter)
01614             query_name(hitter, name_hitter, MAX_BUF);
01615         else
01616             name_hitter[0] = '\0';
01617 
01618         /* Log players killing other players - makes it easier to detect
01619          * and filter out malicious player killers - that is why the
01620          * ip address is included.
01621          */
01622         if (op->type == PLAYER && !battleg)  {
01623             time_t t = time(NULL);
01624             struct tm *tmv;
01625             char buf[256];
01626             char name[MAX_BUF];
01627 
01628             tmv = localtime(&t);
01629             strftime(buf, 256, "%a %b %d %H:%M:%S %Y", tmv);
01630             query_name(op, name, MAX_BUF);
01631 
01632             LOG(llevInfo, "%s PLAYER_KILL_PLAYER: %s (%s) killed %s\n", buf, owner->name, owner->contr->socket.host, name);
01633         }
01634 
01635         /* try to filter some things out - basically, if you are
01636          * killing a level 1 creature and your level 20, you
01637          * probably don't want to see that.
01638          */
01639         if (owner->level < op->level*2|| op->stats.exp > 1000) {
01640             if (owner != hitter) {
01641                 char killed[MAX_BUF], with[MAX_BUF];
01642 
01643                 query_name(op, killed, MAX_BUF);
01644                 query_name(hitter, with, MAX_BUF);
01645                 draw_ext_info_format(NDI_BLACK, 0, owner, MSG_TYPE_ATTACK, MSG_TYPE_ATTACK_DID_KILL,
01646                                      "You killed %s with %s.",
01647                                      "You killed %s with %s.",
01648                                      killed, with);
01649             } else {
01650                 char killed[MAX_BUF];
01651 
01652                 query_name(op, killed, MAX_BUF);
01653                 draw_ext_info_format(NDI_BLACK, 0, owner, MSG_TYPE_ATTACK, MSG_TYPE_ATTACK_DID_KILL,
01654                                      "You killed %s.",
01655                                      "You killed %s.",
01656                                      killed);
01657             }
01658             /* Only play sounds for melee kills */
01659             if (hitter->type == PLAYER)
01660                 play_sound_map(SOUND_TYPE_HIT, owner, 0, "kill");
01661         }
01662 
01663         /* If a player kills another player, not on
01664          * battleground, the "killer" looses 1 luck. Since this is
01665          * not reversible, it's actually quite a pain IMHO. -AV
01666          * Fix bug in that we were changing the luck of the hitter, not
01667          * player that the object belonged to - so if you killed another player
01668          * with spells, pets, whatever, there was no penalty.
01669          * Changed to make luck penalty configurable in settings.
01670          */
01671         if (op->type == PLAYER && owner != op && !battleg)
01672             change_luck(owner, -settings.pk_luck_penalty);
01673 
01674         /* This code below deals with finding the appropriate skill
01675          * to credit exp to.  This is a bit problematic - we should
01676                 * probably never really have to look at current_weapon->skill
01677          */
01678         skill = NULL;
01679         if (hitter->skill && hitter->type != PLAYER)
01680             skill = hitter->skill;
01681         else if (owner->chosen_skill) {
01682             skill = owner->chosen_skill->skill;
01683             skop = owner->chosen_skill;
01684         } else if (QUERY_FLAG(owner, FLAG_READY_WEAPON))
01685             skill = owner->current_weapon->skill;
01686         else
01687             LOG(llevError, "kill_object - unable to find skill that killed monster\n");
01688 
01689         /* We have the skill we want to credit to - now find the object this goes
01690          * to.  Make sure skop is an actual skill, and not a skill tool!
01691          */
01692         if ((!skop || skop->type != SKILL) && skill) {
01693             int i;
01694 
01695             for (i = 0; i < NUM_SKILLS; i++)
01696                 if (owner->contr->last_skill_ob[i]
01697                 && !strcmp(owner->contr->last_skill_ob[i]->skill, skill)) {
01698                     skop = owner->contr->last_skill_ob[i];
01699                     break;
01700                 }
01701         }
01702     } /* Was it a player that hit somethign */
01703     else {
01704         skill = NULL;
01705     }
01706 
01707     /* Pet (or spell) killed something. */
01708     if (owner != hitter) {
01709         char name_op[MAX_BUF], name_hitter[MAX_BUF];
01710 
01711         query_name(op, name_op, MAX_BUF);
01712         query_name(hitter, name_hitter, MAX_BUF);
01713         (void)sprintf(buf, "%s killed %s with %s%s.", owner->name, name_op, name_hitter, battleg ? " (duel)" : (pk ? " (pk)" : ""));
01714     } else {
01715         (void)sprintf(buf, "%s killed %s%s%s.", hitter->name, op->name,
01716                       (QUERY_FLAG(hitter, FLAG_MONSTER)) || hitter->type == PLAYER ?
01717                       " in hand to hand combat" : "", battleg ? " (duel)" : (pk ? " (pk)" : ""));
01718     }
01719     /* These may have been set in the player code section above */
01720     if (!skop)
01721         skop = hitter->chosen_skill;
01722     if (!skill && skop)
01723         skill = skop->skill;
01724 
01725     draw_ext_info(NDI_ALL, op->type == PLAYER ? 1 : 10, NULL, MSG_TYPE_ADMIN, MSG_TYPE_ADMIN_PLAYER,
01726                   buf, NULL);
01727 
01728 
01729     /* If you didn't kill yourself, and your not the wizard */
01730     if (owner != op && !QUERY_FLAG(op, FLAG_WAS_WIZ)) {
01731         sint64 exp;
01732 
01733         exp = calc_skill_exp(owner, op, skop);
01734 
01735         /* Really don't give much experience for killing other players */
01736         if (op->type == PLAYER) {
01737             if (battleg) {
01738                 draw_ext_info(NDI_UNIQUE, 0, owner, MSG_TYPE_ATTACK, MSG_TYPE_ATTACK_DID_KILL,
01739                               "Your foe has fallen!\nVICTORY!!!", NULL);
01740             } else {
01741                 exp = settings.pk_max_experience_percent*exp/100;
01742                 if (settings.pk_max_experience >= 0)
01743                     exp = MIN(settings.pk_max_experience, exp);
01744                 /* Never exceed what victim can lose considering permanent exp. */
01745                 exp = check_exp_loss(op, exp);
01746             }
01747         }
01748 
01749         /* Don't know why this is set this way - doesn't make
01750          * sense to just divide everything by two for no reason.
01751          */
01752 
01753         if (!settings.simple_exp)
01754             exp = exp/2;
01755 
01756         /* if op is standing on "battleground" (arena), no way to gain
01757          * exp by killing him
01758          */
01759         if (battleg)
01760             exp = 0;
01761 
01762 #ifdef PARTY_KILL_LOG
01763         if (owner->type == PLAYER && owner->contr->party != NULL) {
01764             char name[MAX_BUF];
01765 
01766             query_name(owner, name, MAX_BUF);
01767             add_kill_to_party(party, name, query_name(op), exp);
01768         }
01769 #endif
01770         share_exp(owner, exp, skill, SK_EXP_TOTAL);
01771 
01772     } /* end if person didn't kill himself */
01773 
01774     if (op->type != PLAYER) {
01775         if (QUERY_FLAG(op, FLAG_FRIENDLY)) {
01776             object *owner1 = get_owner(op);
01777 
01778             if (owner1 != NULL && owner1->type == PLAYER) {
01779                 /*play_sound_player_only(owner1->contr, SOUND_PET_IS_KILLED, 0, 0);*/
01780                 /* Maybe we should include the owner that killed this, maybe not */
01781                 draw_ext_info_format(NDI_UNIQUE, 0, owner1, MSG_TYPE_ATTACK, MSG_TYPE_ATTACK_PET_DIED,
01782                                      "Your pet, the %s, is killed by %s.",
01783                                      "Your pet, the %s, is killed by %s.",
01784                                      op->name, hitter->name);
01785             }
01786             remove_friendly_object(op);
01787         }
01788         remove_ob(op);
01789         free_object(op);
01790     /* Player has been killed! */
01791     } else {
01792         if (owner->type == PLAYER) {
01793             snprintf(op->contr->killer, BIG_NAME, "%s the %s", owner->name, owner->contr->title);
01794         } else {
01795             strncpy(op->contr->killer, hitter->name, BIG_NAME);
01796             op->contr->killer[BIG_NAME-1] = '\0';
01797         }
01798         /* Need to run kill_player (just in case, make sure is not wiz) */
01799         if (!QUERY_FLAG(op, FLAG_WIZ))
01800             kill_player(op);
01801     }
01802     /* This was return -1 - that doesn't seem correct - if we return -1, process
01803      * continues in the calling function.
01804      */
01805     return maxdam;
01806 }
01807 
01818 int friendly_fire(object *op, object *hitter) {
01819     object *owner;
01820     int friendlyfire;
01821 
01822     if (hitter->head)
01823         hitter = hitter->head;
01824 
01825     friendlyfire = 0;
01826 
01827     if (op->type == PLAYER) {
01828         if (hitter->type == PLAYER && hitter->contr->peaceful == 1)
01829             return 1;
01830 
01831         if ((owner = get_owner(hitter)) != NULL) {
01832             if (owner->type == PLAYER && owner->contr->peaceful == 1)
01833                 friendlyfire = 2;
01834         }
01835 
01836         if (hitter->type == SPELL
01837         || hitter->type == POISONING
01838         || hitter->type == DISEASE
01839         || hitter->type == RUNE)
01840             friendlyfire = 0;
01841     }
01842     return friendlyfire;
01843 }
01844 
01868 int hit_player(object *op, int dam, object *hitter, uint32 type, int full_hit) {
01869     int maxdam = 0, ndam = 0, attacktype = 1, magic = (type&AT_MAGIC);
01870     int maxattacktype, attacknum;
01871     int body_attack = op && op->head;   /* Did we hit op's head? */
01872     int simple_attack;
01873     tag_t op_tag, hitter_tag;
01874     int rtn_kill = 0;
01875     int friendlyfire;
01876 
01877     if (get_attack_mode(&op, &hitter, &simple_attack))
01878         return 0;
01879 
01880     /* very simple: if our target has no_damage 1 set or is wiz, we can't hurt him */
01881     if (QUERY_FLAG(op, FLAG_WIZ) || QUERY_FLAG(op, FLAG_NO_DAMAGE))
01882         return 0;
01883 
01884     op_tag = op->count;
01885     hitter_tag = hitter->count;
01886 
01887     if (body_attack) {
01888         /* slow and paralyze must hit the head.  But we don't want to just
01889          * return - we still need to process other attacks the spell still
01890          * might have.  So just remove the paralyze and slow attacktypes,
01891          * and keep on processing if we have other attacktypes.
01892          * return if only magic or nothing is left - under normal code
01893          * we don't attack with pure magic if there is another attacktype.
01894          * Only do processing if the initial attacktype includes one of those
01895          * attack so we don't cancel out things like magic bullet.
01896          */
01897         if (type&(AT_PARALYZE|AT_SLOW)) {
01898             type &= ~(AT_PARALYZE|AT_SLOW);
01899             if (!type || type == AT_MAGIC)
01900                 return 0;
01901         }
01902     }
01903 
01904     if (!simple_attack && op->type == DOOR) {
01905         object *tmp;
01906 
01907         for (tmp = op->inv; tmp != NULL; tmp = tmp->below)
01908             if (tmp->type == RUNE || tmp->type == TRAP) {
01909                 spring_trap(tmp, hitter);
01910                 if (was_destroyed(hitter, hitter_tag)
01911                 || was_destroyed(op, op_tag)
01912                 || abort_attack(op, hitter, simple_attack))
01913                     return 0;
01914                 break;
01915             }
01916     }
01917 
01918     if (!QUERY_FLAG(op, FLAG_ALIVE) || op->stats.hp < 0) {
01919         /* FIXME: If a player is killed by a rune in a door, the
01920          * was_destroyed() check above doesn't return, and might get here.
01921          */
01922         LOG(llevDebug, "victim (arch %s, name %s) already dead in hit_player()\n", op->arch->name, op->name);
01923         return 0;
01924     }
01925 
01926 #ifdef ATTACK_DEBUG
01927     LOG(llevDebug, "hit player: attacktype %d, dam %d\n", type, dam);
01928 #endif
01929 
01930     if (magic) {
01931         /* basically:  dam = dam*(100-op->resist[attacknum])/100;
01932          * in case 0>dam>1, we try to "simulate" a float value-effect */
01933         dam = dam*(100-op->resist[ATNR_MAGIC]);
01934         if (dam >= 100)
01935             dam /= 100;
01936         else
01937             dam = (dam > (rndm(0, 99))) ? 1 : 0;
01938     }
01939 
01940     /* AT_CHAOS here is a weapon or monster.  Spells are handled by hit_map
01941      * We don't use shuffle_attack(), because that changes the it in the
01942      * creature structure, and thus is permanent until fix_object() is
01943      * called again.  Chaos should change on each attack.
01944      */
01945     if (type&AT_CHAOS) {
01946         int i;
01947 
01948         i = rndm(0, 21);
01949         type = ATTACKS[i].attacktype|AT_MAGIC;
01950     }
01951 
01952     /* Holyword is really an attacktype modifier (like magic is).  If
01953      * holyword is part of an attacktype, then make sure the creature is
01954      * a proper match, otherwise no damage.
01955      */
01956     if (type&AT_HOLYWORD) {
01957         const object *god;
01958 
01959         if ((!hitter->slaying || (!(op->race && strstr(hitter->slaying, op->race))
01960                     && !(op->name && strstr(hitter->slaying, op->name))))
01961         && (!QUERY_FLAG(op, FLAG_UNDEAD) || (hitter->title != NULL
01962                     && (god = find_god(determine_god(hitter))) != NULL
01963                     && god->race != NULL
01964                     && strstr(god->race, undead_name) != NULL)))
01965             return 0;
01966     }
01967 
01968     maxattacktype = type; /* initialize this to something */
01969     for (attacknum = 0; attacknum < NROFATTACKS; attacknum++, attacktype = 1<<attacknum) {
01970         /* Magic isn't really a true attack type - it gets combined with other
01971          * attack types.  As such, skip it over.  However, if magic is
01972          * the only attacktype in the group, then still attack with it
01973          */
01974         if ((attacktype == AT_MAGIC) && (type&~AT_MAGIC))
01975             continue;
01976 
01977         /* Go through and hit the player with each attacktype, one by one.
01978                * hit_with_one_attacktype only figures out the damage, doesn't inflict
01979          * it.  It will do the appropriate action for attacktypes with
01980          * effects (slow, paralization, etc.
01981                 */
01982         if (type&attacktype) {
01983             ndam = hit_with_one_attacktype(op, hitter, dam, attacknum);
01984             /* the >= causes us to prefer messages from special attacks, if
01985              * the damage is equal.
01986              */
01987             if (ndam >= maxdam) {
01988                 maxdam = ndam;
01989                 maxattacktype = 1<<attacknum;
01990             }
01991             /* Special case: death attack always deals all damage, as it should kill the monster
01992              * right away. */
01993             if (attacktype == AT_DEATH && ndam > 0)
01994                 full_hit = 1;
01995         }
01996     }
01997 
01998     /* if this is friendly fire then do a set % of damage only
01999      * Note - put a check in to make sure this attack is actually
02000      * doing damage - otherwise, the +1 in the coe below will make
02001      * an attack do damage before when it otherwise didn't
02002      */
02003     friendlyfire = friendly_fire(op, hitter);
02004     if (friendlyfire && maxdam) {
02005         maxdam = ((dam*settings.set_friendly_fire)/100)+1;
02006 
02007 #ifdef ATTACK_DEBUG
02008         LOG(llevDebug, "Friendly fire (type:%d setting: %d%) did %d damage dropped to %d\n", friendlyfire, settings.set_friendly_fire, dam, maxdam);
02009 #endif
02010     }
02011 
02012     if (!full_hit) {
02013         archetype *at;
02014         int area;
02015         int remainder;
02016 
02017         area = 0;
02018         for (at = op->arch; at != NULL; at = at->more)
02019             area++;
02020         assert(area > 0);
02021 
02022         /* basically: maxdam /= area; we try to "simulate" a float
02023            value-effect */
02024         remainder = 100*(maxdam%area)/area;
02025         maxdam /= area;
02026         if (RANDOM()%100 < remainder)
02027             maxdam++;
02028     }
02029 
02030 #ifdef ATTACK_DEBUG
02031     LOG(llevDebug, "Attacktype %d did %d damage\n", type, maxdam);
02032 #endif
02033 
02034     if (get_owner(hitter))
02035         op->enemy = hitter->owner;
02036     else if (QUERY_FLAG(hitter, FLAG_ALIVE))
02037         op->enemy = hitter;
02038 
02039     if (QUERY_FLAG(op, FLAG_UNAGGRESSIVE) && op->type != PLAYER) {
02040         /* The unaggressives look after themselves 8) */
02041         CLEAR_FLAG(op, FLAG_UNAGGRESSIVE);
02042         npc_call_help(op);
02043     }
02044 
02045     if (magic && did_make_save(op, op->level, 0))
02046         maxdam = maxdam/2;
02047 
02048     attack_message(maxdam, maxattacktype, op, hitter);
02049 
02050     op->stats.hp -= maxdam;
02051 
02052     /* Eneq(@csd.uu.se): Check to see if monster runs away. */
02053     if ((op->stats.hp >= 0)
02054     && (QUERY_FLAG(op, FLAG_MONSTER) || op->type == PLAYER)
02055     && op->stats.hp < (signed short)(((float)op->run_away/(float)100)*(float)op->stats.maxhp)) {
02056         if (QUERY_FLAG(op, FLAG_MONSTER))
02057             SET_FLAG(op, FLAG_RUN_AWAY);
02058         else
02059             scare_creature(op, hitter);
02060     }
02061 
02062     if (QUERY_FLAG(op, FLAG_TEAR_DOWN)) {
02063         if (maxdam)
02064             tear_down_wall(op);
02065         return maxdam; /* nothing more to do for wall */
02066     }
02067 
02068     /* See if the creature has been killed */
02069     rtn_kill = kill_object(op, maxdam, hitter, type);
02070     if (rtn_kill != -1)
02071         return rtn_kill;
02072 
02073     /* Used to be ghosthit removal - we now use the ONE_HIT flag.  Note
02074      * that before if the player was immune to ghosthit, the monster
02075      * remained - that is no longer the case.
02076      */
02077     if (QUERY_FLAG(hitter, FLAG_ONE_HIT)) {
02078         if (QUERY_FLAG(hitter, FLAG_FRIENDLY))
02079             remove_friendly_object(hitter);
02080         remove_ob(hitter);
02081         free_object(hitter);
02082     /* Lets handle creatures that are splitting now */
02083     } else if (type&AT_PHYSICAL && !QUERY_FLAG(op, FLAG_FREED) && QUERY_FLAG(op, FLAG_SPLITTING)) {
02084         int i;
02085         int friendly = QUERY_FLAG(op, FLAG_FRIENDLY);
02086         int unaggressive = QUERY_FLAG(op, FLAG_UNAGGRESSIVE);
02087         object *owner = get_owner(op);
02088 
02089         if (!op->other_arch) {
02090             LOG(llevError, "SPLITTING without other_arch error.\n");
02091             return maxdam;
02092         }
02093         remove_ob(op);
02094         for (i = 0; i < NROFNEWOBJS(op); i++) { /* This doesn't handle op->more yet */
02095             object *tmp = arch_to_object(op->other_arch);
02096             int j;
02097 
02098             tmp->stats.hp = op->stats.hp;
02099             if (friendly) {
02100                 SET_FLAG(tmp, FLAG_FRIENDLY);
02101                 add_friendly_object(tmp);
02102                 tmp->attack_movement = PETMOVE;
02103                 if (owner != NULL)
02104                     set_owner(tmp, owner);
02105             }
02106             if (unaggressive)
02107                 SET_FLAG(tmp, FLAG_UNAGGRESSIVE);
02108             j = find_first_free_spot(tmp, op->map, op->x, op->y);
02109             if (j == -1) /* No spot to put this monster */
02110                 free_object(tmp);
02111             else {
02112                 tmp->x = op->x+freearr_x[j],
02113                 tmp->y = op->y+freearr_y[j];
02114                 insert_ob_in_map(tmp, op->map, NULL, 0);
02115             }
02116         }
02117         if (friendly)
02118             remove_friendly_object(op);
02119         free_object(op);
02120     } else if (type&AT_DRAIN && hitter->type == GRIMREAPER && hitter->value++ > 10) {
02121         remove_ob(hitter);
02122         free_object(hitter);
02123     }
02124     return maxdam;
02125 }
02126 
02137 static void poison_living(object *op, object *hitter, int dam) {
02138     archetype *at = find_archetype("poisoning");
02139     object *tmp = present_arch_in_ob(at, op);
02140 
02141     if (tmp == NULL) {
02142         if ((tmp = arch_to_object(at)) == NULL)
02143             LOG(llevError, "Failed to clone arch poisoning.\n");
02144         else {
02145             tmp = insert_ob_in_ob(tmp, op);
02146             /*  peterm:  give poisoning some teeth.  It should
02147              * be able to kill things better than it does:
02148              * damage should be dependent something--I choose to
02149              * do this:  if it's a monster, the damage from the
02150              * poisoning goes as the level of the monster/2.
02151              * If anything else, goes as damage.
02152              */
02153 
02154             if (QUERY_FLAG(hitter, FLAG_ALIVE))
02155                 tmp->stats.dam += hitter->level/2;
02156             else
02157                 tmp->stats.dam = dam;
02158 
02159             copy_owner(tmp, hitter);   /*  so we get credit for poisoning kills */
02160             if (hitter->skill && hitter->skill != tmp->skill) {
02161                 if (tmp->skill)
02162                     free_string(tmp->skill);
02163                 tmp->skill = add_refcount(hitter->skill);
02164             }
02165 
02166             tmp->stats.food += dam;  /*  more damage, longer poisoning */
02167 
02168             if (op->type == PLAYER) {
02169                 /* player looses stats, maximum is -10 of each */
02170                 tmp->stats.Con = MAX(-(dam/4+1), -10);
02171                 tmp->stats.Str = MAX(-(dam/3+2), -10);
02172                 tmp->stats.Dex = MAX(-(dam/6+1), -10);
02173                 tmp->stats.Int = MAX(-dam/7, -10);
02174                 SET_FLAG(tmp, FLAG_APPLIED);
02175                 fix_object(op);
02176                 draw_ext_info(NDI_UNIQUE, 0, op,
02177                               MSG_TYPE_ATTRIBUTE, MSG_TYPE_ATTRIBUTE_BAD_EFFECT_START,
02178                               "You suddenly feel very ill.", NULL);
02179             }
02180             if (hitter->type == PLAYER)
02181                 draw_ext_info_format(NDI_UNIQUE, 0, hitter,
02182                                      MSG_TYPE_ATTACK, MSG_TYPE_ATTACK_DID_HIT,
02183                                      "You poison %s.",
02184                                      "You poison %s.",
02185                                      op->name);
02186             else if (get_owner(hitter) != NULL && hitter->owner->type == PLAYER)
02187                 draw_ext_info_format(NDI_UNIQUE, 0, hitter->owner,
02188                                      MSG_TYPE_ATTACK, MSG_TYPE_ATTACK_PET_HIT,
02189                                      "Your %s poisons %s.",
02190                                      "Your %s poisons %s.",
02191                                      hitter->name, op->name);
02192         }
02193         tmp->speed_left = 0;
02194     } else
02195         tmp->stats.food++;
02196 }
02197 
02208 static void slow_living(object *op, object *hitter, int dam) {
02209     archetype *at = find_archetype("slowness");
02210     object *tmp;
02211 
02212     if (at == NULL) {
02213         LOG(llevError, "Can't find slowness archetype.\n");
02214     }
02215     if ((tmp = present_arch_in_ob(at, op)) == NULL) {
02216         tmp = arch_to_object(at);
02217         tmp = insert_ob_in_ob(tmp, op);
02218         draw_ext_info(NDI_UNIQUE, 0, op, MSG_TYPE_ATTRIBUTE, MSG_TYPE_ATTRIBUTE_BAD_EFFECT_START,
02219                       "The world suddenly moves very fast!", NULL);
02220     } else
02221         tmp->stats.food++;
02222     SET_FLAG(tmp, FLAG_APPLIED);
02223     tmp->speed_left = 0;
02224     fix_object(op);
02225 }
02226 
02237 void confuse_living(object *op, object *hitter, int dam) {
02238     object *tmp;
02239     int maxduration;
02240 
02241     tmp = present_in_ob_by_name(FORCE, "confusion", op);
02242     if (!tmp) {
02243         tmp = create_archetype(FORCE_NAME);
02244         tmp = insert_ob_in_ob(tmp, op);
02245     }
02246 
02247     /* Duration added per hit and max. duration of confusion both depend
02248      *  on the player's resistance
02249      */
02250     tmp->speed = 0.05;
02251     tmp->subtype = FORCE_CONFUSION;
02252     tmp->duration = 8+MAX(1, 5*(100-op->resist[ATNR_CONFUSION])/100);
02253     if (tmp->name)
02254         free_string(tmp->name);
02255     tmp->name = add_string("confusion");
02256     maxduration = MAX(2, 30*(100-op->resist[ATNR_CONFUSION])/100);
02257     if (tmp->duration > maxduration)
02258         tmp->duration = maxduration;
02259 
02260     if (op->type == PLAYER && !QUERY_FLAG(op, FLAG_CONFUSED))
02261         draw_ext_info(NDI_UNIQUE, 0, op, MSG_TYPE_ATTRIBUTE, MSG_TYPE_ATTRIBUTE_BAD_EFFECT_START,
02262                       "You suddenly feel very confused!", NULL);
02263     SET_FLAG(op, FLAG_CONFUSED);
02264 }
02265 
02276 void blind_living(object *op, object *hitter, int dam) {
02277     object *tmp, *owner;
02278     char victim[MAX_BUF];
02279 
02280     /* Save some work if we know it isn't going to affect the player */
02281     if (op->resist[ATNR_BLIND] == 100)
02282         return;
02283 
02284     tmp = present_in_ob(BLINDNESS, op);
02285     if (!tmp) {
02286         tmp = create_archetype("blindness");
02287         SET_FLAG(tmp, FLAG_BLIND);
02288         SET_FLAG(tmp, FLAG_APPLIED);
02289         /* use floats so we don't lose too much precision due to rounding errors.
02290          * speed is a float anyways.
02291          */
02292         tmp->speed = tmp->speed*(100.0-(float)op->resist[ATNR_BLIND])/100;
02293 
02294         tmp = insert_ob_in_ob(tmp, op);
02295         change_abil(op, tmp);  /* Mostly to display any messages */
02296         fix_object(op);        /* This takes care of some other stuff */
02297 
02298         if (hitter->owner)
02299             owner = get_owner(hitter);
02300         else
02301             owner = hitter;
02302 
02303         query_name(op, victim, MAX_BUF);
02304         draw_ext_info_format(NDI_UNIQUE, 0, owner, MSG_TYPE_ATTACK, MSG_TYPE_ATTACK_DID_HIT,
02305                              "Your attack blinds %s!",
02306                              "Your attack blinds %s!",
02307                              victim);
02308     }
02309     tmp->stats.food += dam;
02310     if (tmp->stats.food > 10)
02311         tmp->stats.food = 10;
02312 }
02313 
02324 void paralyze_living(object *op, object *hitter, int dam) {
02325     float effect, max;
02326     /* object *tmp; */
02327 
02328     /* This is strange stuff... someone knows for what this is
02329      * written? Well, i think this can and should be removed
02330     */
02331 
02332 /*
02333     if ((tmp = present(PARAIMAGE, op->map, op->x, op->y)) == NULL) {
02334         tmp = clone_arch(PARAIMAGE);
02335         tmp->x = op->x,
02336         tmp->y = op->y;
02337         insert_ob_in_map(tmp, op->map, tmp, INS_NO_MERGE|INS_NO_WALK_ON);
02338     }
02339 */
02340 
02341     /* Do this as a float - otherwise, rounding might very well reduce this to 0 */
02342     effect = (float)dam*3.0*(100.0-op->resist[ATNR_PARALYZE])/100;
02343 
02344     if (effect == 0)
02345         return;
02346 
02347     op->speed_left -= FABS(op->speed)*effect;
02348 /*  tmp->stats.food += (signed short)
02349         effect/op->speed;*/
02350 
02351     /* max number of ticks to be affected for. */
02352     max = (100-op->resist[ATNR_PARALYZE])/2;
02353     if (op->speed_left < -(FABS(op->speed)*max))
02354         op->speed_left = (float)-(FABS(op->speed)*max);
02355 
02356 /*  tmp->stats.food = (signed short)(max/FABS(op->speed)); */
02357 }
02358 
02379 static void deathstrike_living(object *op, object *hitter, int *dam) {
02380     int atk_lev, def_lev, kill_lev;
02381 
02382     if (hitter->slaying) {
02383         if (!((QUERY_FLAG(op, FLAG_UNDEAD) && strstr(hitter->slaying, undead_name))
02384         || (op->race && strstr(hitter->slaying, op->race))))
02385             return;
02386     } else
02387         if (QUERY_FLAG(op, FLAG_UNDEAD))
02388             return;
02389 
02390     def_lev = op->level;
02391     if (def_lev < 1) {
02392         LOG(llevError, "BUG: arch %s, name %s with level < 1\n", op->arch->name, op->name);
02393         def_lev = 1;
02394     }
02395     atk_lev = (hitter->chosen_skill ? hitter->chosen_skill->level : hitter->level)/2;
02396     /* LOG(llevDebug, "Deathstrike - attack level %d, defender level %d\n", atk_lev, def_lev); */
02397 
02398     if (atk_lev >= def_lev) {
02399         kill_lev = random_roll(0, atk_lev-1, hitter, PREFER_HIGH);
02400 
02401         /* Note that the below effectively means the ratio of the atk vs
02402          * defener level is important - if level 52 character has very little
02403          * chance of killing a level 50 monster.  This should probably be
02404          * redone.
02405          */
02406         if (kill_lev >= def_lev) {
02407             *dam = op->stats.hp+10; /* take all hp. they can still save for 1/2 */
02408             /* I think this doesn't really do much.  Because of
02409              * integer rounding, this only makes any difference if the
02410              * attack level is double the defender level.
02411              */
02412             *dam *= kill_lev/def_lev;
02413         }
02414     } else {
02415         *dam = 0;  /* no harm done */
02416     }
02417 }
02418 
02431 static void thrown_item_effect(object *hitter, object *victim) {
02432     if (!QUERY_FLAG(hitter, FLAG_ALIVE)) {
02433         /* May not need a switch for just 2 types, but this makes it
02434          * easier for expansion.
02435          */
02436         switch (hitter->type) {
02437         case POTION:
02438             /* should player get a save throw instead of checking magic protection? */
02439             if (QUERY_FLAG(victim, FLAG_ALIVE)
02440             && !QUERY_FLAG(victim, FLAG_UNDEAD)
02441             && (victim->resist[ATNR_MAGIC] < 60))
02442                 (void)ob_apply(hitter, victim, 0);
02443             break;
02444 
02445         case POISON: /* poison drinks */
02446             /* As with potions, should monster get a save? */
02447             if (QUERY_FLAG(victim, FLAG_ALIVE)
02448             && !QUERY_FLAG(victim, FLAG_UNDEAD)
02449             && (victim->resist[ATNR_POISON] < 60))
02450                 (void)ob_apply(victim, hitter, 0);
02451             break;
02452 
02453             /* Removed case statements that did nothing.
02454              * food may be poisonous, but monster must be willing to eat it,
02455              * so we don't handle it here.
02456              * Containers should perhaps break open, but that code was disabled.
02457              */
02458         }
02459     }
02460 }
02461 
02471 static int adj_attackroll(object *hitter, object *target) {
02472     object *attacker = hitter;
02473     int adjust = 0;
02474 
02475     /* safety */
02476     if (!target || !hitter || !hitter->map || !target->map || !on_same_map(hitter, target)) {
02477         LOG(llevError, "BUG: adj_attackroll(): hitter and target not on same map\n");
02478         return 0;
02479     }
02480 
02481     /* aimed missiles use the owning object's sight */
02482     if (is_aimed_missile(hitter)) {
02483         if ((attacker = get_owner(hitter)) == NULL)
02484             attacker = hitter;
02485         /* A player who saves but hasn't quit still could have objects
02486          * owned by him - need to handle that case to avoid crashes.
02487          */
02488         if (QUERY_FLAG(attacker, FLAG_REMOVED))
02489             attacker = hitter;
02490     } else if (!QUERY_FLAG(hitter, FLAG_ALIVE))
02491         return 0;
02492 
02493     /* determine the condtions under which we make an attack.
02494      * Add more cases, as the need occurs. */
02495 
02496     if (!can_see_enemy(attacker, target)) {
02497         /* target is unseen */
02498         if (target->invisible || QUERY_FLAG(attacker, FLAG_BLIND))
02499             adjust -= 10;
02500         /* dark map penalty for the hitter (lacks infravision if we got here). */
02501         else if (target->map && target->map->darkness > 0 && !stand_in_light(target))
02502             adjust -= target->map->darkness;
02503     }
02504 
02505     if (QUERY_FLAG(attacker, FLAG_SCARED))
02506         adjust -= 3;
02507 
02508     if (QUERY_FLAG(target, FLAG_UNAGGRESSIVE))
02509         adjust += 1;
02510 
02511     if (QUERY_FLAG(target, FLAG_SCARED))
02512         adjust += 1;
02513 
02514     if (QUERY_FLAG(attacker, FLAG_CONFUSED))
02515         adjust -= 3;
02516 
02517     /* if we attack at a different 'altitude' its harder */
02518     if ((attacker->move_type&target->move_type) == 0)
02519         adjust -= 2;
02520 
02521     return adjust;
02522 }
02523 
02532 static int is_aimed_missile(object *op) {
02533     /* I broke what used to be one big if into a few nested
02534      * ones so that figuring out the logic is at least possible.
02535      */
02536     if (op && (op->move_type&MOVE_FLYING)) {
02537         if (op->type == ARROW || op->type == THROWN_OBJ)
02538             return 1;
02539         else if (op->type == SPELL_EFFECT
02540         && (op->subtype == SP_BULLET || op->subtype == SP_EXPLOSION))
02541             return 1;
02542     }
02543     return 0;
02544 }