Crossfire Server, Branch 1.12
R12190
|
00001 /* 00002 * static char *rcsid_pets_c = 00003 * "$Id: pets.c 11578 2009-02-23 22:02:27Z lalo $"; 00004 */ 00005 00006 /* 00007 CrossFire, A Multiplayer game for X-windows 00008 00009 Copyright (C) 2006 Mark Wedel & Crossfire Development Team 00010 Copyright (C) 1992 Frank Tore Johansen 00011 00012 This program is free software; you can redistribute it and/or modify 00013 it under the terms of the GNU General Public License as published by 00014 the Free Software Foundation; either version 2 of the License, or 00015 (at your option) any later version. 00016 00017 This program is distributed in the hope that it will be useful, 00018 but WITHOUT ANY WARRANTY; without even the implied warranty of 00019 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 00020 GNU General Public License for more details. 00021 00022 You should have received a copy of the GNU General Public License 00023 along with this program; if not, write to the Free Software 00024 Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. 00025 00026 The authors can be reached via e-mail at crossfire-devel@real-time.com 00027 */ 00028 00034 #include <global.h> 00035 #ifndef __CEXTRACT__ 00036 #include <sproto.h> 00037 #endif 00038 00045 static void mark_inventory_as_no_drop(object *ob) { 00046 object *tmp; 00047 00048 for (tmp = ob->inv; tmp != NULL; tmp = tmp->below) { 00049 SET_FLAG(tmp, FLAG_NO_DROP); 00050 } 00051 } 00052 00067 object *get_pet_enemy(object *pet, rv_vector *rv) { 00068 object *owner, *tmp, *attacker, *tmp3; 00069 int i; 00070 sint16 x, y; 00071 mapstruct *nm; 00072 int search_arr[SIZEOFFREE]; 00073 int mflags; 00074 00075 attacker = pet->attacked_by; /*pointer to attacking enemy*/ 00076 pet->attacked_by = NULL; /*clear this, since we are dealing with it*/ 00077 00078 if ((owner = get_owner(pet)) != NULL) { 00079 /* If the owner has turned on the pet, make the pet 00080 * unfriendly. 00081 */ 00082 if ((check_enemy(owner, rv)) == pet) { 00083 CLEAR_FLAG(pet, FLAG_FRIENDLY); 00084 remove_friendly_object(pet); 00085 pet->attack_movement &= ~PETMOVE; 00086 return owner; 00087 } 00088 } else { 00089 /* else the owner is no longer around, so the 00090 * pet no longer needs to be friendly. 00091 */ 00092 CLEAR_FLAG(pet, FLAG_FRIENDLY); 00093 remove_friendly_object(pet); 00094 pet->attack_movement &= ~PETMOVE; 00095 return NULL; 00096 } 00097 /* If they are not on the same map, the pet won't be agressive */ 00098 if (!on_same_map(pet, owner)) 00099 return NULL; 00100 00101 /* See if the pet has an existing enemy. If so, don't start a new one*/ 00102 if ((tmp = check_enemy(pet, rv)) != NULL) { 00103 if (tmp == owner && !QUERY_FLAG(pet, FLAG_CONFUSED) 00104 && QUERY_FLAG(pet, FLAG_FRIENDLY)) 00105 /* without this check, you can actually get pets with 00106 * enemy set to owner! 00107 */ 00108 pet->enemy = NULL; 00109 else 00110 return tmp; 00111 } 00112 get_search_arr(search_arr); 00113 00114 if (owner->type == PLAYER && owner->contr->petmode > pet_normal) { 00115 if (owner->contr->petmode == pet_sad) { 00116 tmp = find_nearest_living_creature(pet); 00117 if (tmp != NULL) { 00118 get_rangevector(pet, tmp, rv, 0); 00119 if (check_enemy(pet, rv) != NULL) 00120 return tmp; 00121 else 00122 pet->enemy = NULL; 00123 } 00124 /* if we got here we have no enemy */ 00125 /* we return NULL to avoid heading back to the owner */ 00126 pet->enemy = NULL; 00127 return NULL; 00128 } 00129 } 00130 00131 /* Since the pet has no existing enemy, look for anything nasty 00132 * around the owner that it should go and attack. 00133 */ 00134 tmp3 = NULL; 00135 for (i = 0; i < SIZEOFFREE; i++) { 00136 x = owner->x+freearr_x[search_arr[i]]; 00137 y = owner->y+freearr_y[search_arr[i]]; 00138 nm = owner->map; 00139 /* Only look on the space if there is something alive there. */ 00140 mflags = get_map_flags(nm, &nm, x, y, &x, &y); 00141 if (!(mflags&P_OUT_OF_MAP) && mflags&P_IS_ALIVE) { 00142 for (tmp = GET_MAP_OB(nm, x, y); tmp != NULL; tmp = tmp->above) { 00143 object *tmp2 = tmp->head == NULL ? tmp : tmp->head; 00144 00145 if (QUERY_FLAG(tmp2, FLAG_ALIVE) 00146 && ((!QUERY_FLAG(tmp2, FLAG_FRIENDLY) && tmp2->type != PLAYER) || should_arena_attack(pet, owner, tmp2)) 00147 && !QUERY_FLAG(tmp2, FLAG_UNAGGRESSIVE) 00148 && tmp2 != pet 00149 && tmp2 != owner 00150 && can_detect_enemy(pet, tmp2, rv)) { 00151 if (!can_see_enemy(pet, tmp2)) { 00152 if (tmp3 != NULL) 00153 tmp3 = tmp2; 00154 } else { 00155 pet->enemy = tmp2; 00156 if (check_enemy(pet, rv) != NULL) 00157 return tmp2; 00158 else 00159 pet->enemy = NULL; 00160 } 00161 }/* if this is a valid enemy */ 00162 }/* for objects on this space */ 00163 }/* if there is something living on this space */ 00164 } /* for loop of spaces around the owner */ 00165 00166 /* fine, we went through the whole loop and didn't find one we could 00167 see, take what we have */ 00168 if (tmp3 != NULL) { 00169 pet->enemy = tmp3; 00170 if (check_enemy(pet, rv) != NULL) 00171 return tmp3; 00172 else 00173 pet->enemy = NULL; 00174 } 00175 00176 /* No threat to owner, check to see if the pet has an attacker*/ 00177 if (attacker) { 00178 /* need to be sure this is the right one! */ 00179 if (attacker->count == pet->attacked_by_count) { 00180 /* also need to check to make sure it is not freindly */ 00181 /* or otherwise non-hostile, and is an appropriate target */ 00182 if (!QUERY_FLAG(attacker, FLAG_FRIENDLY) && on_same_map(pet, attacker)) { 00183 pet->enemy = attacker; 00184 if (check_enemy(pet, rv) != NULL) 00185 return attacker; 00186 else 00187 pet->enemy = NULL; 00188 } 00189 } 00190 } 00191 00192 /* Don't have an attacker or legal enemy, so look for a new one!. 00193 * This looks for one around where the pet is. Thus, you could lead 00194 * a pet to danger, then take a few steps back. This code is basically 00195 * the same as the code that looks around the owner. 00196 */ 00197 if (owner->type == PLAYER && owner->contr->petmode != pet_defend) { 00198 tmp3 = NULL; 00199 for (i = 0; i < SIZEOFFREE; i++) { 00200 x = pet->x+freearr_x[search_arr[i]]; 00201 y = pet->y+freearr_y[search_arr[i]]; 00202 nm = pet->map; 00203 /* Only look on the space if there is something alive there. */ 00204 mflags = get_map_flags(nm, &nm, x, y, &x, &y); 00205 if (!(mflags&P_OUT_OF_MAP) && mflags&P_IS_ALIVE) { 00206 for (tmp = GET_MAP_OB(nm, x, y); tmp != NULL; tmp = tmp->above) { 00207 object *tmp2 = tmp->head == NULL ? tmp : tmp->head; 00208 if (QUERY_FLAG(tmp2, FLAG_ALIVE) 00209 && ((!QUERY_FLAG(tmp2, FLAG_FRIENDLY) && tmp2->type != PLAYER) || should_arena_attack(pet, owner, tmp2)) 00210 && !QUERY_FLAG(tmp2, FLAG_UNAGGRESSIVE) 00211 && tmp2 != pet 00212 && tmp2 != owner 00213 && can_detect_enemy(pet, tmp2, rv)) { 00214 if (!can_see_enemy(pet, tmp2)) { 00215 if (tmp3 != NULL) 00216 tmp3 = tmp2; 00217 } else { 00218 pet->enemy = tmp2; 00219 if (check_enemy(pet, rv) != NULL) 00220 return tmp2; 00221 else 00222 pet->enemy = NULL; 00223 } 00224 } /* make sure we can get to the bugger */ 00225 }/* for objects on this space */ 00226 } /* if there is something living on this space */ 00227 } /* for loop of spaces around the pet */ 00228 } /* pet in defence mode */ 00229 00230 /* fine, we went through the whole loop and didn't find one we could 00231 see, take what we have */ 00232 if (tmp3 != NULL) { 00233 pet->enemy = tmp3; 00234 if (check_enemy(pet, rv) != NULL) 00235 return tmp3; 00236 else 00237 pet->enemy = NULL; 00238 } 00239 00240 /* Didn't find anything - return the owner's enemy or NULL */ 00241 return check_enemy(pet, rv); 00242 } 00243 00250 void terminate_all_pets(object *owner) { 00251 objectlink *obl, *next; 00252 00253 for (obl = first_friendly_object; obl != NULL; obl = next) { 00254 object *ob = obl->ob; 00255 next = obl->next; 00256 if (get_owner(ob) == owner) { 00257 if (!QUERY_FLAG(ob, FLAG_REMOVED)) 00258 remove_ob(ob); 00259 remove_friendly_object(ob); 00260 free_object(ob); 00261 } 00262 } 00263 } 00264 00273 void remove_all_pets(void) { 00274 objectlink *obl, *next; 00275 object *owner; 00276 00277 for (obl = first_friendly_object; obl != NULL; obl = next) { 00278 next = obl->next; 00279 if (obl->ob->type != PLAYER 00280 && QUERY_FLAG(obl->ob, FLAG_FRIENDLY) 00281 && (owner = get_owner(obl->ob)) != NULL 00282 && !on_same_map(owner, obl->ob)) { 00283 /* follow owner checks map status for us. Note that pet can 00284 * die in follow_owner, so check for obl->ob existence 00285 */ 00286 follow_owner(obl->ob, owner); 00287 if (obl->ob && QUERY_FLAG(obl->ob, FLAG_REMOVED) && FABS(obl->ob->speed) > MIN_ACTIVE_SPEED) { 00288 object *ob = obl->ob; 00289 00290 LOG(llevMonster, "(pet failed to follow)\n"); 00291 remove_friendly_object(ob); 00292 free_object(ob); 00293 } 00294 } 00295 } 00296 } 00297 00306 void follow_owner(object *ob, object *owner) { 00307 object *tmp; 00308 int dir; 00309 00310 if (!QUERY_FLAG(ob, FLAG_REMOVED)) 00311 remove_ob(ob); 00312 00313 if (owner->map == NULL) { 00314 LOG(llevError, "Can't follow owner: no map.\n"); 00315 return; 00316 } 00317 if (owner->map->in_memory != MAP_IN_MEMORY) { 00318 LOG(llevError, "Owner of the pet not on a map in memory!?\n"); 00319 return; 00320 } 00321 dir = find_free_spot(ob, owner->map, owner->x, owner->y, 1, SIZEOFFREE); 00322 00323 if (dir == -1) { 00324 LOG(llevMonster, "No space for pet to follow, freeing %s.\n", ob->name); 00325 return; /* Will be freed since it's removed */ 00326 } 00327 for (tmp = ob; tmp != NULL; tmp = tmp->more) { 00328 tmp->x = owner->x+freearr_x[dir]+(tmp->arch == NULL ? 0 : tmp->arch->clone.x); 00329 tmp->y = owner->y+freearr_y[dir]+(tmp->arch == NULL ? 0 : tmp->arch->clone.y); 00330 tmp->map = owner->map; 00331 if (OUT_OF_REAL_MAP(tmp->map, tmp->x, tmp->y)) { 00332 tmp->map = get_map_from_coord(tmp->map, &tmp->x, &tmp->y); 00333 } 00334 } 00335 insert_ob_in_map(ob, ob->map, NULL, 0); 00336 if (owner->type == PLAYER) /* Uh, I hope this is always true... */ 00337 draw_ext_info(NDI_UNIQUE, 0, owner, MSG_TYPE_SPELL, MSG_TYPE_SPELL_PET, 00338 "Your pet magically appears next to you", NULL); 00339 return; 00340 } 00341 00348 void pet_move(object *ob) { 00349 int dir, i; 00350 tag_t tag; 00351 sint16 dx, dy; 00352 object *ob2, *owner; 00353 mapstruct *m; 00354 00355 /* Check to see if player pulled out */ 00356 if ((owner = get_owner(ob)) == NULL) { 00357 remove_ob(ob); /* Will be freed when returning */ 00358 remove_friendly_object(ob); 00359 free_object(ob); 00360 LOG(llevMonster, "Pet: no owner, leaving.\n"); 00361 return; 00362 } 00363 00364 /* move monster into the owners map if not in the same map */ 00365 if (!on_same_map(ob, owner)) { 00366 follow_owner(ob, owner); 00367 return; 00368 } 00369 /* Calculate Direction */ 00370 if (owner->type == PLAYER && owner->contr->petmode == pet_sad) { 00371 /* in S&D mode, if we have no enemy, run randomly about. */ 00372 for (i = 0; i < 15; i++) { 00373 dir = rndm(1, 8); 00374 dx = ob->x+freearr_x[dir]; 00375 dy = ob->y+freearr_y[dir]; 00376 m = ob->map; 00377 if (get_map_flags(ob->map, &m, dx, dy, &dx, &dy)&P_OUT_OF_MAP) 00378 continue; 00379 else if (OB_TYPE_MOVE_BLOCK(ob, GET_MAP_MOVE_BLOCK(m, dx, dy))) 00380 continue; 00381 else 00382 break; 00383 } 00384 } else { 00385 dir = find_dir_2(ob->x-ob->owner->x, ob->y-ob->owner->y); 00386 } 00387 ob->direction = dir; 00388 00389 tag = ob->count; 00390 /* move_ob returns 0 if the object couldn't move. If that is the 00391 * case, lets do some other work. 00392 */ 00393 if (!(move_ob(ob, dir, ob))) { 00394 object *part; 00395 00396 /* the failed move_ob above may destroy the pet, so check here */ 00397 if (was_destroyed(ob, tag)) 00398 return; 00399 00400 for (part = ob; part != NULL; part = part->more) { 00401 dx = part->x+freearr_x[dir]; 00402 dy = part->y+freearr_y[dir]; 00403 m = get_map_from_coord(part->map, &dx, &dy); 00404 if (!m) 00405 continue; 00406 00407 for (ob2 = GET_MAP_OB(m, dx, dy); ob2 != NULL; ob2 = ob2->above) { 00408 object *new_ob; 00409 00410 new_ob = ob2->head ? ob2->head : ob2; 00411 if (new_ob == ob) 00412 break; 00413 if (new_ob == ob->owner) 00414 return; 00415 if (get_owner(new_ob) == ob->owner) 00416 break; 00417 00418 /* Hmm. Did we try to move into an enemy monster? If so, 00419 * make it our enemy. 00420 */ 00421 if (QUERY_FLAG(new_ob, FLAG_ALIVE) 00422 && !QUERY_FLAG(ob, FLAG_UNAGGRESSIVE) 00423 && !QUERY_FLAG(new_ob, FLAG_UNAGGRESSIVE) 00424 && !QUERY_FLAG(new_ob, FLAG_FRIENDLY)) { 00425 ob->enemy = new_ob; 00426 if (new_ob->enemy == NULL) 00427 new_ob->enemy = ob; 00428 return; 00429 } else if (new_ob->type == PLAYER) { 00430 draw_ext_info(NDI_UNIQUE, 0, new_ob, 00431 MSG_TYPE_MISC, MSG_SUBTYPE_NONE, 00432 "You stand in the way of someones pet.", NULL); 00433 return; 00434 } 00435 } 00436 } 00437 /* Try a different course */ 00438 dir = absdir(dir+4-(RANDOM()%5)-(RANDOM()%5)); 00439 (void)move_ob(ob, dir, ob); 00440 } 00441 return; 00442 } 00443 00444 /**************************************************************************** 00445 * 00446 * GOLEM SPELL CODE 00447 * 00448 ****************************************************************************/ 00449 00464 static object *fix_summon_pet(archetype *at, object *op, int dir, int is_golem) { 00465 archetype *atmp; 00466 object *tmp = NULL, *prev = NULL, *head = NULL; 00467 00468 for (atmp = at; atmp != NULL; atmp = atmp->more) { 00469 tmp = arch_to_object(atmp); 00470 if (atmp == at) { 00471 if (!is_golem) 00472 SET_FLAG(tmp, FLAG_MONSTER); 00473 set_owner(tmp, op); 00474 if (op->type == PLAYER) { 00475 tmp->stats.exp = 0; 00476 add_friendly_object(tmp); 00477 SET_FLAG(tmp, FLAG_FRIENDLY); 00478 if (is_golem) 00479 CLEAR_FLAG(tmp, FLAG_MONSTER); 00480 } else if (QUERY_FLAG(op, FLAG_FRIENDLY)) { 00481 object *owner = get_owner(op); 00482 00483 if (owner != NULL) {/* For now, we transfer ownership */ 00484 set_owner(tmp, owner); 00485 tmp->attack_movement = PETMOVE; 00486 add_friendly_object(tmp); 00487 SET_FLAG(tmp, FLAG_FRIENDLY); 00488 } 00489 } 00490 if (op->type != PLAYER || !is_golem) { 00491 tmp->attack_movement = PETMOVE; 00492 tmp->speed_left = -1; 00493 tmp->type = 0; 00494 tmp->enemy = op->enemy; 00495 } else 00496 tmp->type = GOLEM; 00497 } 00498 if (head == NULL) 00499 head = tmp; 00500 tmp->x = op->x+freearr_x[dir]+tmp->arch->clone.x; 00501 tmp->y = op->y+freearr_y[dir]+tmp->arch->clone.y; 00502 tmp->map = op->map; 00503 if (tmp->invisible) 00504 tmp->invisible = 0; 00505 if (head != tmp) 00506 tmp->head = head, 00507 prev->more = tmp; 00508 prev = tmp; 00509 } 00510 head->direction = dir; 00511 00512 if (head->randomitems) { 00513 create_treasure(head->randomitems, head, GT_APPLY|GT_STARTEQUIP, 6, 0); 00514 } 00515 mark_inventory_as_no_drop(head); 00516 00517 /* need to change some monster attr to prevent problems/crashing */ 00518 head->last_heal = 0; 00519 head->last_eat = 0; 00520 head->last_grace = 0; 00521 head->last_sp = 0; 00522 head->other_arch = NULL; 00523 head->stats.exp = 0; 00524 CLEAR_FLAG(head, FLAG_CHANGING); 00525 CLEAR_FLAG(head, FLAG_STAND_STILL); 00526 CLEAR_FLAG(head, FLAG_GENERATOR); 00527 CLEAR_FLAG(head, FLAG_SPLITTING); 00528 if (head->attacktype&AT_GHOSTHIT) 00529 head->attacktype = (AT_PHYSICAL|AT_DRAIN); 00530 00531 return head; 00532 } 00533 00541 void move_golem(object *op) { 00542 int made_attack = 0; 00543 object *tmp; 00544 tag_t tag; 00545 00546 if (QUERY_FLAG(op, FLAG_MONSTER)) 00547 return; /* Has already been moved */ 00548 00549 if (get_owner(op) == NULL) { 00550 LOG(llevDebug, "Golem without owner destructed.\n"); 00551 remove_ob(op); 00552 free_object(op); 00553 return; 00554 } 00555 /* It would be nice to have a cleaner way of what message to print 00556 * when the golem expires than these hard coded entries. 00557 * Note it is intentional that a golems duration is based on its 00558 * hp, and not duration 00559 */ 00560 if (--op->stats.hp < 0) { 00561 if (op->msg) 00562 draw_ext_info(NDI_UNIQUE, 0, op->owner, MSG_TYPE_SPELL, MSG_TYPE_SPELL_PET, 00563 op->msg, op->msg); 00564 op->owner->contr->ranges[range_golem] = NULL; 00565 op->owner->contr->golem_count = 0; 00566 remove_friendly_object(op); 00567 remove_ob(op); 00568 free_object(op); 00569 return; 00570 } 00571 00572 /* Do golem attacks/movement for single & multisq golems. 00573 * Assuming here that op is the 'head' object. Pass only op to 00574 * move_ob (makes recursive calls to other parts) 00575 * move_ob returns 0 if the creature was not able to move. 00576 */ 00577 tag = op->count; 00578 if (move_ob(op, op->direction, op)) 00579 return; 00580 if (was_destroyed(op, tag)) 00581 return; 00582 00583 for (tmp = op; tmp; tmp = tmp->more) { 00584 sint16 x = tmp->x+freearr_x[op->direction], y = tmp->y+freearr_y[op->direction]; 00585 object *victim; 00586 mapstruct *m; 00587 int mflags; 00588 00589 m = op->map; 00590 mflags = get_map_flags(m, &m, x, y, &x, &y); 00591 00592 if (mflags&P_OUT_OF_MAP) 00593 continue; 00594 00595 for (victim = GET_MAP_OB(op->map, x, y); victim; victim = victim->above) 00596 if (QUERY_FLAG(victim, FLAG_ALIVE)) 00597 break; 00598 00599 /* We used to call will_hit_self to make sure we don't 00600 * hit ourselves, but that didn't work, and I don't really 00601 * know if that was more efficient anyways than this. 00602 * This at least works. Note that victim->head can be NULL, 00603 * but since we are not trying to dereferance that pointer, 00604 * that isn't a problem. 00605 */ 00606 if (victim && victim != op && victim->head != op) { 00607 /* for golems with race fields, we don't attack 00608 * aligned races 00609 */ 00610 00611 if (victim->race && op->race && strstr(op->race, victim->race)) { 00612 if (op->owner) 00613 draw_ext_info_format(NDI_UNIQUE, 0, op->owner, 00614 MSG_TYPE_SPELL, MSG_TYPE_SPELL_PET, 00615 "%s avoids damaging %s.", 00616 "%s avoids damaging %s.", 00617 op->name, victim->name); 00618 } else if (victim == op->owner) { 00619 if (op->owner) 00620 draw_ext_info_format(NDI_UNIQUE, 0, op->owner, 00621 MSG_TYPE_SPELL, MSG_TYPE_SPELL_PET, 00622 "%s avoids damaging you.", 00623 "%s avoids damaging you.", 00624 op->name); 00625 } else { 00626 attack_ob(victim, op); 00627 made_attack = 1; 00628 } 00629 } /* If victim */ 00630 } 00631 if (made_attack) 00632 update_object(op, UP_OBJ_FACE); 00633 } 00634 00648 void control_golem(object *op, int dir) { 00649 op->direction = dir; 00650 } 00651 00669 int summon_golem(object *op, object *caster, int dir, object *spob) { 00670 object *tmp; 00671 const object *god = NULL; 00672 archetype *at; 00673 char buf[MAX_BUF]; 00674 00675 /* Because there can be different golem spells, player may want to 00676 * 'lose' their old golem. 00677 */ 00678 if (op->type == PLAYER 00679 && op->contr->ranges[range_golem] != NULL 00680 && op->contr->golem_count == op->contr->ranges[range_golem]->count) { 00681 draw_ext_info(NDI_UNIQUE, 0, op, 00682 MSG_TYPE_SPELL, MSG_TYPE_SPELL_PET, 00683 "You dismiss your existing golem.", NULL); 00684 remove_ob(op->contr->ranges[range_golem]); 00685 free_object(op->contr->ranges[range_golem]); 00686 op->contr->ranges[range_golem] = NULL; 00687 op->contr->golem_count = -1; 00688 } 00689 00690 if (spob->other_arch) 00691 at = spob->other_arch; 00692 else if (spob->race) { 00693 god = find_god(determine_god(caster)); 00694 if (!god) { 00695 draw_ext_info_format(NDI_UNIQUE, 0, op, 00696 MSG_TYPE_SPELL, MSG_TYPE_SPELL_FAILURE, 00697 "You must worship a god to cast %s.", 00698 "You must worship a god to cast %s.", 00699 spob->name); 00700 return 0; 00701 } 00702 00703 at = determine_holy_arch(god, spob->race); 00704 if (!at) { 00705 draw_ext_info_format(NDI_UNIQUE, 0, op, 00706 MSG_TYPE_SPELL, MSG_TYPE_SPELL_FAILURE, 00707 "%s has no %s for you to call.", 00708 "%s has no %s for you to call.", 00709 god->name, spob->race); 00710 return 0; 00711 } 00712 } else { 00713 LOG(llevError, "Spell %s lacks other_arch\n", spob->name); 00714 return 0; 00715 } 00716 00717 if (!dir) 00718 dir = find_free_spot(NULL, op->map, op->x, op->y, 1, SIZEOFFREE1+1); 00719 00720 if ((dir == -1) 00721 || ob_blocked(&at->clone, op->map, op->x+freearr_x[dir], op->y+freearr_y[dir])) { 00722 draw_ext_info(NDI_UNIQUE, 0, op, 00723 MSG_TYPE_SPELL, MSG_TYPE_SPELL_FAILURE, 00724 "There is something in the way.", NULL); 00725 return 0; 00726 } 00727 /* basically want to get proper map/coordinates for this object */ 00728 00729 if (!(tmp = fix_summon_pet(at, op, dir, GOLEM))) { 00730 draw_ext_info(NDI_UNIQUE, 0, op, 00731 MSG_TYPE_SPELL, MSG_TYPE_SPELL_FAILURE, 00732 "Your spell fails.", NULL); 00733 return 0; 00734 } 00735 00736 if (op->type == PLAYER) { 00737 tmp->type = GOLEM; 00738 set_owner(tmp, op); 00739 set_spell_skill(op, caster, spob, tmp); 00740 op->contr->ranges[range_golem] = tmp; 00741 op->contr->golem_count = tmp->count; 00742 /* give the player control of the golem */ 00743 op->contr->shoottype = range_golem; 00744 } else { 00745 if (QUERY_FLAG(op, FLAG_FRIENDLY)) { 00746 object *owner = get_owner(op); 00747 00748 if (owner != NULL) { /* For now, we transfer ownership */ 00749 set_owner(tmp, owner); 00750 tmp->attack_movement = PETMOVE; 00751 add_friendly_object(tmp); 00752 SET_FLAG(tmp, FLAG_FRIENDLY); 00753 } 00754 } 00755 SET_FLAG(tmp, FLAG_MONSTER); 00756 } 00757 00758 /* make the speed positive.*/ 00759 tmp->speed = FABS(tmp->speed); 00760 00761 /* This sets the level dependencies on dam and hp for monsters */ 00762 /* players can't cope with too strong summonings. */ 00763 /* but monsters can. reserve these for players. */ 00764 if (op->type == PLAYER) { 00765 tmp->stats.hp += spob->duration+SP_level_duration_adjust(caster, spob); 00766 if (!spob->stats.dam) 00767 tmp->stats.dam += SP_level_dam_adjust(caster, spob); 00768 else 00769 tmp->stats.dam = spob->stats.dam+SP_level_dam_adjust(caster, spob); 00770 tmp->speed += .02*SP_level_range_adjust(caster, spob); 00771 tmp->speed = MIN(tmp->speed, 1.0); 00772 if (spob->attacktype) 00773 tmp->attacktype = spob->attacktype; 00774 } 00775 tmp->stats.wc -= SP_level_range_adjust(caster, spob); 00776 00777 /* limit the speed to 0.3 for non-players, 1 for players. */ 00778 00779 /* make experience increase in proportion to the strength. 00780 * this is a bit simplistic - we are basically just looking at how 00781 * often the sp doubles and use that as the ratio. 00782 */ 00783 tmp->stats.exp *= 1+(MAX(spob->stats.maxgrace, spob->stats.sp)/caster_level(caster, spob)); 00784 tmp->speed_left = 0; 00785 tmp->direction = dir; 00786 00787 /* Holy spell - some additional tailoring */ 00788 if (god) { 00789 object *tmp2; 00790 00791 snprintf(buf, sizeof(buf), "%s of %s", spob->name, god->name); 00792 buf[0] = toupper(buf[0]); 00793 for (tmp2 = tmp; tmp2; tmp2 = tmp2->more) { 00794 if (tmp2->name) 00795 free_string(tmp2->name); 00796 tmp2->name = add_string(buf); 00797 } 00798 tmp->attacktype |= god->attacktype; 00799 memcpy(tmp->resist, god->resist, sizeof(tmp->resist)); 00800 if (tmp->race) 00801 FREE_AND_CLEAR_STR(tmp->race); 00802 if (god->race) 00803 tmp->race = add_string(god->race); 00804 if (tmp->slaying) 00805 FREE_AND_CLEAR_STR(tmp->slaying); 00806 if (god->slaying) 00807 tmp->slaying = add_string(god->slaying); 00808 /* safety, we must allow a god's servants some reasonable attack */ 00809 if (!(tmp->attacktype&AT_PHYSICAL)) 00810 tmp->attacktype |= AT_PHYSICAL; 00811 } 00812 00813 insert_ob_in_map(tmp, tmp->map, op, 0); 00814 return 1; 00815 } 00816 00817 /*************************************************************************** 00818 * 00819 * Summon monster/pet/other object code 00820 * 00821 ***************************************************************************/ 00822 00837 static object *choose_cult_monster(object *pl, const object *god, int summon_level) { 00838 char buf[MAX_BUF]; 00839 const char *race; 00840 int racenr, mon_nr, i; 00841 racelink *list; 00842 objectlink *tobl; 00843 object *otmp; 00844 00845 /* Determine the number of races available */ 00846 racenr = 0; 00847 strcpy(buf, god->race); 00848 race = strtok(buf, ","); 00849 while (race) { 00850 racenr++; 00851 race = strtok(NULL, ","); 00852 } 00853 00854 /* next, randomly select a race from the aligned_races string */ 00855 if (racenr > 1) { 00856 racenr = rndm(0, racenr-1); 00857 strcpy(buf, god->race); 00858 race = strtok(buf, ","); 00859 for (i = 0; i < racenr; i++) 00860 race = strtok(NULL, ","); 00861 } else 00862 race = god->race; 00863 00864 00865 /* see if a we can match a race list of monsters. This should not 00866 * happen, so graceful recovery isn't really needed, but this sanity 00867 * checking is good for cases where the god archetypes mismatch the 00868 * race file 00869 */ 00870 if ((list = find_racelink(race)) == NULL) { 00871 draw_ext_info_format(NDI_UNIQUE, 0, pl, 00872 MSG_TYPE_SPELL, MSG_TYPE_SPELL_FAILURE, 00873 "The spell fails! %s's creatures are beyond the range of your summons", 00874 "The spell fails! %s's creatures are beyond the range of your summons", 00875 god->name); 00876 LOG(llevDebug, "choose_cult_monster() requested non-existent aligned race!\n"); 00877 return NULL; 00878 } 00879 00880 /* search for an apprplritate monster on this race list */ 00881 mon_nr = 0; 00882 for (tobl = list->member; tobl; tobl = tobl->next) { 00883 otmp = tobl->ob; 00884 if (!otmp || !QUERY_FLAG(otmp, FLAG_MONSTER)) 00885 continue; 00886 if (otmp->level <= summon_level) 00887 mon_nr++; 00888 } 00889 00890 /* If this god has multiple race entries, we should really choose another. 00891 * But then we either need to track which ones we have tried, or just 00892 * make so many calls to this function, and if we get so many without 00893 * a valid entry, assuming nothing is available and quit. 00894 */ 00895 if (!mon_nr) 00896 return NULL; 00897 00898 mon_nr = rndm(0, mon_nr-1); 00899 for (tobl = list->member; tobl; tobl = tobl->next) { 00900 otmp = tobl->ob; 00901 if (!otmp || !QUERY_FLAG(otmp, FLAG_MONSTER)) 00902 continue; 00903 if (otmp->level <= summon_level && !mon_nr--) 00904 return otmp; 00905 } 00906 /* This should not happen */ 00907 LOG(llevDebug, "choose_cult_monster() mon_nr was set, but did not find a monster\n"); 00908 return NULL; 00909 } 00910 00929 int summon_object(object *op, object *caster, object *spell_ob, int dir, const char *stringarg) { 00930 sint16 x, y, nrof = 1, i; 00931 archetype *summon_arch; 00932 int ndir, mult; 00933 00934 if (spell_ob->other_arch) { 00935 summon_arch = spell_ob->other_arch; 00936 } else if (spell_ob->randomitems) { 00937 int level = caster_level(caster, spell_ob); 00938 treasure *tr, *lasttr = NULL; 00939 00940 /* In old code, this was a very convuluted for statement, 00941 * with all the checks in the 'for' portion itself. Much 00942 * more readable to break some of the conditions out. 00943 */ 00944 for (tr = spell_ob->randomitems->items; tr; tr = tr->next) { 00945 if (level < tr->magic) 00946 break; 00947 lasttr = tr; 00948 if (stringarg && !strcmp(tr->item->name, stringarg)) 00949 break; 00950 if (tr->next == NULL || tr->next->item == NULL) 00951 break; 00952 } 00953 if (!lasttr) { 00954 LOG(llevError, "Treasurelist %s did not generate a valid entry in summon_object\n", spell_ob->randomitems->name); 00955 draw_ext_info(NDI_UNIQUE, 0, op, 00956 MSG_TYPE_SPELL, MSG_TYPE_SPELL_FAILURE, 00957 "The spell fails to summon any monsters.", NULL); 00958 return 0; 00959 } 00960 summon_arch = lasttr->item; 00961 nrof = lasttr->nrof; 00962 00963 } else if (spell_ob->race && !strcmp(spell_ob->race, "GODCULTMON")) { 00964 const object *god = find_god(determine_god(op)); 00965 object *mon, *owner; 00966 int summon_level, tries; 00967 00968 if (!god && ((owner = get_owner(op)) != NULL)) { 00969 god = find_god(determine_god(owner)); 00970 } 00971 /* If we can't find a god, can't get what monster to summon */ 00972 if (!god) 00973 return 0; 00974 00975 if (!god->race) { 00976 draw_ext_info_format(NDI_UNIQUE, 0, op, 00977 MSG_TYPE_SPELL, MSG_TYPE_SPELL_FAILURE, 00978 "%s has no creatures that you may summon!", 00979 "%s has no creatures that you may summon!", 00980 god->name); 00981 return 0; 00982 } 00983 /* the summon level */ 00984 summon_level = caster_level(caster, spell_ob); 00985 if (summon_level == 0) 00986 summon_level = 1; 00987 tries = 0; 00988 do { 00989 mon = choose_cult_monster(op, god, summon_level); 00990 if (!mon) { 00991 draw_ext_info_format(NDI_UNIQUE, 0, op, 00992 MSG_TYPE_SPELL, MSG_TYPE_SPELL_FAILURE, 00993 "%s fails to send anything.", 00994 "%s fails to send anything.", 00995 god->name); 00996 return 0; 00997 } 00998 ndir = dir; 00999 if (!ndir) 01000 ndir = find_free_spot(mon, op->map, op->x, op->y, 1, SIZEOFFREE); 01001 if (ndir == -1 01002 || ob_blocked(mon, op->map, op->x+freearr_x[ndir], op->y+freearr_y[ndir])) { 01003 ndir = -1; 01004 if (++tries == 5) { 01005 draw_ext_info(NDI_UNIQUE, 0, op, 01006 MSG_TYPE_SPELL, MSG_TYPE_SPELL_FAILURE, 01007 "There is something in the way.", NULL); 01008 return 0; 01009 } 01010 } 01011 } while (ndir == -1); 01012 if (mon->level > (summon_level/2)) 01013 nrof = random_roll(1, 2, op, PREFER_HIGH); 01014 else 01015 nrof = die_roll(2, 2, op, PREFER_HIGH); 01016 summon_arch = mon->arch; 01017 } else { 01018 summon_arch = NULL; 01019 } 01020 01021 if (spell_ob->stats.dam) 01022 nrof += spell_ob->stats.dam+SP_level_dam_adjust(caster, spell_ob); 01023 01024 if (!summon_arch) { 01025 draw_ext_info(NDI_UNIQUE, 0, op, MSG_TYPE_SPELL, MSG_TYPE_SPELL_FAILURE, 01026 "There is no monsters available for summoning.", NULL); 01027 return 0; 01028 } 01029 01030 if (dir) { 01031 /* Only fail if caster specified a blocked direction. */ 01032 x = freearr_x[dir]; 01033 y = freearr_y[dir]; 01034 if (ob_blocked(&summon_arch->clone, op->map, op->x+x, op->y+y)) { 01035 draw_ext_info(NDI_UNIQUE, 0, op, MSG_TYPE_SPELL, MSG_TYPE_SPELL_FAILURE, 01036 "There is something in the way.", NULL); 01037 return 0; 01038 } 01039 } 01040 01041 mult = (RANDOM()%2 ? -1 : 1); 01042 01043 for (i = 1; i <= nrof; i++) { 01044 archetype *atmp; 01045 object *prev = NULL, *head = NULL, *tmp; 01046 01047 if (dir) { 01048 ndir = absdir(dir+(i/2)*mult); 01049 mult = -mult; 01050 } else 01051 ndir = find_free_spot(&summon_arch->clone, op->map, op->x, op->y, 1, SIZEOFFREE); 01052 01053 if (ndir > 0) { 01054 x = freearr_x[ndir]; 01055 y = freearr_y[ndir]; 01056 } 01057 01058 if (ndir == -1 || ob_blocked(&summon_arch->clone, op->map, op->x+x, op->y+y)) 01059 continue; 01060 01061 for (atmp = summon_arch; atmp != NULL; atmp = atmp->more) { 01062 tmp = arch_to_object(atmp); 01063 if (atmp == summon_arch) { 01064 if (QUERY_FLAG(tmp, FLAG_MONSTER)) { 01065 set_owner(tmp, op); 01066 set_spell_skill(op, caster, spell_ob, tmp); 01067 tmp->enemy = op->enemy; 01068 tmp->type = 0; 01069 CLEAR_FLAG(tmp, FLAG_SLEEP); 01070 if (op->type == PLAYER || QUERY_FLAG(op, FLAG_FRIENDLY)) { 01071 /* If this is not set, we make it friendly */ 01072 if (!QUERY_FLAG(spell_ob, FLAG_MONSTER)) { 01073 SET_FLAG(tmp, FLAG_FRIENDLY); 01074 add_friendly_object(tmp); 01075 tmp->stats.exp = 0; 01076 if (spell_ob->attack_movement) 01077 tmp->attack_movement = spell_ob->attack_movement; 01078 if (get_owner(op)) 01079 set_owner(tmp, get_owner(op)); 01080 } 01081 } 01082 } 01083 if (tmp->speed > MIN_ACTIVE_SPEED) 01084 tmp->speed_left = -1; 01085 } 01086 if (head == NULL) 01087 head = tmp; 01088 else { 01089 tmp->head = head; 01090 prev->more = tmp; 01091 } 01092 prev = tmp; 01093 tmp->x = op->x+x+tmp->arch->clone.x; 01094 tmp->y = op->y+y+tmp->arch->clone.y; 01095 tmp->map = get_map_from_coord(op->map, &tmp->x, &tmp->y); 01096 } 01097 head->direction = freedir[ndir]; 01098 head->stats.exp = 0; 01099 head = insert_ob_in_map(head, head->map, op, 0); 01100 if (head && head->randomitems) { 01101 create_treasure(head->randomitems, head, GT_APPLY|GT_STARTEQUIP, 6, 0); 01102 } 01103 if (head != NULL) { 01104 mark_inventory_as_no_drop(head); 01105 } 01106 } /* for i < nrof */ 01107 return 1; 01108 } 01109 01119 static object *get_real_owner(object *ob) { 01120 object *realowner = ob; 01121 01122 if (realowner == NULL) 01123 return NULL; 01124 01125 while (get_owner(realowner) != NULL) { 01126 realowner = get_owner(realowner); 01127 } 01128 return realowner; 01129 } 01130 01146 int should_arena_attack(object *pet, object *owner, object *target) { 01147 object *rowner, *towner; 01148 01149 /* exit if the target, pet, or owner is null. */ 01150 if ((target == NULL) || (pet == NULL) || (owner == NULL)) 01151 return 0; 01152 01153 /* get the owners of itself and the target, this is to deal with pets of 01154 pets */ 01155 rowner = get_real_owner(owner); 01156 if (target->type != PLAYER) { 01157 towner = get_real_owner(target); 01158 } else { 01159 towner = NULL; 01160 } 01161 01162 /* if the pet has no owner, exit with error */ 01163 if (rowner == NULL) { 01164 LOG(llevError, "Pet has no owner.\n"); 01165 return 0; 01166 } 01167 01168 /* if the target is not a player, and has no owner, we shouldn't be here 01169 */ 01170 if (towner == NULL && target->type != PLAYER) { 01171 LOG(llevError, "Target is not a player but has no owner. We should not be here.\n"); 01172 return 0; 01173 } 01174 01175 /* make sure that the owner is a player */ 01176 if (rowner->type != PLAYER) 01177 return 0; 01178 01179 /* abort if the petmode is not arena */ 01180 if (rowner->contr->petmode != pet_arena) 01181 return 0; 01182 01183 /* abort if the pet, it's owner, or the target is not on battleground*/ 01184 if (!(op_on_battleground(pet, NULL, NULL, NULL) 01185 && op_on_battleground(owner, NULL, NULL, NULL) 01186 && op_on_battleground(target, NULL, NULL, NULL))) 01187 return 0; 01188 01189 /* if the target is a monster, make sure it's owner is not the same */ 01190 if (target->type != PLAYER && rowner == towner) 01191 return 0; 01192 01193 /* check if the target is a player which affects how it will handle 01194 parties */ 01195 if (target->type != PLAYER) { 01196 /* if the target is owned by a player make sure than make sure 01197 it's not in the same party */ 01198 if (towner->type == PLAYER && rowner->contr->party != NULL) { 01199 if (rowner->contr->party == towner->contr->party) 01200 return 0; 01201 } 01202 } else { 01203 /* if the target is a player make sure than make sure it's not 01204 in the same party */ 01205 if (rowner->contr->party != NULL) { 01206 if (rowner->contr->party == target->contr->party) 01207 return 0; 01208 } 01209 } 01210 01211 return 1; 01212 }