|
Atrinik Server 2.5
|
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 }
1.7.4