Atrinik Server 2.5
server/attack.c
Go to the documentation of this file.
00001 /************************************************************************
00002 *            Atrinik, a Multiplayer Online Role Playing Game            *
00003 *                                                                       *
00004 *    Copyright (C) 2009-2011 Alex Tokar and Atrinik Development Team    *
00005 *                                                                       *
00006 * Fork from Daimonin (Massive Multiplayer Online Role Playing Game)     *
00007 * and Crossfire (Multiplayer game for X-windows).                       *
00008 *                                                                       *
00009 * This program is free software; you can redistribute it and/or modify  *
00010 * it under the terms of the GNU General Public License as published by  *
00011 * the Free Software Foundation; either version 2 of the License, or     *
00012 * (at your option) any later version.                                   *
00013 *                                                                       *
00014 * This program is distributed in the hope that it will be useful,       *
00015 * but WITHOUT ANY WARRANTY; without even the implied warranty of        *
00016 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the         *
00017 * GNU General Public License for more details.                          *
00018 *                                                                       *
00019 * You should have received a copy of the GNU General Public License     *
00020 * along with this program; if not, write to the Free Software           *
00021 * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.             *
00022 *                                                                       *
00023 * The author can be reached at admin@atrinik.org                        *
00024 ************************************************************************/
00025 
00030 #include <global.h>
00031 
00032 #define ATTACK_HIT_DAMAGE(_op, _anum)       dam = dam * ((double) _op->attack[_anum] * (double) 0.01); dam >= 1.0f ? (damage = (int) dam) : (damage = 1)
00033 #define ATTACK_PROTECT_DAMAGE(_op, _anum)   dam = dam * ((double) (100 - _op->protection[_anum]) * (double) 0.01)
00034 
00035 static void thrown_item_effect(object *hitter, object *victim);
00036 static int get_attack_mode(object **target, object **hitter,int *simple_attack);
00037 static int abort_attack(object *target, object *hitter, int simple_attack);
00038 static int attack_ob_simple(object *op, object *hitter, int base_dam, int base_wc);
00039 static void send_attack_msg(object *op, object *hitter, int attacknum, int dam, int damage);
00040 static int hit_player_attacktype(object *op, object *hitter, int damage, uint32 attacknum);
00041 static void poison_player(object *op, object *hitter, float dam);
00042 static void slow_living(object *op);
00043 static void blind_living(object *op, object *hitter, int dam);
00044 static int adj_attackroll(object *hitter, object *target);
00045 static int is_aimed_missile(object *op);
00046 
00052 int attack_ob(object *op, object *hitter)
00053 {
00054     if (op->head)
00055     {
00056         op = op->head;
00057     }
00058 
00059     if (hitter->head)
00060     {
00061         hitter = hitter->head;
00062     }
00063 
00064     return attack_ob_simple(op, hitter, hitter->stats.dam, hitter->stats.wc);
00065 }
00066 
00074 static int attack_ob_simple(object *op, object *hitter, int base_dam, int base_wc)
00075 {
00076     int simple_attack, roll, dam = 0;
00077     tag_t op_tag, hitter_tag;
00078 
00079     if (op->head)
00080     {
00081         op = op->head;
00082     }
00083 
00084     if (hitter->head)
00085     {
00086         hitter = hitter->head;
00087     }
00088 
00089     if (get_attack_mode(&op, &hitter, &simple_attack))
00090     {
00091         return 1;
00092     }
00093 
00094     /* Trigger the ATTACK event */
00095     trigger_event(EVENT_ATTACK, hitter, hitter, op, NULL, 0, base_dam, base_wc, SCRIPT_FIX_ALL);
00096 
00097     op_tag = op->count;
00098     hitter_tag = hitter->count;
00099 
00100     if (!hitter->stats.wc_range)
00101     {
00102         LOG(llevDebug, "attack.c: hitter %s has wc_range == 0! (set to 20)\n", query_name(hitter, NULL));
00103         hitter->stats.wc_range = 20;
00104     }
00105 
00106     roll = rndm(0, hitter->stats.wc_range);
00107 
00108     /* Adjust roll for various situations. */
00109     if (!simple_attack)
00110     {
00111         roll += adj_attackroll(hitter, op);
00112     }
00113 
00114     /* So we do one swing */
00115     if (hitter->type == PLAYER)
00116     {
00117         CONTR(hitter)->anim_flags |= PLAYER_AFLAG_ENEMY;
00118     }
00119 
00120     /* Force player to face enemy */
00121     if (hitter->type == PLAYER)
00122     {
00123         rv_vector dir;
00124 
00125         if (get_rangevector(hitter, op, &dir, RV_NO_DISTANCE))
00126         {
00127             if (hitter->head)
00128             {
00129                 hitter->head->anim_enemy_dir = dir.direction;
00130                 hitter->head->facing = dir.direction;
00131             }
00132             else
00133             {
00134                 hitter->anim_enemy_dir = dir.direction;
00135                 hitter->facing = dir.direction;
00136             }
00137         }
00138     }
00139 
00140     /* See if we hit the creature */
00141     if (roll >= hitter->stats.wc_range || op->stats.ac <= base_wc + roll)
00142     {
00143         int hitdam = base_dam;
00144 
00145         /* At this point NO ONE will still sleep */
00146         CLEAR_FLAG(op, FLAG_SLEEP);
00147 
00148         if (hitter->type == ARROW)
00149         {
00150             play_sound_map(hitter->map, CMD_SOUND_EFFECT, "arrow_hit.ogg", hitter->x, hitter->y, 0, 0);
00151         }
00152         else
00153         {
00154             if (hitter->attack[ATNR_SLASH])
00155             {
00156                 play_sound_map(hitter->map, CMD_SOUND_EFFECT, "hit_slash.ogg", hitter->x, hitter->y, 0, 0);
00157             }
00158             else if (hitter->attack[ATNR_CLEAVE])
00159             {
00160                 play_sound_map(hitter->map, CMD_SOUND_EFFECT, "hit_cleave.ogg", hitter->x, hitter->y, 0, 0);
00161             }
00162             else if (hitter->attack[ATNR_IMPACT])
00163             {
00164                 play_sound_map(hitter->map, CMD_SOUND_EFFECT, "hit_impact.ogg", hitter->x, hitter->y, 0, 0);
00165             }
00166             else
00167             {
00168                 play_sound_map(hitter->map, CMD_SOUND_EFFECT, "hit_pierce.ogg", hitter->x, hitter->y, 0, 0);
00169             }
00170         }
00171 
00172         if (!simple_attack)
00173         {
00174             /* Thrown items (hitter) will have various effects
00175              * when they hit the victim. For things like thrown daggers,
00176              * this sets 'hitter' to the actual dagger, and not the
00177              * wrapper object. */
00178             thrown_item_effect(hitter, op);
00179 
00180             if (was_destroyed(hitter, hitter_tag) || was_destroyed(op, op_tag) || abort_attack(op, hitter, simple_attack))
00181             {
00182                 return dam;
00183             }
00184         }
00185 
00186         /* Need to do at least 1 damage, otherwise there is no point
00187          * to go further and it will cause FPE's below. */
00188         if (hitdam <= 0)
00189         {
00190             hitdam = 1;
00191         }
00192 
00193         /* Handle monsters that hit back */
00194         if (!simple_attack && QUERY_FLAG(op, FLAG_HITBACK) && IS_LIVE(hitter))
00195         {
00196             hit_player(hitter, rndm(0, op->stats.dam), op, AT_PHYSICAL);
00197 
00198             if (was_destroyed(op, op_tag) || was_destroyed(hitter, hitter_tag) || abort_attack(op, hitter, simple_attack))
00199             {
00200                 return dam;
00201             }
00202         }
00203 
00204         dam = hit_player(op, rndm(hitdam / 2 + 1, hitdam), hitter, AT_PHYSICAL);
00205 
00206         if (was_destroyed(op, op_tag) || was_destroyed(hitter, hitter_tag) || abort_attack(op, hitter, simple_attack))
00207         {
00208             return dam;
00209         }
00210     }
00211     /* We missed */
00212     else
00213     {
00214         if (hitter->type != ARROW)
00215         {
00216             if (hitter->type == PLAYER)
00217             {
00218                 play_sound_map(hitter->map, CMD_SOUND_EFFECT, "miss_player1.ogg", hitter->x, hitter->y, 0, 0);
00219                 new_draw_info_format(0, COLOR_ORANGE, hitter, "You miss %s!", op->name);
00220 
00221                 if (op->type == PLAYER)
00222                 {
00223                     new_draw_info_format(0, COLOR_PURPLE, op, "%s misses you!", hitter->name);
00224                 }
00225             }
00226             else
00227             {
00228                 play_sound_map(hitter->map, CMD_SOUND_EFFECT, "miss_mob1.ogg", hitter->x, hitter->y, 0, 0);
00229                 new_draw_info_format(0, COLOR_PURPLE, op, "%s misses you!", hitter->name);
00230             }
00231         }
00232     }
00233 
00234     return dam;
00235 }
00236 
00247 int hit_player(object *op, int dam, object *hitter, int type)
00248 {
00249     object *hit_obj, *hitter_owner, *target_obj;
00250     int maxdam = 0;
00251     int attacknum, hit_level;
00252     int simple_attack;
00253     int rtn_kill = 0;
00254 
00255     /* If our target has no_damage 1 set or is wiz, we can't hurt him. */
00256     if (QUERY_FLAG (op, FLAG_WIZ) || QUERY_FLAG(op, FLAG_INVULNERABLE))
00257     {
00258         return 0;
00259     }
00260 
00261     if (hitter->head)
00262     {
00263         hitter = hitter->head;
00264     }
00265 
00266     if (op->head)
00267     {
00268         op = op->head;
00269     }
00270 
00271     /* Check if the object to hit has any HP left */
00272     if (op->stats.hp < 0)
00273     {
00274         return 0;
00275     }
00276 
00277     /* Get the hitter's owner. */
00278     hitter_owner = get_owner(hitter);
00279 
00280     /* Sanity check: If the hitter has ownercount (so it had an owner)
00281      * but the owner itself is no longer valid, we won't do any damage,
00282      * otherwise player could fire an arrow, logout, and the arrow itself
00283      * would cause damage to anything it hits, even friendly creatures. */
00284     if (hitter->ownercount && !hitter_owner)
00285     {
00286         return 0;
00287     }
00288 
00289     if (hitter_owner)
00290     {
00291         hit_obj = hitter_owner;
00292     }
00293     else
00294     {
00295         hit_obj = hitter;
00296     }
00297 
00298     if (!(target_obj = get_owner(op)))
00299     {
00300         target_obj = op;
00301     }
00302 
00303     /* Get from hitter object the right skill level. */
00304     if (hit_obj->type == PLAYER)
00305     {
00306         hit_level = SK_level(hit_obj);
00307     }
00308     else
00309     {
00310         hit_level = hitter->level;
00311     }
00312 
00313     /* Very useful sanity check */
00314     if (hit_level == 0 || target_obj->level == 0)
00315     {
00316         LOG(llevDebug, "hit_player(): hit or target object level == 0(h:>%s< (o:>%s<) l->%d t:>%s< (>%s<)(o:>%s<) l->%d\n", query_name(hitter, NULL), query_name(get_owner(hitter), NULL), hit_level, query_name(op, NULL), target_obj->arch->name, query_name(get_owner(op), NULL), target_obj->level);
00317     }
00318 
00319     /* Do not let friendly objects attack each other. */
00320     if (is_friend_of(hit_obj, op))
00321     {
00322         return 0;
00323     }
00324 
00325     if (hit_level > target_obj->level && hit_obj->type == MONSTER)
00326     {
00327         dam += (int) ((float) (dam / 2) * ((float) (hit_level - target_obj->level) / (target_obj->level > 25 ? 25.0f : (float) target_obj->level)));
00328     }
00329 
00330     /* Something hit player (can be disease or poison too), break
00331      * praying. */
00332     if (op->type == PLAYER && CONTR(op)->was_praying)
00333     {
00334         new_draw_info(0, COLOR_WHITE, op, "Your praying is disrupted.");
00335         CONTR(op)->praying = 0;
00336         CONTR(op)->was_praying = 0;
00337     }
00338 
00339     /* Check for PVP areas. */
00340     if (op->type == PLAYER || (get_owner(op) && op->owner->type == PLAYER))
00341     {
00342         if (hitter->type == PLAYER || (get_owner(hitter) && hitter->owner->type == PLAYER))
00343         {
00344             if (!pvp_area(op->type == PLAYER ? op : get_owner(op), hitter->type == PLAYER ? hitter : get_owner(hitter)))
00345             {
00346                 return 0;
00347             }
00348         }
00349     }
00350 
00351     /* Check objects are valid, on same map and set them to head when
00352      * needed. */
00353     if (get_attack_mode(&op, &hitter, &simple_attack))
00354     {
00355         return 0;
00356     }
00357 
00358     /* Go through and hit the player with each attacktype, one by one.
00359      * hit_player_attacktype only figures out the damage, doesn't inflict
00360      * it. It will do the appropriate action for attacktypes with
00361      * effects (slow, paralization, etc). */
00362     for (attacknum = 0; attacknum < NROFATTACKS; attacknum++)
00363     {
00364         if (hitter->attack[attacknum])
00365         {
00366             maxdam += hit_player_attacktype(op, hitter, dam, attacknum);
00367         }
00368     }
00369 
00370     /* If one gets attacked, the attacker will become the enemy */
00371     if (!OBJECT_VALID(op->enemy, op->enemy_count) && !IS_INVISIBLE(hit_obj, op) && !QUERY_FLAG(hit_obj, FLAG_INVULNERABLE))
00372     {
00373         set_npc_enemy(op, hit_obj, NULL);
00374     }
00375 
00376     /* This is needed to send the hit number animations to the clients */
00377     if (op->damage_round_tag != ROUND_TAG)
00378     {
00379         op->last_damage = 0;
00380         op->damage_round_tag = ROUND_TAG;
00381     }
00382 
00383     if (hit_obj->type == PLAYER)
00384     {
00385         CONTR(hit_obj)->stat_damage_dealt += maxdam;
00386     }
00387 
00388     if (target_obj->type == PLAYER)
00389     {
00390         CONTR(target_obj)->stat_damage_taken += maxdam;
00391     }
00392 
00393     op->last_damage += maxdam;
00394 
00395     /* Damage the target got */
00396     op->stats.hp -= maxdam;
00397 
00398     /* Check to see if monster runs away. */
00399     if ((op->stats.hp >= 0) && QUERY_FLAG(op, FLAG_MONSTER) && op->stats.hp < (signed short) (((float) op->run_away / 100.0f) * (float) op->stats.maxhp))
00400     {
00401         SET_FLAG(op, FLAG_RUN_AWAY);
00402     }
00403 
00404     /* rtn_kill is here negative! */
00405     if ((rtn_kill = kill_object(op, dam, hitter, type)))
00406     {
00407         return (maxdam + rtn_kill + 1);
00408     }
00409 
00410     /* Used to be ghosthit removal - we now use the ONE_HIT flag.  Note
00411      * that before if the player was immune to ghosthit, the monster
00412      * remained - that is no longer the case. */
00413     if (QUERY_FLAG(hitter, FLAG_ONE_HIT))
00414     {
00415         /* Remove, but don't drop inventory */
00416         remove_ob(hitter);
00417         check_walk_off(hitter, NULL, MOVE_APPLY_VANISHED);
00418     }
00419     /* Let's handle creatures that are splitting now */
00420     else if ((type & AT_PHYSICAL) && !OBJECT_FREE(op) && QUERY_FLAG(op, FLAG_SPLITTING))
00421     {
00422         int i;
00423         int unaggressive = QUERY_FLAG(op, FLAG_UNAGGRESSIVE);
00424 
00425         if (!op->other_arch)
00426         {
00427             LOG(llevBug, "SPLITTING without other_arch error.\n");
00428             return maxdam;
00429         }
00430 
00431         remove_ob(op);
00432 
00433         if (check_walk_off(op, NULL, MOVE_APPLY_VANISHED) == CHECK_WALK_OK)
00434         {
00435             /* This doesn't handle op->more yet */
00436             for (i = 0; i < op->stats.food; i++)
00437             {
00438                 object *tmp = arch_to_object(op->other_arch);
00439                 int j;
00440 
00441                 tmp->stats.hp = op->stats.hp;
00442 
00443                 if (unaggressive)
00444                 {
00445                     SET_FLAG(tmp, FLAG_UNAGGRESSIVE);
00446                 }
00447 
00448                 j = find_first_free_spot(tmp->arch, NULL, op->map,op->x, op->y);
00449 
00450                 /* Found spot to put this monster */
00451                 if (j >= 0)
00452                 {
00453                     tmp->x = op->x + freearr_x[j], tmp->y = op->y + freearr_y[j];
00454                     insert_ob_in_map(tmp,op->map, NULL, 0);
00455                 }
00456             }
00457         }
00458     }
00459 
00460     return maxdam;
00461 }
00462 
00471 int hit_map(object *op, int dir, int reduce)
00472 {
00473     object *tmp, *next, *tmp_obj, *tmp_head;
00474     mapstruct *map;
00475     int x, y;
00476     tag_t op_tag, next_tag = 0;
00477 
00478     if (OBJECT_FREE(op))
00479     {
00480         LOG(llevBug, "hit_map(): free object\n");
00481         return 0;
00482     }
00483 
00484     if (QUERY_FLAG(op, FLAG_REMOVED) || op->env != NULL)
00485     {
00486         LOG(llevBug, "hit_map(): hitter (arch %s, name %s) not on a map\n", op->arch->name, query_name(op, NULL));
00487         return 0;
00488     }
00489 
00490     if (op->head)
00491     {
00492         op = op->head;
00493     }
00494 
00495     op_tag = op->count;
00496 
00497     if (!op->map)
00498     {
00499         LOG(llevBug, "hit_map(): %s has no map.\n", query_name(op, NULL));
00500         return 0;
00501     }
00502 
00503     x = op->x + freearr_x[dir];
00504     y = op->y + freearr_y[dir];
00505 
00506     if (!(map = get_map_from_coord(op->map, &x, &y)))
00507     {
00508         return 0;
00509     }
00510 
00511     next = get_map_ob(map, x, y);
00512 
00513     if (next)
00514     {
00515         next_tag = next->count;
00516     }
00517 
00518     if (!(tmp_obj = get_owner(op)))
00519     {
00520         tmp_obj = op;
00521     }
00522 
00523     if (tmp_obj->head)
00524     {
00525         tmp_obj = tmp_obj->head;
00526     }
00527 
00528     while (next)
00529     {
00530         if (was_destroyed(next, next_tag))
00531         {
00532             /* There may still be objects that were above 'next', but there is no
00533              * simple way to find out short of copying all object references and
00534              * tags into a temporary array before we start processing the first
00535              * object.  That's why we just abort.
00536              *
00537              * This happens whenever attack spells (like fire) hit a pile
00538              * of objects. This is not a bug - nor an error. */
00539             break;
00540         }
00541 
00542         tmp = next;
00543         next = tmp->above;
00544 
00545         if (next)
00546         {
00547             next_tag = next->count;
00548         }
00549 
00550         if (OBJECT_FREE(tmp))
00551         {
00552             LOG(llevBug, "hit_map(): found freed object (%s)\n", tmp->arch->name ? tmp->arch->name : "<NULL>");
00553             break;
00554         }
00555 
00556         /* Something could have happened to 'tmp' while 'tmp->below' was processed.
00557          * For example, 'tmp' was put in an icecube.
00558          * This is one of the few cases where on_same_map should not be used. */
00559         if (tmp->map != map || tmp->x != x || tmp->y != y)
00560         {
00561             continue;
00562         }
00563 
00564         /* Cones with race set can only damage members of that race. */
00565         if (op->type == CONE && op->race && tmp->race != op->race)
00566         {
00567             continue;
00568         }
00569 
00570         /* First, we check player... */
00571         if (QUERY_FLAG(tmp, FLAG_IS_PLAYER))
00572         {
00573             if (IS_ATTACK_SPELL(op) && spell_attack_missed(op, tmp))
00574             {
00575                 continue;
00576             }
00577 
00578             hit_player(tmp, op->stats.dam, op, AT_INTERNAL);
00579 
00580             if (was_destroyed(op, op_tag))
00581             {
00582                 break;
00583             }
00584         }
00585         else if (IS_LIVE(tmp))
00586         {
00587             sint16 dam = op->stats.dam;
00588 
00589             tmp_head = HEAD(tmp);
00590 
00591             if (is_friend_of(tmp_obj, tmp_head))
00592             {
00593                 continue;
00594             }
00595 
00596             if (IS_ATTACK_SPELL(op) && spell_attack_missed(op, tmp_head))
00597             {
00598                 continue;
00599             }
00600 
00601             if (tmp->quick_pos && reduce)
00602             {
00603                 dam /= (tmp->quick_pos >> 4) + 1;
00604             }
00605 
00606             hit_player(tmp, dam, op, AT_INTERNAL);
00607 
00608             if (was_destroyed(op, op_tag))
00609             {
00610                 break;
00611             }
00612         }
00613     }
00614 
00615     return 0;
00616 }
00617 
00628 static int hit_player_attacktype(object *op, object *hitter, int damage, uint32 attacknum)
00629 {
00630     double dam = (double) damage;
00631 
00632     /* Sanity check */
00633     if (dam < 0)
00634     {
00635         LOG(llevBug, "hit_player_attacktype called with negative damage: %f from object: %s\n", dam, query_name(op, NULL));
00636         return 0;
00637     }
00638 
00639     if (hitter->slaying)
00640     {
00641         if (((op->race != NULL) && strstr(hitter->slaying, op->race)) || (op->arch && (op->arch->name != NULL) &&  strstr(op->arch->name, hitter->slaying)))
00642         {
00643             if (QUERY_FLAG(hitter, FLAG_IS_ASSASSINATION))
00644             {
00645                 damage = (int) ((double) damage * 2.25);
00646             }
00647             else
00648             {
00649                 damage = (int) ((double) damage * 1.75);
00650             }
00651 
00652             dam = (double) damage;
00653         }
00654     }
00655 
00656     /* AT_INTERNAL is supposed to do exactly dam. Put a case here so
00657      * people can't mess with that or it otherwise get confused. */
00658     if (attacknum == ATNR_INTERNAL)
00659     {
00660         /* Adjust damage */
00661         dam = dam * ((double) hitter->attack[ATNR_INTERNAL] / 100.0);
00662 
00663         /* handle special object attacks */
00664         /* we have a poison force object (that's the poison we had inserted) */
00665         if (hitter->type == POISONING)
00666         {
00667             /* Map to poison... */
00668             attacknum = ATNR_POISON;
00669 
00670             if (op->protection[attacknum] == 100)
00671             {
00672                 dam = 0;
00673                 send_attack_msg(op, hitter, attacknum, (int) dam, damage);
00674                 return 0;
00675             }
00676 
00677             /* Reduce to % protection */
00678             ATTACK_PROTECT_DAMAGE(op, attacknum);
00679         }
00680 
00681         if (damage && dam < 1.0)
00682         {
00683             dam = 1.0;
00684         }
00685 
00686         send_attack_msg(op, hitter, attacknum, (int) dam, damage);
00687         return (int) dam;
00688     }
00689 
00690     /* Quick check for immunity - if so, we skip here.
00691      * Our formula is (100 - resist) / 100 - so test for 100 = zero division */
00692     if (op->protection[attacknum] == 100)
00693     {
00694         ATTACK_HIT_DAMAGE(hitter, attacknum);
00695         send_attack_msg(op, hitter, attacknum, 0, dam);
00696         return 0;
00697     }
00698 
00699     switch (attacknum)
00700     {
00701         case ATNR_IMPACT:
00702         case ATNR_SLASH:
00703         case ATNR_CLEAVE:
00704         case ATNR_PIERCE:
00705             check_physically_infect(op, hitter);
00706 
00707             ATTACK_HIT_DAMAGE(hitter, attacknum);
00708             ATTACK_PROTECT_DAMAGE(op, attacknum);
00709 
00710             if (damage && dam < 1.0)
00711             {
00712                 dam = 1.0;
00713             }
00714 
00715             send_attack_msg(op, hitter, attacknum, (int) dam, damage);
00716             break;
00717 
00718         case ATNR_POISON:
00719             ATTACK_HIT_DAMAGE(hitter, attacknum);
00720             ATTACK_PROTECT_DAMAGE(op, attacknum);
00721 
00722             if (damage && dam < 1.0)
00723             {
00724                 dam = 1.0;
00725             }
00726 
00727             send_attack_msg(op, hitter, attacknum, (int) dam, damage);
00728 
00729             if (dam && IS_LIVE(op))
00730             {
00731                 poison_player(op, hitter, (float)dam);
00732             }
00733 
00734             break;
00735 
00736         case ATNR_CONFUSION:
00737         case ATNR_SLOW:
00738         case ATNR_PARALYZE:
00739         case ATNR_BLIND:
00740         {
00741             int level_diff = MIN(MAXLEVEL, MAX(0, op->level - hitter->level));
00742 
00743             if (op->speed && (QUERY_FLAG(op, FLAG_MONSTER) || op->type == PLAYER) && !(rndm(0, (attacknum == ATNR_SLOW ? 6 : 3) - 1)) && ((rndm(1, 20) + op->protection[attacknum] / 10) < savethrow[level_diff]))
00744             {
00745                 if (attacknum == ATNR_CONFUSION)
00746                 {
00747                     if (hitter->type == PLAYER)
00748                     {
00749                         new_draw_info_format(0, COLOR_ORANGE, hitter, "You confuse %s!", op->name);
00750                     }
00751 
00752                     if (op->type == PLAYER)
00753                     {
00754                         new_draw_info_format(0, COLOR_PURPLE, op, "%s confused you!", hitter->name);
00755                     }
00756 
00757                     confuse_living(op);
00758                 }
00759                 else if (attacknum == ATNR_SLOW)
00760                 {
00761                     if (hitter->type == PLAYER)
00762                     {
00763                         new_draw_info_format(0, COLOR_ORANGE, hitter, "You slow %s!", op->name);
00764                     }
00765 
00766                     if (op->type == PLAYER)
00767                     {
00768                         new_draw_info_format(0, COLOR_PURPLE, op, "%s slowed you!", hitter->name);
00769                     }
00770 
00771                     slow_living(op);
00772                 }
00773                 else if (attacknum == ATNR_PARALYZE)
00774                 {
00775                     if (hitter->type == PLAYER)
00776                     {
00777                         new_draw_info_format(0, COLOR_ORANGE, hitter, "You paralyze %s!", op->name);
00778                     }
00779 
00780                     if (op->type == PLAYER)
00781                     {
00782                         new_draw_info_format(0, COLOR_PURPLE, op, "%s paralyzed you!", hitter->name);
00783                     }
00784 
00785                     paralyze_living(op, (int) dam);
00786                 }
00787                 else if (attacknum == ATNR_BLIND && !QUERY_FLAG(op, FLAG_UNDEAD))
00788                 {
00789                     if (hitter->type == PLAYER)
00790                     {
00791                         new_draw_info_format(0, COLOR_ORANGE, hitter, "You blind %s!", op->name);
00792                     }
00793 
00794                     if (op->type == PLAYER)
00795                     {
00796                         new_draw_info_format(0, COLOR_PURPLE, op, "%s blinded you!", hitter->name);
00797                     }
00798 
00799                     blind_living(op, hitter, (int) dam);
00800                 }
00801             }
00802 
00803             dam = 0;
00804         }
00805 
00806         break;
00807 
00808         default:
00809             ATTACK_HIT_DAMAGE(hitter, attacknum);
00810             ATTACK_PROTECT_DAMAGE(op, attacknum);
00811 
00812             if (damage && dam < 1.0)
00813             {
00814                 dam = 1.0;
00815             }
00816 
00817             send_attack_msg(op, hitter, attacknum, (int) dam, damage);
00818             break;
00819     }
00820 
00821     return (int) dam;
00822 }
00823 
00832 static void send_attack_msg(object *op, object *hitter, int attacknum, int dam, int damage)
00833 {
00834     object *orig_hitter = hitter;
00835 
00836     if (op->type == PLAYER)
00837     {
00838         new_draw_info_format(0, COLOR_PURPLE, op, "%s hit you for %d (%d) damage.", hitter->name, dam, dam - damage);
00839     }
00840 
00841     if (hitter->type == PLAYER || ((hitter = get_owner(hitter)) && hitter->type == PLAYER))
00842     {
00843         new_draw_info_format(0, COLOR_ORANGE, hitter, "You hit %s for %d (%d) with %s.", op->name, dam, dam - damage, attacknum == ATNR_INTERNAL ? orig_hitter->name : attack_name[attacknum]);
00844     }
00845 }
00846 
00852 static void share_kill_exp_one(object *op, sint64 exp, object *skill)
00853 {
00854     if (exp)
00855     {
00856         add_exp(op, exp, skill->stats.sp, 0);
00857     }
00858     else
00859     {
00860         new_draw_info(0, COLOR_WHITE, op, "Your enemy wasn't worth any experience to you.");
00861     }
00862 }
00863 
00871 static void share_kill_exp(object *op, sint64 exp, object *skill)
00872 {
00873     int shares = 0, count = 0;
00874     party_struct *party;
00875     objectlink *ol;
00876 
00877     if (!CONTR(op)->party)
00878     {
00879         share_kill_exp_one(op, exp, skill);
00880         return;
00881     }
00882 
00883     party = CONTR(op)->party;
00884 
00885     for (ol = party->members; ol; ol = ol->next)
00886     {
00887         if (on_same_map(ol->objlink.ob, op))
00888         {
00889             sint16 skill_id = party_member_get_skill(ol->objlink.ob, skill);
00890 
00891             if (skill_id == NO_SKILL_READY)
00892             {
00893                 continue;
00894             }
00895 
00896             count++;
00897             shares += (CONTR(ol->objlink.ob)->skill_ptr[skill_id]->level + 4);
00898         }
00899     }
00900 
00901     if (count == 1 || shares > exp)
00902     {
00903         share_kill_exp_one(op, exp, skill);
00904     }
00905     else
00906     {
00907         sint64 share = exp / shares, given = 0, nexp;
00908 
00909         for (ol = party->members; ol; ol = ol->next)
00910         {
00911             if (ol->objlink.ob != op && on_same_map(ol->objlink.ob, op))
00912             {
00913                 sint16 skill_id = party_member_get_skill(ol->objlink.ob, skill);
00914 
00915                 if (skill_id == NO_SKILL_READY)
00916                 {
00917                     continue;
00918                 }
00919 
00920                 nexp = (CONTR(ol->objlink.ob)->skill_ptr[skill_id]->level + 4) * share;
00921                 add_exp(ol->objlink.ob, nexp, skill_id, 0);
00922                 given += nexp;
00923             }
00924         }
00925 
00926         exp -= given;
00927         share_kill_exp_one(op, exp, skill);
00928     }
00929 }
00930 
00938 int kill_object(object *op, int dam, object *hitter, int type)
00939 {
00940     int maxdam, battleg;
00941     sint64 exp = 0;
00942     object *owner;
00943 
00944     (void) dam;
00945 
00946     /* Still got some HP left? */
00947     if (op->stats.hp > 0)
00948     {
00949         return -1;
00950     }
00951 
00952     /* Cannot kill wizards. */
00953     if (QUERY_FLAG(op, FLAG_WIZ))
00954     {
00955         return 0;
00956     }
00957 
00958     /* Trigger the DEATH event */
00959     if (trigger_event(EVENT_DEATH, hitter, op, NULL, NULL, type, 0, 0, SCRIPT_FIX_ALL))
00960     {
00961         return 0;
00962     }
00963 
00964     maxdam = op->stats.hp - 1;
00965 
00966     /* Only when some damage is stored, and we're on a map. */
00967     if (op->damage_round_tag == ROUND_TAG && op->map)
00968     {
00969         SET_MAP_DAMAGE(op->map, op->x, op->y, op->last_damage);
00970         SET_MAP_RTAG(op->map, op->x, op->y, ROUND_TAG);
00971     }
00972 
00973     if (op->map)
00974     {
00975         play_sound_map(op->map, CMD_SOUND_EFFECT, "kill.ogg", op->x, op->y, 0, 0);
00976     }
00977 
00978     /* Figure out who to credit for the kill. */
00979     owner = get_owner(hitter);
00980 
00981     if (!owner)
00982     {
00983         owner = hitter;
00984     }
00985 
00986     /* Is the victim in PvP area? */
00987     battleg = pvp_area(NULL, op);
00988 
00989     /* Player killed something. */
00990     if (owner->type == PLAYER)
00991     {
00992         if (owner != hitter)
00993         {
00994             new_draw_info_format(0, COLOR_WHITE, owner, "You killed %s with %s.", query_name(op, NULL), query_name(hitter, NULL));
00995         }
00996         else
00997         {
00998             new_draw_info_format(0, COLOR_WHITE, owner, "You killed %s.", query_name(op, NULL));
00999         }
01000 
01001         if (op->type == MONSTER)
01002         {
01003             shstr *faction, *faction_kill_penalty;
01004 
01005             CONTR(owner)->stat_kills_mob++;
01006             statistic_update("kills", owner, 1, op->name);
01007 
01008             if ((faction = object_get_value(op, "faction")) && (faction_kill_penalty = object_get_value(op, "faction_kill_penalty")))
01009             {
01010                 player_faction_reputation_update(CONTR(owner), faction, -atoll(faction_kill_penalty));
01011             }
01012         }
01013         else if (op->type == PLAYER)
01014         {
01015             CONTR(owner)->stat_kills_pvp++;
01016         }
01017     }
01018 
01019     /* Killed a player in PvP area. */
01020     if (battleg && op->type == PLAYER && owner->type == PLAYER)
01021     {
01022         new_draw_info(0, COLOR_WHITE, owner, "Your foe has fallen!\nVICTORY!!!");
01023     }
01024 
01025     /* Killed a monster and it wasn't in PvP area, so give exp. */
01026     if (!battleg && owner->type == PLAYER && op->type != PLAYER)
01027     {
01028         object *skill;
01029 
01030         /* Figure out the skill that should gain experience. If the hitter
01031          * has chosen_skill set, we will use that. */
01032         if (hitter->chosen_skill)
01033         {
01034             skill = hitter->chosen_skill;
01035         }
01036         /* Otherwise try to use owner's chosen_skill. */
01037         else
01038         {
01039             skill = owner->chosen_skill;
01040         }
01041 
01042         if (skill)
01043         {
01044             /* Calculate how much experience to gain. */
01045             exp = calc_skill_exp(owner, op, skill->level);
01046             /* Give the experience, sharing it with party members if applicable. */
01047             share_kill_exp(owner, exp, skill);
01048         }
01049     }
01050 
01051     /* Player has been killed. */
01052     if (op->type == PLAYER)
01053     {
01054         /* Tell everyone that this player has died. */
01055         if (get_owner(hitter))
01056         {
01057             new_draw_info_format(NDI_ALL, COLOR_WHITE, NULL, "%s killed %s with %s%s.", hitter->owner->name, query_name(op, NULL), query_name(hitter, NULL), battleg ? " (duel)" : "");
01058         }
01059         else
01060         {
01061             new_draw_info_format(NDI_ALL, COLOR_WHITE, NULL, "%s killed %s%s.", hitter->name, op->name, battleg ? " (duel)" : "");
01062         }
01063 
01064         /* Update player's killer. */
01065         if (owner->type == PLAYER)
01066         {
01067             char race[MAX_BUF];
01068 
01069             snprintf(CONTR(op)->killer, sizeof(CONTR(op)->killer), "%s the %s", owner->name, player_get_race_class(owner, race, sizeof(race)));
01070         }
01071         else
01072         {
01073             strncpy(CONTR(op)->killer, owner->name, sizeof(CONTR(op)->killer) - 1);
01074         }
01075 
01076         /* And actually kill the player. */
01077         kill_player(op);
01078     }
01079     /* Monster or something else has been killed. */
01080     else
01081     {
01082         /* Remove the monster from the active list. */
01083         op->speed = 0.0f;
01084         update_ob_speed(op);
01085 
01086         /* Rules:
01087          * 1. Monster will drop corpse for his target, not the killer (unless killer == target).
01088          * 2. NPC kill hit will overwrite player target on drop.
01089          * 3. Kill hit will count if target was an NPC. */
01090         if (owner->type != PLAYER || !op->enemy || op->enemy->type != PLAYER)
01091         {
01092             op->enemy = owner;
01093             op->enemy_count = owner->count;
01094         }
01095 
01096         /* Monster killed another monster. */
01097         if (hitter->type == MONSTER || (get_owner(hitter) && hitter->owner->type == MONSTER))
01098         {
01099             /* No loot */
01100             SET_FLAG(op, FLAG_STARTEQUIP);
01101             /* Force an empty corpse though. */
01102             SET_FLAG(op, FLAG_CORPSE_FORCED);
01103         }
01104         /* No exp, no loot and no corpse. */
01105         else if (!exp)
01106         {
01107             SET_FLAG(op, FLAG_STARTEQUIP);
01108         }
01109 
01110         destruct_ob(op);
01111     }
01112 
01113     return maxdam;
01114 }
01115 
01123 static int get_attack_mode(object **target, object **hitter, int *simple_attack)
01124 {
01125     if (OBJECT_FREE(*target) || OBJECT_FREE(*hitter))
01126     {
01127         LOG(llevBug, "get_attack_mode(): freed object\n");
01128         return 1;
01129     }
01130 
01131     if ((*target)->head)
01132     {
01133         *target = (*target)->head;
01134     }
01135 
01136     if ((*hitter)->head)
01137     {
01138         *hitter = (*hitter)->head;
01139     }
01140 
01141     if ((*hitter)->env != NULL || (*target)->env != NULL || (*hitter)->map == NULL)
01142     {
01143         *simple_attack = 1;
01144         return 0;
01145     }
01146 
01147     if (QUERY_FLAG(*target, FLAG_REMOVED) || QUERY_FLAG(*hitter, FLAG_REMOVED) || !on_same_map((*hitter), (*target)))
01148     {
01149         LOG(llevBug, "hitter (arch %s, name %s) with no relation to target\n", (*hitter)->arch->name, query_name(*hitter, NULL));
01150         return 1;
01151     }
01152 
01153     *simple_attack = 0;
01154     return 0;
01155 }
01156 
01164 static int abort_attack(object *target, object *hitter, int simple_attack)
01165 {
01166     int new_mode;
01167 
01168     if (hitter->env == target || target->env == hitter)
01169     {
01170         new_mode = 1;
01171     }
01172     else if (QUERY_FLAG(target, FLAG_REMOVED) || QUERY_FLAG(hitter, FLAG_REMOVED) || hitter->map == NULL || !on_same_map(hitter, target))
01173     {
01174         return 1;
01175     }
01176     else
01177     {
01178         new_mode = 0;
01179     }
01180 
01181     return new_mode != simple_attack;
01182 }
01183 
01191 object *hit_with_arrow(object *op, object *victim)
01192 {
01193     object *container, *hitter;
01194     int hit_something = 0;
01195     tag_t hitter_tag;
01196     sint16 victim_x, victim_y;
01197     mapstruct *victim_map;
01198 
01199     /* Disassemble missile */
01200     if (op->inv)
01201     {
01202         container = op;
01203         hitter = op->inv;
01204         remove_ob(hitter);
01205         insert_ob_in_map(hitter, container->map, hitter, INS_NO_MERGE | INS_NO_WALK_ON);
01206     }
01207     else
01208     {
01209         container = NULL;
01210         hitter = op;
01211     }
01212 
01213     /* Try to hit victim */
01214     victim_x = victim->x;
01215     victim_y = victim->y;
01216     victim_map = victim->map;
01217     hitter_tag = hitter->count;
01218 
01219     if (HAS_EVENT(hitter, EVENT_ATTACK))
01220     {
01221         /* Trigger the ATTACK event */
01222         trigger_event(EVENT_ATTACK, hitter, hitter, victim, NULL, 0, op->stats.dam, op->stats.wc, SCRIPT_FIX_ALL);
01223     }
01224     else
01225     {
01226         hit_something = attack_ob_simple(victim, hitter, op->stats.dam, op->stats.wc);
01227     }
01228 
01229     /* Arrow attacks door, rune of summoning is triggered, demon is put on
01230      * arrow, move_apply() calls this function, arrow sticks in demon,
01231      * attack_ob_simple() returns, and we've got an arrow that still exists
01232      * but is no longer on the map. Ugh. (Beware: Such things can happen at
01233      * other places as well!) */
01234     if (was_destroyed(hitter, hitter_tag) || hitter->env != NULL)
01235     {
01236         if (container)
01237         {
01238             remove_ob(container);
01239             check_walk_off(container, NULL, MOVE_APPLY_VANISHED);
01240         }
01241 
01242         return NULL;
01243     }
01244 
01245     /* Missile hit victim */
01246     if (hit_something)
01247     {
01248         if (container)
01249         {
01250             remove_ob(container);
01251             check_walk_off(container, NULL, MOVE_APPLY_VANISHED);
01252         }
01253 
01254         hitter = fix_stopped_arrow(hitter);
01255 
01256         if (hitter == NULL)
01257         {
01258             return NULL;
01259         }
01260 
01261         /* Trigger the STOP event */
01262         trigger_event(EVENT_STOP, victim, hitter, NULL, NULL, 0, 0, 0, SCRIPT_FIX_NOTHING);
01263         CLEAR_FLAG(hitter, FLAG_IS_MISSILE);
01264 
01265         /* Else try to put arrow on victim's map square */
01266         if ((victim_x != hitter->x || victim_y != hitter->y))
01267         {
01268             remove_ob(hitter);
01269 
01270             if (check_walk_off(hitter, NULL, MOVE_APPLY_DEFAULT) == CHECK_WALK_OK)
01271             {
01272                 hitter->x = victim_x;
01273                 hitter->y = victim_y;
01274                 insert_ob_in_map(hitter, victim_map, hitter, 0);
01275             }
01276         }
01277         /* Else leave arrow where it is */
01278         else
01279         {
01280             hitter = merge_ob(hitter, NULL);
01281         }
01282 
01283         return NULL;
01284     }
01285 
01286     /* Missile missed victim - reassemble missile */
01287     if (container)
01288     {
01289         /* Technical remove, no walk check */
01290         remove_ob(hitter);
01291         insert_ob_in_ob(hitter, container);
01292     }
01293 
01294     return op;
01295 }
01296 
01302 static void poison_player(object *op, object *hitter, float dam)
01303 {
01304     archetype *at = find_archetype("poisoning");
01305     object *tmp = present_arch_in_ob(at, op);
01306 
01307     /* We only poison players and mobs! */
01308     if (op->type != PLAYER && !QUERY_FLAG(op, FLAG_MONSTER))
01309     {
01310         return;
01311     }
01312 
01313     if (tmp == NULL || hitter->type == POISON)
01314     {
01315         if ((tmp = arch_to_object(at)) == NULL)
01316         {
01317             LOG(llevBug, "Failed to clone arch poisoning.\n");
01318             return;
01319         }
01320         else
01321         {
01322             if (hitter->type == POISON)
01323             {
01324                 dam /= 2.0f;
01325                 tmp->stats.dam = (int) (((dam + rndm(0, dam + 1)) * LEVEL_DAMAGE(hitter->level)) * 0.9f);
01326 
01327                 if (tmp->stats.dam > op->stats.maxhp / 3)
01328                 {
01329                     tmp->stats.dam = op->stats.maxhp / 3;
01330                 }
01331 
01332                 if (tmp->stats.dam < 1)
01333                 {
01334                     tmp->stats.dam = 1;
01335                 }
01336             }
01337             /* Spell or weapon will be handled different! */
01338             else
01339             {
01340                 dam /= 2.0f;
01341                 tmp->stats.dam = (int) ((int) dam + rndm(0, dam + 1));
01342 
01343                 if (tmp->stats.dam > op->stats.maxhp / 3)
01344                 {
01345                     tmp->stats.dam = op->stats.maxhp / 3;
01346                 }
01347 
01348                 if (tmp->stats.dam < 1)
01349                 {
01350                     tmp->stats.dam = 1;
01351                 }
01352             }
01353 
01354             tmp->level = hitter->level;
01355             /* So we get credit for poisoning kills */
01356             copy_owner(tmp, hitter);
01357 
01358             /* Now we adjust numbers of ticks of the DOT force and speed of DOT ticks */
01359             if (hitter->type == POISON)
01360             {
01361                 /* # of ticks */
01362                 tmp->stats.food = hitter->last_heal;
01363                 /* Speed of ticks */
01364                 tmp->speed = tmp->speed_left;
01365             }
01366 
01367             if (op->type == PLAYER)
01368             {
01369                 /* Spells should add here too later */
01370                 if (hitter->type == POISON)
01371                 {
01372                     /* Insert the food force in player too */
01373                     create_food_force(op, hitter, tmp);
01374                     new_draw_info(0, COLOR_WHITE, op, "You suddenly feel very ill.");
01375                 }
01376                 /* We have hit with weapon or something */
01377                 else
01378                 {
01379                     new_draw_info_format(0, COLOR_WHITE, op, "%s has poisoned you!", query_name(hitter, NULL));
01380                     insert_ob_in_ob(tmp, op);
01381                     SET_FLAG(tmp, FLAG_APPLIED);
01382                     fix_player(op);
01383                 }
01384             }
01385             /* It's a monster */
01386             else
01387             {
01388                 /* Monster eats poison */
01389                 if (hitter->type == POISON)
01390                 {
01391                 }
01392                 /* Hit from poison force! */
01393                 else
01394                 {
01395                     insert_ob_in_ob(tmp, op);
01396                     SET_FLAG(tmp, FLAG_APPLIED);
01397                     fix_monster(op);
01398 
01399                     if (hitter->type == PLAYER)
01400                     {
01401                         new_draw_info_format(0, COLOR_WHITE, hitter, "You poisoned %s!", query_name(op, NULL));
01402                     }
01403                     else if (get_owner(hitter) && hitter->owner->type == PLAYER)
01404                     {
01405                         new_draw_info_format(0, COLOR_WHITE, hitter->owner, "%s poisoned %s!", query_name(hitter, NULL), query_name(op, NULL));
01406                     }
01407 
01408                 }
01409             }
01410         }
01411 
01412         tmp->speed_left = 0;
01413     }
01414     else
01415     {
01416         tmp->stats.food++;
01417     }
01418 }
01419 
01423 static void slow_living(object *op)
01424 {
01425     archetype *at = find_archetype("slowness");
01426     object *tmp;
01427 
01428     if (at == NULL)
01429     {
01430         LOG(llevBug, "Can't find slowness archetype.\n");
01431     }
01432 
01433     if ((tmp = present_arch_in_ob(at, op)) == NULL)
01434     {
01435         tmp = arch_to_object(at);
01436         tmp = insert_ob_in_ob(tmp, op);
01437         new_draw_info(0, COLOR_WHITE, op, "The world suddenly moves very fast!");
01438     }
01439     else
01440     {
01441         tmp->stats.food++;
01442     }
01443 
01444     SET_FLAG(tmp, FLAG_APPLIED);
01445     tmp->speed_left = 0;
01446     fix_player(op);
01447 }
01448 
01452 void confuse_living(object *op)
01453 {
01454     object *tmp;
01455     int maxduration;
01456 
01457     tmp = present_in_ob(CONFUSION, op);
01458 
01459     if (!tmp)
01460     {
01461         tmp = get_archetype("confusion");
01462         tmp = insert_ob_in_ob(tmp, op);
01463     }
01464 
01465     /* Duration added per hit and max. duration of confusion both depend
01466      * on the player's resistance */
01467     tmp->stats.food += MAX(1, 5 * (100 - op->protection[ATNR_CONFUSION]) / 100);
01468     maxduration = MAX(2, 30 * (100 - op->protection[ATNR_CONFUSION]) / 100);
01469 
01470     if (tmp->stats.food > maxduration)
01471     {
01472         tmp->stats.food = maxduration;
01473     }
01474 
01475     if (op->type == PLAYER && !QUERY_FLAG(op, FLAG_CONFUSED))
01476     {
01477         new_draw_info(0, COLOR_WHITE, op, "You suddenly feel very confused!");
01478     }
01479 
01480     SET_FLAG(op, FLAG_CONFUSED);
01481 }
01482 
01488 static void blind_living(object *op, object *hitter, int dam)
01489 {
01490     object *tmp, *owner;
01491 
01492     /* Save some work if we know it isn't going to affect the player */
01493     if (op->protection[ATNR_BLIND] == 100)
01494     {
01495         return;
01496     }
01497 
01498     tmp = present_in_ob(BLINDNESS, op);
01499 
01500     if (!tmp)
01501     {
01502         tmp = get_archetype("blindness");
01503         SET_FLAG(tmp, FLAG_BLIND);
01504         SET_FLAG(tmp, FLAG_APPLIED);
01505         /* Use floats so we don't lose too much precision due to rounding errors.
01506          * speed is a float anyways. */
01507         tmp->speed = tmp->speed * ((float) 100.0 - (float) op->protection[ATNR_BLIND]) / (float) 100;
01508 
01509         tmp = insert_ob_in_ob(tmp, op);
01510         /* Mostly to display any messages */
01511         change_abil(op, tmp);
01512         /* This takes care of some other stuff */
01513         fix_player(op);
01514 
01515         if (hitter->owner)
01516         {
01517             owner = get_owner(hitter);
01518         }
01519         else
01520         {
01521             owner = hitter;
01522         }
01523 
01524         new_draw_info_format(0, COLOR_WHITE, owner, "Your attack blinds %s!", query_name(op, NULL));
01525     }
01526 
01527     tmp->stats.food += dam;
01528 
01529     if (tmp->stats.food > 10)
01530     {
01531         tmp->stats.food = 10;
01532     }
01533 }
01534 
01539 void paralyze_living(object *op, int dam)
01540 {
01541     float effect, max;
01542 
01543     /* Do this as a float - otherwise, rounding might very well reduce this to 0 */
01544     effect = (float) dam * (float) 3.0 * ((float) 100.0 - (float) op->protection[ATNR_PARALYZE]) / (float) 100;
01545 
01546     if (effect == 0)
01547     {
01548         return;
01549     }
01550 
01551     /* We mark this object as paralyzed */
01552     SET_FLAG(op, FLAG_PARALYZED);
01553 
01554     op->speed_left -= FABS(op->speed) * effect;
01555 
01556     /* Max number of ticks to be affected for. */
01557     max = ((float) 100 - (float) op->protection[ATNR_PARALYZE]) / (float) 2;
01558 
01559     if (op->speed_left < -(FABS(op->speed) * max))
01560     {
01561         op->speed_left = (float) -(FABS(op->speed) * max);
01562     }
01563 }
01564 
01570 static void thrown_item_effect(object *hitter, object *victim)
01571 {
01572     if (!IS_LIVE(hitter))
01573     {
01574         /* May not need a switch for just 2 types, but this makes it
01575          * easier for expansion. */
01576         switch (hitter->type)
01577         {
01578             case POTION:
01579                 if (hitter->stats.sp != SP_NO_SPELL && spells[hitter->stats.sp].flags&SPELL_DESC_DIRECTION)
01580                 {
01581                     /* apply potion ALWAYS fire on the spot the applier stands - good for healing - bad for firestorm */
01582                     cast_spell(hitter, hitter, hitter->direction, hitter->stats.sp, 1, CAST_POTION, NULL);
01583                 }
01584 
01585                 decrease_ob(hitter);
01586                 break;
01587 
01588             /* Poison drinks */
01589             case POISON:
01590                 if (IS_LIVE(victim) && !QUERY_FLAG(victim, FLAG_UNDEAD))
01591                 {
01592                     apply_poison(victim, hitter);
01593                 }
01594 
01595                 break;
01596         }
01597     }
01598 }
01599 
01605 static int adj_attackroll(object *hitter, object *target)
01606 {
01607     object *attacker = hitter;
01608     int adjust = 0;
01609 
01610     /* Safety */
01611     if (!target || !hitter || !hitter->map || !target->map || !on_same_map(hitter, target))
01612     {
01613         LOG(llevBug, "adj_attackroll(): hitter and target not on same map\n");
01614         return 0;
01615     }
01616 
01617     /* Aimed missiles use the owning object's sight */
01618     if (is_aimed_missile(hitter))
01619     {
01620         if ((attacker = get_owner(hitter)) == NULL)
01621         {
01622             attacker = hitter;
01623         }
01624     }
01625     else if (!IS_LIVE(hitter))
01626     {
01627         return 0;
01628     }
01629 
01630     /* Invisible means, we can't see it - same for blind */
01631     if (IS_INVISIBLE(target, attacker) || QUERY_FLAG(attacker, FLAG_BLIND))
01632     {
01633         adjust -= 12;
01634     }
01635 
01636     if (QUERY_FLAG(attacker, FLAG_SCARED))
01637     {
01638         adjust -= 3;
01639     }
01640 
01641     if (QUERY_FLAG(target, FLAG_UNAGGRESSIVE))
01642     {
01643         adjust += 1;
01644     }
01645 
01646     if (QUERY_FLAG(target, FLAG_SCARED))
01647     {
01648         adjust += 1;
01649     }
01650 
01651     if (QUERY_FLAG(attacker, FLAG_CONFUSED))
01652     {
01653         adjust -= 3;
01654     }
01655 
01656     /* If we attack at a different 'altitude' it's harder */
01657     if (QUERY_FLAG(attacker, FLAG_FLYING) != QUERY_FLAG(target, FLAG_FLYING))
01658     {
01659         adjust -= 2;
01660     }
01661 
01662     return adjust;
01663 }
01664 
01669 static int is_aimed_missile(object *op)
01670 {
01671     if (op && QUERY_FLAG(op, FLAG_FLYING) && (op->type == ARROW || op->type == THROWN_OBJ))
01672     {
01673         return 1;
01674     }
01675 
01676     return 0;
01677 }
01678 
01685 int is_melee_range(object *hitter, object *enemy)
01686 {
01687     int xt, yt, s;
01688     object *tmp;
01689     mapstruct *mt;
01690 
01691     /* Check squares around */
01692     for (s = 0; s < 9; s++)
01693     {
01694         xt = hitter->x + freearr_x[s];
01695         yt = hitter->y + freearr_y[s];
01696 
01697         if (!(mt = get_map_from_coord(hitter->map, &xt, &yt)))
01698         {
01699             continue;
01700         }
01701 
01702         for (tmp = enemy; tmp != NULL; tmp = tmp->more)
01703         {
01704             /* Strike! */
01705             if (tmp->map == mt && tmp->x == xt && tmp->y == yt)
01706             {
01707                 return 1;
01708             }
01709         }
01710     }
01711 
01712     return 0;
01713 }
01714 
01720 int spell_attack_missed(object *hitter, object *enemy)
01721 {
01722     int roll = rndm(SPELL_MISS_ROLL_MIN, SPELL_MISS_ROLL_MAX);
01723 
01724     if (hitter->map)
01725     {
01726         /* Adjust roll for various situations. */
01727         roll += adj_attackroll(hitter, enemy);
01728     }
01729 
01730     if (roll >= SPELL_MISS_ROLL_MAX || enemy->level <= hitter->level + roll)
01731     {
01732         return 0;
01733     }
01734 
01735     return 1;
01736 }