Crossfire Server, Trunk
pets.cpp
Go to the documentation of this file.
1 /*
2  * Crossfire -- cooperative multi-player graphical RPG and adventure game
3  *
4  * Copyright (c) 1999-2014 Mark Wedel and the Crossfire Development Team
5  * Copyright (c) 1992 Frank Tore Johansen
6  *
7  * Crossfire is free software and comes with ABSOLUTELY NO WARRANTY. You are
8  * welcome to redistribute it under certain conditions. For details, please
9  * see COPYING and LICENSE.
10  *
11  * The authors can be reached via e-mail at <crossfire@metalforge.org>.
12  */
13 
19 #include "global.h"
20 
21 #include <assert.h>
22 #include <ctype.h>
23 #include <stdlib.h>
24 #include <string.h>
25 
26 #include "sproto.h"
27 
34 static void mark_inventory_as_no_drop(object *ob) {
38 }
39 
54 object *pets_get_enemy(object *pet, rv_vector *rv) {
55  object *owner, *tmp, *attacker, *tmp3;
56  int i;
57  int16_t x, y;
58  mapstruct *nm;
59  int search_arr[SIZEOFFREE];
60  int mflags;
61 
62  attacker = pet->attacked_by; /*pointer to attacking enemy*/
63  pet->attacked_by = NULL; /*clear this, since we are dealing with it*/
64 
65  owner = object_get_owner(pet);
66  if (owner != NULL) {
67  /* If the owner has turned on the pet, make the pet
68  * unfriendly.
69  */
70  if (monster_check_enemy(owner, rv) == pet) {
73  pet->attack_movement &= ~PETMOVE;
74  return owner;
75  }
76  } else {
77  /* else the owner is no longer around, so the
78  * pet no longer needs to be friendly.
79  */
82  pet->attack_movement &= ~PETMOVE;
83  return NULL;
84  }
85  /* If they are not on the same map, the pet won't be agressive */
86  if (!on_same_map(pet, owner))
87  return NULL;
88 
89  /* See if the pet has an existing enemy. If so, don't start a new one*/
90  tmp = monster_check_enemy(pet, rv);
91  if (tmp != NULL) {
92  if (tmp != owner || QUERY_FLAG(pet, FLAG_CONFUSED) || !QUERY_FLAG(pet, FLAG_FRIENDLY))
93  return tmp;
94 
95  /* without this check, you can actually get pets with
96  * enemy set to owner!
97  */
98  object_set_enemy(pet, NULL);
99  }
100  get_search_arr(search_arr);
101 
102  if (owner->type == PLAYER && owner->contr->petmode == pet_sad) {
103  tmp = monster_find_nearest_enemy(pet, owner);
104  if (tmp != NULL && get_rangevector(pet, tmp, rv, 0))
105  return tmp;
106  /* if we got here we still have no enemy */
107  /* we return NULL to avoid heading back to the owner */
108  object_set_enemy(pet, NULL);
109  return NULL;
110  }
111 
112  /* Since the pet has no existing enemy, look for anything nasty
113  * around the owner that it should go and attack.
114  */
115  tmp3 = NULL;
116  for (i = 0; i < SIZEOFFREE; i++) {
117  x = owner->x+freearr_x[search_arr[i]];
118  y = owner->y+freearr_y[search_arr[i]];
119  nm = owner->map;
120  /* Only look on the space if there is something alive there. */
121  mflags = get_map_flags(nm, &nm, x, y, &x, &y);
122  if (!(mflags&P_OUT_OF_MAP) && mflags&P_IS_ALIVE) {
123  FOR_MAP_PREPARE(nm, x, y, tmp) {
124  object *tmp2 = HEAD(tmp);
125 
126  if (QUERY_FLAG(tmp2, FLAG_ALIVE)
127  && ((!QUERY_FLAG(tmp2, FLAG_FRIENDLY) && tmp2->type != PLAYER) || pets_should_arena_attack(pet, owner, tmp2))
128  && !QUERY_FLAG(tmp2, FLAG_UNAGGRESSIVE)
129  && tmp2 != pet
130  && tmp2 != owner
131  && monster_can_detect_enemy(pet, tmp2, rv)) {
132  if (!monster_can_see_enemy(pet, tmp2)) {
133  if (tmp3 == NULL)
134  tmp3 = tmp2;
135  } else {
136  object_set_enemy(pet, tmp2);
137  if (monster_check_enemy(pet, rv) != NULL)
138  return tmp2;
139  object_set_enemy(pet, NULL);
140  }
141  }/* if this is a valid enemy */
142  } FOR_MAP_FINISH();/* for objects on this space */
143  }/* if there is something living on this space */
144  } /* for loop of spaces around the owner */
145 
146  /* fine, we went through the whole loop and didn't find one we could
147  see, take what we have */
148  if (tmp3 != NULL) {
149  object_set_enemy(pet, tmp3);
150  if (monster_check_enemy(pet, rv) != NULL)
151  return tmp3;
152  object_set_enemy(pet, NULL);
153  }
154 
155  /* No threat to owner, check to see if the pet has an attacker*/
156  if (attacker != NULL) {
157  /* need to be sure this is the right one! */
158  if (attacker->count == pet->attacked_by_count) {
159  /* also need to check to make sure it is not freindly */
160  /* or otherwise non-hostile, and is an appropriate target */
161  if (!QUERY_FLAG(attacker, FLAG_FRIENDLY) && on_same_map(pet, attacker)) {
162  object_set_enemy(pet, attacker);
163  if (monster_check_enemy(pet, rv) != NULL)
164  return attacker;
165  object_set_enemy(pet, NULL);
166  }
167  }
168  }
169 
170  /* Don't have an attacker or legal enemy, so look for a new one!.
171  * This looks for one around where the pet is. Thus, you could lead
172  * a pet to danger, then take a few steps back. This code is basically
173  * the same as the code that looks around the owner.
174  */
175  if (owner->type == PLAYER && owner->contr->petmode != pet_defend) {
176  tmp3 = NULL;
177  for (i = 0; i < SIZEOFFREE; i++) {
178  x = pet->x+freearr_x[search_arr[i]];
179  y = pet->y+freearr_y[search_arr[i]];
180  nm = pet->map;
181  /* Only look on the space if there is something alive there. */
182  mflags = get_map_flags(nm, &nm, x, y, &x, &y);
183  if (!(mflags&P_OUT_OF_MAP) && mflags&P_IS_ALIVE) {
184  FOR_MAP_PREPARE(nm, x, y, tmp) {
185  object *tmp2 = HEAD(tmp);
186  if (QUERY_FLAG(tmp2, FLAG_ALIVE)
187  && ((!QUERY_FLAG(tmp2, FLAG_FRIENDLY) && tmp2->type != PLAYER) || pets_should_arena_attack(pet, owner, tmp2))
188  && !QUERY_FLAG(tmp2, FLAG_UNAGGRESSIVE)
189  && tmp2 != pet
190  && tmp2 != owner
191  && monster_can_detect_enemy(pet, tmp2, rv)) {
192  if (!monster_can_see_enemy(pet, tmp2)) {
193  assert(tmp3 == NULL);
194  } else {
195  object_set_enemy(pet, tmp2);
196  if (monster_check_enemy(pet, rv) != NULL)
197  return tmp2;
198  object_set_enemy(pet, NULL);
199  }
200  } /* make sure we can get to the bugger */
201  } FOR_MAP_FINISH();/* for objects on this space */
202  } /* if there is something living on this space */
203  } /* for loop of spaces around the pet */
204  } /* pet in defence mode */
205 
206  /* fine, we went through the whole loop and didn't find one we could
207  see, take what we have */
208  if (tmp3 != NULL) {
209  object_set_enemy(pet, tmp3);
210  if (monster_check_enemy(pet, rv) != NULL)
211  return tmp3;
212  object_set_enemy(pet, NULL);
213  }
214 
215  /* Didn't find anything - return the owner's enemy or NULL */
216  return monster_check_enemy(pet, rv);
217 }
218 
225 void pets_terminate_all(object *owner) {
226  objectlink *link = get_friends_of(owner), *cur;
227  if (!link) {
228  return;
229  }
230  for (cur = link; cur != NULL; cur = cur->next) {
231  object *ob = cur->ob;
232  if (!QUERY_FLAG(ob, FLAG_REMOVED))
233  object_remove(ob);
236  }
237 
238  free_objectlink(link);
239 }
240 
249 void pets_attempt_follow(object *for_owner, int force) {
250  objectlink *list = get_friends_of(for_owner), *cur;
251  object *owner;
252  if (!list) {
253  return;
254  }
255 
256  for (cur = list; cur != NULL; cur = cur->next) {
257  if (cur->ob->type != PLAYER
258  && QUERY_FLAG(cur->ob, FLAG_FRIENDLY)
259  && (owner = object_get_owner(cur->ob)) != NULL
260  && (for_owner == NULL || for_owner == owner)
261  && (force || !on_same_map(owner, cur->ob))) {
262  /* follow owner checks map status for us. Note that pet can
263  * die in pets_follow_owner(), so check for obl->ob existence
264  */
265  pets_follow_owner(cur->ob, owner);
266  if (cur->ob && QUERY_FLAG(cur->ob, FLAG_REMOVED) && FABS(cur->ob->speed) > MIN_ACTIVE_SPEED) {
267  LOG(llevMonster, "(pet failed to follow)\n");
268  remove_friendly_object(cur->ob);
270  }
271  }
272  }
274 }
275 
284 void pets_follow_owner(object *ob, object *owner) {
285  int dir;
286 
287  if (!QUERY_FLAG(ob, FLAG_REMOVED))
288  object_remove(ob);
289 
290  if (owner->map == NULL) {
291  LOG(llevError, "Can't follow owner: no map.\n");
292  return;
293  }
294  if (owner->map->in_memory != MAP_IN_MEMORY) {
295  LOG(llevError, "Owner of the pet not on a map in memory!?\n");
296  return;
297  }
298  dir = object_find_free_spot(ob, owner->map, owner->x, owner->y, 1, SIZEOFFREE);
299 
300  if (dir == -1) {
301  LOG(llevMonster, "No space for pet to follow, freeing %s.\n", ob->name);
302  return; /* Will be freed since it's removed */
303  }
304  object_insert_in_map_at(ob, owner->map, NULL, 0, owner->x+freearr_x[dir], owner->y+freearr_y[dir]);
305  if (owner->type == PLAYER) /* Uh, I hope this is always true... */
307  "Your pet magically appears next to you");
308  return;
309 }
310 
317 void pets_move(object *ob) {
318  int dir, i;
319  tag_t tag;
320  int16_t dx, dy;
321  object *owner;
322  mapstruct *m;
323 
324  /* Check to see if player pulled out */
325  owner = object_get_owner(ob);
326  if (owner == NULL) {
327  object_remove(ob); /* Will be freed when returning */
330  LOG(llevMonster, "Pet: no owner, leaving.\n");
331  return;
332  }
333 
334  /* move monster into the owners map if not in the same map */
335  if (!on_same_map(ob, owner)) {
336  pets_follow_owner(ob, owner);
337  return;
338  }
339  /* Calculate Direction */
340  if (owner->type == PLAYER && owner->contr->petmode == pet_sad) {
341  /* in S&D mode, if we have no enemy, run randomly about. */
342  for (i = 0; i < 15; i++) {
343  dir = get_random_dir();
344  dx = ob->x+freearr_x[dir];
345  dy = ob->y+freearr_y[dir];
346  m = ob->map;
347  if (!(get_map_flags(ob->map, &m, dx, dy, &dx, &dy)&P_OUT_OF_MAP)
348  && !OB_TYPE_MOVE_BLOCK(ob, GET_MAP_MOVE_BLOCK(m, dx, dy)))
349  break;
350  }
351  } else {
352  rv_vector rv;
353 
354  if (get_rangevector(ob, owner, &rv, 0))
355  dir = rv.direction;
356  else
357  dir = get_random_dir();
358  }
359  ob->direction = dir;
360 
361  tag = ob->count;
362  /* move_ob returns 0 if the object couldn't move. If that is the
363  * case, lets do some other work.
364  */
365  if (!move_ob(ob, dir, ob)) {
366  object *part;
367 
368  /* the failed move_ob above may destroy the pet, so check here */
370  return;
371 
372  for (part = ob; part != NULL; part = part->more) {
373  dx = part->x+freearr_x[dir];
374  dy = part->y+freearr_y[dir];
375  m = get_map_from_coord(part->map, &dx, &dy);
376  if (m == NULL)
377  continue;
378 
379  FOR_MAP_PREPARE(m, dx, dy, ob2) {
380  object *new_ob;
381 
382  new_ob = HEAD(ob2);
383  if (new_ob == ob)
384  break;
385  if (new_ob == owner)
386  return;
387  if (object_get_owner(new_ob) == owner)
388  break;
389 
390  /* Hmm. Did we try to move into an enemy monster? If so,
391  * make it our enemy.
392  */
393  if (QUERY_FLAG(new_ob, FLAG_ALIVE)
395  && !QUERY_FLAG(new_ob, FLAG_UNAGGRESSIVE)
396  && !QUERY_FLAG(new_ob, FLAG_FRIENDLY)) {
397  object_set_enemy(ob, new_ob);
398  if (new_ob->enemy == NULL)
399  object_set_enemy(new_ob, ob);
400  return;
401  } else if (new_ob->type == PLAYER) {
402  draw_ext_info(NDI_UNIQUE, 0, new_ob,
404  "You stand in the way of someones pet.");
405  return;
406  }
407  } FOR_MAP_FINISH();
408  }
409  /* Try a different course */
410  dir = absdir(dir+4-(RANDOM()%5)-(RANDOM()%5));
411  (void)move_ob(ob, dir, ob);
412  }
413  return;
414 }
415 
416 /****************************************************************************
417  *
418  * GOLEM SPELL CODE
419  *
420  ****************************************************************************/
421 
434 static object *fix_summon_pet(archetype *at, object *op, int dir) {
435  archetype *atmp;
436  object *tmp = NULL, *prev = NULL, *head = NULL;
437 
438  for (atmp = at; atmp != NULL; atmp = atmp->more) {
439  tmp = arch_to_object(atmp);
440  if (atmp == at) {
441 
442  /* Ensure the golem can actually move if no move_type defined.
443  * This check is redundant since this is checked at server startup. */
444  if (tmp->move_type == 0) {
445  LOG(llevError, "summoned %s [%s] is without move_type!\n", tmp->name, atmp->name);
446  tmp->move_type = MOVE_WALK;
447  }
448 
450  if (op->type == PLAYER) {
451  tmp->stats.exp = 0;
455  } else if (QUERY_FLAG(op, FLAG_FRIENDLY)) {
456  object *owner = object_get_owner(op);
457 
458  if (owner != NULL) {/* For now, we transfer ownership */
459  object_set_owner(tmp, owner);
460  tmp->attack_movement = PETMOVE;
463  }
464  }
465  if (op->type != PLAYER) {
466  tmp->attack_movement = PETMOVE;
467  tmp->speed_left = -1;
468  tmp->type = 0;
469  object_set_enemy(tmp, op->enemy);
470  } else
471  tmp->type = GOLEM;
472  }
473  if (head == NULL)
474  head = tmp;
475  tmp->x = op->x+freearr_x[dir]+tmp->arch->clone.x;
476  tmp->y = op->y+freearr_y[dir]+tmp->arch->clone.y;
477  tmp->map = op->map;
478  if (tmp->invisible)
479  tmp->invisible = 0;
480  if (head != tmp)
481  tmp->head = head,
482  prev->more = tmp;
483  prev = tmp;
484  }
485  head->direction = dir;
486 
487  if (head->randomitems) {
488  create_treasure(head->randomitems, head, GT_STARTEQUIP, 6, 0);
489  if (QUERY_FLAG(head, FLAG_MONSTER)) {
491  }
492  }
494 
495  /* need to change some monster attr to prevent problems/crashing */
496  head->last_heal = 0;
497  head->last_eat = 0;
498  head->last_grace = 0;
499  head->last_sp = 0;
500  head->other_arch = NULL;
501  head->stats.exp = 0;
502  CLEAR_FLAG(head, FLAG_CHANGING);
504  CLEAR_FLAG(head, FLAG_GENERATOR);
505  CLEAR_FLAG(head, FLAG_SPLITTING);
506  if (head->attacktype&AT_GHOSTHIT)
507  head->attacktype = (AT_PHYSICAL|AT_DRAIN);
508 
509  return head;
510 }
511 
519 void pets_move_golem(object *op) {
520  int made_attack = 0;
521  object *tmp;
522  tag_t tag;
523  object *owner;
524 
525  if (QUERY_FLAG(op, FLAG_MONSTER))
526  return; /* Has already been moved */
527 
528  owner = object_get_owner(op);
529  if (owner == NULL) {
530  LOG(llevDebug, "Golem without owner destructed.\n");
531  object_remove(op);
533  return;
534  }
535  /* It would be nice to have a cleaner way of what message to print
536  * when the golem expires than these hard coded entries.
537  * Note it is intentional that a golems duration is based on its
538  * hp, and not duration
539  */
540  if (--op->stats.hp < 0) {
541  if (op->msg != NULL)
543  op->msg);
544  owner->contr->ranges[range_golem] = NULL;
545  owner->contr->golem_count = 0;
547  object_remove(op);
549  return;
550  }
551 
552  /* Do golem attacks/movement for single & multisq golems.
553  * Assuming here that op is the 'head' object. Pass only op to
554  * move_ob (makes recursive calls to other parts)
555  * move_ob returns 0 if the creature was not able to move.
556  */
557  tag = op->count;
558  if (move_ob(op, op->direction, op))
559  return;
561  return;
562 
563  for (tmp = op; tmp; tmp = tmp->more) {
564  int16_t x = tmp->x+DIRX(op), y = tmp->y+DIRY(op);
565  object *victim;
566  mapstruct *m;
567  int mflags;
568 
569  m = op->map;
570  mflags = get_map_flags(m, &m, x, y, &x, &y);
571 
572  if (mflags&P_OUT_OF_MAP)
573  continue;
574 
575  victim = NULL;
576  FOR_MAP_PREPARE(op->map, x, y, tmp)
577  if (QUERY_FLAG(tmp, FLAG_ALIVE)) {
578  victim = tmp;
579  break;
580  }
581  FOR_MAP_FINISH();
582 
583  /* We used to call will_hit_self to make sure we don't
584  * hit ourselves, but that didn't work, and I don't really
585  * know if that was more efficient anyways than this.
586  * This at least works. Note that victim->head can be NULL,
587  * but since we are not trying to dereferance that pointer,
588  * that isn't a problem.
589  */
590  if (victim != NULL && victim != op && victim->head != op) {
591  /* for golems with race fields, we don't attack
592  * aligned races
593  */
594 
595  if (victim->race != NULL && op->race != NULL && strstr(op->race, victim->race)) {
596  if (owner != NULL)
599  "%s avoids damaging %s.",
600  op->name, victim->name);
601  } else if (victim == owner) {
602  if (owner != NULL)
605  "%s avoids damaging you.",
606  op->name);
607  } else {
608  attack_ob(victim, op);
609  made_attack = 1;
610  }
611  } /* If victim */
612  }
613  if (made_attack)
615 }
616 
630 void pets_control_golem(object *op, int dir) {
631  op->direction = dir;
632 }
633 
651 int pets_summon_golem(object *op, object *caster, int dir, object *spob) {
652  object *tmp;
653  const object *god = NULL;
654  archetype *at;
655  char buf[MAX_BUF];
656 
657  /* Because there can be different golem spells, player may want to
658  * 'lose' their old golem.
659  */
660  if (op->type == PLAYER
661  && op->contr->ranges[range_golem] != NULL
662  && op->contr->golem_count == op->contr->ranges[range_golem]->count) {
665  "You dismiss your existing golem.");
666  object_remove(op->contr->ranges[range_golem]);
667  object_free_drop_inventory(op->contr->ranges[range_golem]);
668  op->contr->ranges[range_golem] = NULL;
669  op->contr->golem_count = -1;
670  }
671 
672  if (spob->other_arch != NULL)
673  at = spob->other_arch;
674  else if (spob->race != NULL) {
675  god = find_god(determine_god(caster));
676  if (god == NULL) {
679  "You must worship a god to cast %s.",
680  spob->name);
681  return 0;
682  }
683 
684  at = determine_holy_arch(god, spob->race);
685  if (at == NULL) {
688  "%s has no %s for you to call.",
689  god->name, spob->race);
690  return 0;
691  }
692  } else {
693  LOG(llevError, "Spell %s lacks other_arch\n", spob->name);
694  return 0;
695  }
696 
697  if (!dir)
698  dir = object_find_free_spot(NULL, op->map, op->x, op->y, 1, SIZEOFFREE1+1);
699 
700  if (dir == -1
701  || ob_blocked(&at->clone, op->map, op->x+freearr_x[dir], op->y+freearr_y[dir])) {
704  "There is something in the way.");
705  return 0;
706  }
707  /* basically want to get proper map/coordinates for this object */
708  tmp = fix_summon_pet(at, op, dir);
709  if (tmp == NULL) {
712  "Your spell fails.");
713  return 0;
714  }
715 
716  if (op->type == PLAYER) {
717  tmp->type = GOLEM;
719  set_spell_skill(op, caster, spob, tmp);
720  op->contr->ranges[range_golem] = tmp;
721  op->contr->golem_count = tmp->count;
722  /* give the player control of the golem */
723  op->contr->shoottype = range_golem;
724  } else {
725  if (QUERY_FLAG(op, FLAG_FRIENDLY)) {
726  object *owner = object_get_owner(op);
727 
728  if (owner != NULL) { /* For now, we transfer ownership */
729  object_set_owner(tmp, owner);
730  tmp->attack_movement = PETMOVE;
732  set_spell_skill(op, caster, spob, tmp);
734  }
735  }
737  }
738 
739  /* make the speed positive.*/
740  tmp->speed = FABS(tmp->speed);
741 
742  /* This sets the level dependencies on dam and hp for monsters */
743  /* players can't cope with too strong summonings. */
744  /* but monsters can. reserve these for players. */
745  if (op->type == PLAYER) {
746  tmp->stats.hp += spob->duration+SP_level_duration_adjust(caster, spob);
747  tmp->stats.maxhp = tmp->stats.hp;
748  if (!spob->stats.dam)
749  tmp->stats.dam += SP_level_dam_adjust(caster, spob);
750  else
751  tmp->stats.dam = spob->stats.dam+SP_level_dam_adjust(caster, spob);
752  tmp->speed += .02*SP_level_range_adjust(caster, spob);
753  tmp->speed = MIN(tmp->speed, 1.0);
754  if (spob->attacktype)
755  tmp->attacktype = spob->attacktype;
756  }
757  tmp->stats.wc -= SP_level_wc_adjust(caster, spob);
758 
759  /* limit the speed to 0.3 for non-players, 1 for players. */
760 
761  /* make experience increase in proportion to the strength.
762  * this is a bit simplistic - we are basically just looking at how
763  * often the sp doubles and use that as the ratio.
764  */
765  tmp->stats.exp *= 1+(MAX(spob->stats.maxgrace, spob->stats.sp)/caster_level(caster, spob));
766  tmp->speed_left = 0;
767  tmp->direction = dir;
768 
769  /* Holy spell - some additional tailoring */
770  if (god != NULL) {
771  object *tmp2;
772 
773  snprintf(buf, sizeof(buf), "%s of %s", spob->name, god->name);
774  buf[0] = toupper(buf[0]);
775  for (tmp2 = tmp; tmp2; tmp2 = tmp2->more) {
776  if (tmp2->name != NULL)
777  free_string(tmp2->name);
778  tmp2->name = add_string(buf);
779  }
780  tmp->attacktype |= god->attacktype;
781  memcpy(tmp->resist, god->resist, sizeof(tmp->resist));
782  if (tmp->race != NULL)
783  FREE_AND_CLEAR_STR(tmp->race);
784  if (god->race != NULL)
785  tmp->race = add_string(god->race);
786  if (tmp->slaying != NULL)
787  FREE_AND_CLEAR_STR(tmp->slaying);
788  if (god->slaying != NULL)
789  tmp->slaying = add_string(god->slaying);
790  /* safety, we must allow a god's servants some reasonable attack */
791  if (!(tmp->attacktype&AT_PHYSICAL))
792  tmp->attacktype |= AT_PHYSICAL;
793  }
794 
795  object_insert_in_map_at(tmp, tmp->map, op, 0, tmp->x, tmp->y);
796  return 1;
797 }
798 
799 /***************************************************************************
800  *
801  * Summon monster/pet/other object code
802  *
803  ***************************************************************************/
804 
819 static object *choose_cult_monster(object *pl, const object *god, int summon_level) {
820  char buf[MAX_BUF];
821  const char *race;
822  int racenr, i;
823  object *otmp;
824 
825  /* Determine the number of races available */
826  racenr = 0;
827  safe_strncpy(buf, god->race, sizeof(buf));
828  race = strtok(buf, ",");
829  while (race) {
830  racenr++;
831  race = strtok(NULL, ",");
832  }
833 
834  /* next, randomly select a race from the aligned_races string */
835  if (racenr > 1) {
836  racenr = rndm(0, racenr-1);
837  safe_strncpy(buf, god->race, sizeof(buf));
838  race = strtok(buf, ",");
839  for (i = 0; i < racenr; i++)
840  race = strtok(NULL, ",");
841  } else
842  race = god->race;
843 
844  otmp = races_get_random_monster(race, summon_level);
845  if (!otmp) {
848  "The spell fails! %s's creatures are beyond the range of your summons",
849  god->name);
850  }
851  return otmp;
852 }
853 
872 int pets_summon_object(object *op, object *caster, object *spell_ob, int dir, const char *stringarg) {
873  int16_t x, y, nrof = 1, i;
874  archetype *summon_arch;
875  int ndir, mult;
876 
877  if (spell_ob->other_arch != NULL) {
878  summon_arch = spell_ob->other_arch;
879  } else if (spell_ob->randomitems != NULL) {
880  int level = caster_level(caster, spell_ob);
881  treasure *tr, *lasttr = NULL;
882 
883  /* In old code, this was a very convuluted for statement,
884  * with all the checks in the 'for' portion itself. Much
885  * more readable to break some of the conditions out.
886  */
887  for (tr = spell_ob->randomitems->items; tr; tr = tr->next) {
888  if (level < tr->magic)
889  break;
890  lasttr = tr;
891  if (stringarg != NULL && !strcmp(tr->item->name, stringarg))
892  break;
893  if (tr->next == NULL || tr->next->item == NULL)
894  break;
895  }
896  if (lasttr == NULL) {
897  LOG(llevError, "Treasurelist %s did not generate a valid entry in pets_summon_object\n", spell_ob->randomitems->name);
900  "The spell fails to summon any monsters.");
901  return 0;
902  }
903  summon_arch = lasttr->item;
904  nrof = lasttr->nrof;
905  } else if (spell_ob->race != NULL && !strcmp(spell_ob->race, "GODCULTMON")) {
906  const object *god = find_god(determine_god(op));
907  object *mon, *owner;
908  int summon_level, tries;
909 
910  if (god == NULL) {
911  owner = object_get_owner(op);
912  if (owner != NULL) {
913  god = find_god(determine_god(owner));
914  }
915  }
916  /* If we can't find a god, can't get what monster to summon */
917  if (god == NULL)
918  return 0;
919 
920  if (god->race == NULL) {
923  "%s has no creatures that you may summon!",
924  god->name);
925  return 0;
926  }
927  /* the summon level */
928  summon_level = caster_level(caster, spell_ob);
929  if (summon_level == 0)
930  summon_level = 1;
931  tries = 0;
932  do {
933  mon = choose_cult_monster(op, god, summon_level);
934  if (mon == NULL) {
937  "%s fails to send anything.",
938  god->name);
939  return 0;
940  }
941  ndir = dir;
942  if (!ndir)
943  ndir = object_find_free_spot(mon, op->map, op->x, op->y, 1, SIZEOFFREE);
944  if (ndir == -1
945  || ob_blocked(mon, op->map, op->x+freearr_x[ndir], op->y+freearr_y[ndir])) {
946  ndir = -1;
947  if (++tries == 5) {
950  "There is something in the way.");
951  return 0;
952  }
953  }
954  } while (ndir == -1);
955  if (mon->level > summon_level/2)
956  nrof = random_roll(1, 2, op, PREFER_HIGH);
957  else
958  nrof = die_roll(2, 2, op, PREFER_HIGH);
959  summon_arch = mon->arch;
960  } else {
961  summon_arch = NULL;
962  }
963 
964  if (spell_ob->stats.dam)
965  nrof += spell_ob->stats.dam+SP_level_dam_adjust(caster, spell_ob);
966 
967  if (summon_arch == NULL) {
969  "There is no monsters available for summoning.");
970  return 0;
971  }
972 
973  if (dir) {
974  /* Only fail if caster specified a blocked direction. */
975  x = freearr_x[dir];
976  y = freearr_y[dir];
977  if (ob_blocked(&summon_arch->clone, op->map, op->x+x, op->y+y)) {
979  "There is something in the way.");
980  return 0;
981  }
982  }
983 
984  mult = (RANDOM()%2 ? -1 : 1);
985 
986  for (i = 1; i <= nrof; i++) {
987  archetype *atmp;
988  object *prev = NULL, *head = NULL, *tmp;
989 
990  if (dir) {
991  ndir = absdir(dir+(i/2)*mult);
992  mult = -mult;
993  } else
994  ndir = object_find_free_spot(&summon_arch->clone, op->map, op->x, op->y, 1, SIZEOFFREE);
995 
996  x = ndir > 0 ? freearr_x[ndir] : 0;
997  y = ndir > 0 ? freearr_y[ndir] : 0;
998  if (ndir == -1 || ob_blocked(&summon_arch->clone, op->map, op->x+x, op->y+y))
999  continue;
1000 
1001  for (atmp = summon_arch; atmp != NULL; atmp = atmp->more) {
1002  tmp = arch_to_object(atmp);
1003  if (atmp == summon_arch) {
1004  if (QUERY_FLAG(tmp, FLAG_MONSTER)) {
1006  set_spell_skill(op, caster, spell_ob, tmp);
1007  object_set_enemy(tmp, op->enemy);
1008  tmp->type = 0;
1010  if (op->type == PLAYER || QUERY_FLAG(op, FLAG_FRIENDLY)) {
1011  /* If this is not set, we make it friendly */
1012  if (!QUERY_FLAG(spell_ob, FLAG_MONSTER)) {
1015  tmp->stats.exp = 0;
1016  if (spell_ob->attack_movement)
1017  tmp->attack_movement = spell_ob->attack_movement;
1018  if (object_get_owner(op) != NULL)
1020  }
1021  }
1022  }
1023  if (tmp->speed > MIN_ACTIVE_SPEED)
1024  tmp->speed_left = -1;
1025  }
1026  if (head == NULL)
1027  head = tmp;
1028  else {
1029  tmp->head = head;
1030  prev->more = tmp;
1031  }
1032  prev = tmp;
1033  }
1034  head->direction = freedir[ndir];
1035  head->stats.exp = 0;
1036  head = object_insert_in_map_at(head, op->map, op, 0, op->x+x, op->y+y);
1037  if (head != NULL && head->randomitems) {
1038  create_treasure(head->randomitems, head, GT_STARTEQUIP, 6, 0);
1039  if (QUERY_FLAG(head, FLAG_MONSTER)) {
1041  }
1042  }
1043  if (head != NULL) {
1045  }
1046  } /* for i < nrof */
1047  return 1;
1048 }
1049 
1059 static object *get_real_owner(object *ob) {
1060  object *realowner = ob;
1061 
1062  if (realowner == NULL)
1063  return NULL;
1064 
1065  while (object_get_owner(realowner) != NULL) {
1066  realowner = object_get_owner(realowner);
1067  }
1068  return realowner;
1069 }
1070 
1086 int pets_should_arena_attack(object *pet, object *owner, object *target) {
1087  object *rowner, *towner;
1088 
1089  /* exit if the target, pet, or owner is null. */
1090  if (target == NULL || pet == NULL || owner == NULL)
1091  return 0;
1092 
1093  /* get the owners of itself and the target, this is to deal with pets of
1094  pets */
1095  rowner = get_real_owner(owner);
1096  if (target->type != PLAYER) {
1097  towner = get_real_owner(target);
1098  } else {
1099  towner = NULL;
1100  }
1101 
1102  /* if the pet has no owner, exit with error */
1103  if (rowner == NULL) {
1104  LOG(llevError, "Pet has no owner.\n");
1105  return 0;
1106  }
1107 
1108  /* if the target is not a player, and has no owner, we shouldn't be here
1109  */
1110  if (towner == NULL && target->type != PLAYER) {
1111  LOG(llevError, "Target is not a player but has no owner. We should not be here.\n");
1112  return 0;
1113  }
1114 
1115  /* make sure that the owner is a player */
1116  if (rowner->type != PLAYER)
1117  return 0;
1118 
1119  /* abort if the petmode is not arena */
1120  if (rowner->contr->petmode != pet_arena)
1121  return 0;
1122 
1123  /* abort if the pet, it's owner, or the target is not on battleground*/
1124  if (!(op_on_battleground(pet, NULL, NULL, NULL)
1125  && op_on_battleground(owner, NULL, NULL, NULL)
1126  && op_on_battleground(target, NULL, NULL, NULL)))
1127  return 0;
1128 
1129  /* if the target is a monster, make sure it's owner is not the same */
1130  if (target->type != PLAYER && rowner == towner)
1131  return 0;
1132 
1133  /* check if the target is a player which affects how it will handle
1134  parties */
1135  if (target->type != PLAYER) {
1136  /* if the target is owned by a player make sure than make sure
1137  it's not in the same party */
1138  if (towner->type == PLAYER && rowner->contr->party != NULL) {
1139  if (rowner->contr->party == towner->contr->party)
1140  return 0;
1141  }
1142  } else {
1143  /* if the target is a player make sure than make sure it's not
1144  in the same party */
1145  if (rowner->contr->party != NULL) {
1146  if (rowner->contr->party == target->contr->party)
1147  return 0;
1148  }
1149  }
1150 
1151  return 1;
1152 }
SP_level_range_adjust
int SP_level_range_adjust(const object *caster, const object *spob)
Definition: spell_util.cpp:337
object_was_destroyed
#define object_was_destroyed(op, old_tag)
Definition: object.h:68
treasurestruct::item
struct archt * item
Definition: treasure.h:64
UP_OBJ_FACE
#define UP_OBJ_FACE
Definition: object.h:522
PLAYER
@ PLAYER
Definition: object.h:110
obj::attack_movement
uint16_t attack_movement
Definition: object.h:399
object_get_owner
object * object_get_owner(object *op)
Definition: object.cpp:804
set_spell_skill
void set_spell_skill(object *op, object *caster, object *spob, object *dest)
Definition: spell_util.cpp:92
global.h
choose_cult_monster
static object * choose_cult_monster(object *pl, const object *god, int summon_level)
Definition: pets.cpp:819
liv::dam
int16_t dam
Definition: living.h:46
safe_strncpy
#define safe_strncpy
Definition: compat.h:27
FOR_MAP_FINISH
#define FOR_MAP_FINISH()
Definition: define.h:730
remove_friendly_object
void remove_friendly_object(object *op)
Definition: friend.cpp:54
pl::golem_count
uint32_t golem_count
Definition: player.h:119
pets_terminate_all
void pets_terminate_all(object *owner)
Definition: pets.cpp:225
FLAG_STAND_STILL
#define FLAG_STAND_STILL
Definition: define.h:308
FLAG_CONFUSED
#define FLAG_CONFUSED
Definition: define.h:311
llevError
@ llevError
Definition: logger.h:11
FABS
#define FABS(x)
Definition: define.h:22
pets_control_golem
void pets_control_golem(object *op, int dir)
Definition: pets.cpp:630
LOG
void LOG(LogLevel logLevel, const char *format,...)
Definition: logger.cpp:51
SET_FLAG
#define SET_FLAG(xyz, p)
Definition: define.h:224
monster_can_detect_enemy
int monster_can_detect_enemy(object *op, object *enemy, rv_vector *rv)
Definition: monster.cpp:2579
FLAG_GENERATOR
#define FLAG_GENERATOR
Definition: define.h:248
pets_get_enemy
object * pets_get_enemy(object *pet, rv_vector *rv)
Definition: pets.cpp:54
diamondslots.x
x
Definition: diamondslots.py:15
obj::count
tag_t count
Definition: object.h:305
obj::map
struct mapdef * map
Definition: object.h:303
SIZEOFFREE1
#define SIZEOFFREE1
Definition: define.h:153
QUERY_FLAG
#define QUERY_FLAG(xyz, p)
Definition: define.h:226
obj::race
sstring race
Definition: object.h:324
get_random_dir
int get_random_dir(void)
Definition: utils.cpp:410
liv::maxgrace
int16_t maxgrace
Definition: living.h:45
op_on_battleground
int op_on_battleground(object *op, int *x, int *y, archetype **trophy)
Definition: player.cpp:4206
get_friends_of
objectlink * get_friends_of(const object *owner)
Definition: friend.cpp:119
AT_PHYSICAL
#define AT_PHYSICAL
Definition: attack.h:76
absdir
int absdir(int d)
Definition: object.cpp:3707
obj::attacked_by_count
tag_t attacked_by_count
Definition: object.h:391
object_set_enemy
void object_set_enemy(object *op, object *enemy)
Definition: object.cpp:915
MSG_TYPE_SPELL_PET
#define MSG_TYPE_SPELL_PET
Definition: newclient.h:632
pl
Definition: player.h:105
guildoracle.list
list
Definition: guildoracle.py:87
pets_summon_golem
int pets_summon_golem(object *op, object *caster, int dir, object *spob)
Definition: pets.cpp:651
object_set_owner
void object_set_owner(object *op, object *owner)
Definition: object.cpp:840
guildjoin.ob
ob
Definition: guildjoin.py:42
draw_ext_info_format
void draw_ext_info_format(int flags, int pri, const object *pl, uint8_t type, uint8_t subtype, const char *format,...) PRINTF_ARGS(6
mark_inventory_as_no_drop
static void mark_inventory_as_no_drop(object *ob)
Definition: pets.cpp:34
mapdef::in_memory
uint32_t in_memory
Definition: map.h:338
mon
object * mon
Definition: comet_perf.cpp:75
MIN
#define MIN(x, y)
Definition: compat.h:21
races_get_random_monster
object * races_get_random_monster(const char *race, int level)
Definition: races.cpp:24
SP_level_duration_adjust
int SP_level_duration_adjust(const object *caster, const object *spob)
Definition: spell_util.cpp:311
DIRX
#define DIRX(xyz)
Definition: define.h:463
Ice.tmp
int tmp
Definition: Ice.py:207
llevMonster
@ llevMonster
Definition: logger.h:14
treasureliststruct::name
sstring name
Definition: treasure.h:86
rndm
int rndm(int min, int max)
Definition: utils.cpp:162
range_golem
@ range_golem
Definition: player.h:34
create_treasure
void create_treasure(treasurelist *t, object *op, int flag, int difficulty, int tries)
Definition: treasure.cpp:256
P_IS_ALIVE
#define P_IS_ALIVE
Definition: map.h:238
MSG_TYPE_MISC
#define MSG_TYPE_MISC
Definition: newclient.h:413
GT_STARTEQUIP
@ GT_STARTEQUIP
Definition: treasure.h:33
buf
StringBuffer * buf
Definition: readable.cpp:1611
obj::randomitems
struct treasureliststruct * randomitems
Definition: object.h:393
treasurestruct
Definition: treasure.h:63
MAX
#define MAX(x, y)
Definition: compat.h:24
obj::duration
int16_t duration
Definition: object.h:411
find_god
const object * find_god(const char *name)
Definition: holy.cpp:319
archt
Definition: object.h:472
FLAG_ALIVE
#define FLAG_ALIVE
Definition: define.h:230
obj::slaying
sstring slaying
Definition: object.h:325
m
static event_registration m
Definition: citylife.cpp:425
obj::attacked_by
struct obj * attacked_by
Definition: object.h:390
MAP_IN_MEMORY
#define MAP_IN_MEMORY
Definition: map.h:131
monster_find_nearest_enemy
object * monster_find_nearest_enemy(object *npc, object *owner)
Definition: monster.cpp:175
object_free_drop_inventory
void object_free_drop_inventory(object *ob)
Definition: object.cpp:1560
object_update
void object_update(object *op, int action)
Definition: object.cpp:1434
PREFER_HIGH
#define PREFER_HIGH
Definition: define.h:563
oblnk::next
struct oblnk * next
Definition: object.h:451
obj::name
sstring name
Definition: object.h:317
determine_god
const char * determine_god(object *op)
Definition: gods.cpp:55
freearr_y
short freearr_y[SIZEOFFREE]
Definition: object.cpp:305
move_ob
int move_ob(object *op, int dir, object *originator)
Definition: move.cpp:58
MOVE_WALK
#define MOVE_WALK
Definition: define.h:392
pl::petmode
petmode_t petmode
Definition: player.h:115
add_string
sstring add_string(const char *str)
Definition: shstr.cpp:124
pets_should_arena_attack
int pets_should_arena_attack(object *pet, object *owner, object *target)
Definition: pets.cpp:1086
HEAD
#define HEAD(op)
Definition: object.h:596
pet_defend
@ pet_defend
Definition: player.h:60
pets_move_golem
void pets_move_golem(object *op)
Definition: pets.cpp:519
obj::x
int16_t x
Definition: object.h:333
CFweardisguise.tag
tag
Definition: CFweardisguise.py:25
GET_MAP_MOVE_BLOCK
#define GET_MAP_MOVE_BLOCK(M, X, Y)
Definition: map.h:193
obj::other_arch
struct archt * other_arch
Definition: object.h:421
FOR_INV_FINISH
#define FOR_INV_FINISH()
Definition: define.h:677
archt::more
struct archt * more
Definition: object.h:475
GOLEM
@ GOLEM
Definition: object.h:148
caster_level
int caster_level(const object *caster, const object *spell)
Definition: spell_util.cpp:193
FLAG_UNAGGRESSIVE
#define FLAG_UNAGGRESSIVE
Definition: define.h:272
pets_attempt_follow
void pets_attempt_follow(object *for_owner, int force)
Definition: pets.cpp:249
tag_t
uint32_t tag_t
Definition: object.h:12
sproto.h
FLAG_NO_DROP
#define FLAG_NO_DROP
Definition: define.h:288
get_map_from_coord
mapstruct * get_map_from_coord(mapstruct *m, int16_t *x, int16_t *y)
Definition: map.cpp:2393
obj::enemy
struct obj * enemy
Definition: object.h:389
mapdef
Definition: map.h:317
MSG_TYPE_SPELL
#define MSG_TYPE_SPELL
Definition: newclient.h:411
random_roll
int random_roll(int min, int max, const object *op, int goodbad)
Definition: utils.cpp:42
MSG_SUBTYPE_NONE
#define MSG_SUBTYPE_NONE
Definition: newclient.h:420
ob_blocked
int ob_blocked(const object *ob, mapstruct *m, int16_t x, int16_t y)
Definition: map.cpp:488
nlohmann::detail::void
j template void())
Definition: json.hpp:4099
FLAG_SPLITTING
#define FLAG_SPLITTING
Definition: define.h:266
object_insert_in_map_at
object * object_insert_in_map_at(object *op, mapstruct *m, object *originator, int flag, int x, int y)
Definition: object.cpp:2098
FLAG_MONSTER
#define FLAG_MONSTER
Definition: define.h:245
SIZEOFFREE
#define SIZEOFFREE
Definition: define.h:155
P_OUT_OF_MAP
#define P_OUT_OF_MAP
Definition: map.h:250
MAX_BUF
#define MAX_BUF
Definition: define.h:35
SP_level_wc_adjust
int SP_level_wc_adjust(const object *caster, const object *spob)
Definition: spell_util.cpp:361
free_string
void free_string(sstring str)
Definition: shstr.cpp:280
monster_check_apply_all
void monster_check_apply_all(object *monster)
Definition: monster.cpp:1997
RANDOM
#define RANDOM()
Definition: define.h:644
FREE_AND_CLEAR_STR
#define FREE_AND_CLEAR_STR(xyz)
Definition: global.h:198
guildbuy.ob2
ob2
Definition: guildbuy.py:23
FOR_MAP_PREPARE
#define FOR_MAP_PREPARE(map_, mx_, my_, it_)
Definition: define.h:723
obj::y
int16_t y
Definition: object.h:333
get_search_arr
void get_search_arr(int *search_arr)
Definition: object.cpp:3635
obj::arch
struct archt * arch
Definition: object.h:420
treasureliststruct::items
struct treasurestruct * items
Definition: treasure.h:92
FLAG_REMOVED
#define FLAG_REMOVED
Definition: define.h:232
monster_can_see_enemy
int monster_can_see_enemy(object *op, object *enemy)
Definition: monster.cpp:2762
obj::type
uint8_t type
Definition: object.h:346
NDI_UNIQUE
#define NDI_UNIQUE
Definition: newclient.h:262
FLAG_FRIENDLY
#define FLAG_FRIENDLY
Definition: define.h:246
obj::stats
living stats
Definition: object.h:376
SP_level_dam_adjust
int SP_level_dam_adjust(const object *caster, const object *spob)
Definition: spell_util.cpp:286
pets_summon_object
int pets_summon_object(object *op, object *caster, object *spell_ob, int dir, const char *stringarg)
Definition: pets.cpp:872
archt::clone
object clone
Definition: object.h:476
MSG_TYPE_SPELL_FAILURE
#define MSG_TYPE_SPELL_FAILURE
Definition: newclient.h:633
obj::contr
struct pl * contr
Definition: object.h:282
add_friendly_object
void add_friendly_object(object *op)
Definition: friend.cpp:34
get_map_flags
int get_map_flags(mapstruct *oldmap, mapstruct **newmap, int16_t x, int16_t y, int16_t *nx, int16_t *ny)
Definition: map.cpp:301
reputation.victim
victim
Definition: reputation.py:14
get_real_owner
static object * get_real_owner(object *ob)
Definition: pets.cpp:1059
give.op
op
Definition: give.py:33
pet_sad
@ pet_sad
Definition: player.h:59
pet_arena
@ pet_arena
Definition: player.h:61
object_find_free_spot
int object_find_free_spot(const object *ob, mapstruct *m, int x, int y, int start, int stop)
Definition: object.cpp:3552
pets_follow_owner
void pets_follow_owner(object *ob, object *owner)
Definition: pets.cpp:284
treasurestruct::nrof
uint16_t nrof
Definition: treasure.h:79
rv_vector
Definition: map.h:373
monster_check_enemy
object * monster_check_enemy(object *npc, rv_vector *rv)
Definition: monster.cpp:72
diamondslots.y
y
Definition: diamondslots.py:16
on_same_map
int on_same_map(const object *op1, const object *op2)
Definition: map.cpp:2665
CLEAR_FLAG
#define CLEAR_FLAG(xyz, p)
Definition: define.h:225
obj::more
struct obj * more
Definition: object.h:301
die_roll
int die_roll(int num, int size, const object *op, int goodbad)
Definition: utils.cpp:122
arch_to_object
object * arch_to_object(archetype *at)
Definition: arch.cpp:230
get_rangevector
int get_rangevector(object *op1, const object *op2, rv_vector *retval, int flags)
Definition: map.cpp:2553
pl::ranges
object * ranges[range_size]
Definition: player.h:116
MIN_ACTIVE_SPEED
#define MIN_ACTIVE_SPEED
Definition: define.h:639
treasurestruct::next
struct treasurestruct * next
Definition: treasure.h:69
draw_ext_info
void draw_ext_info(int flags, int pri, const object *pl, uint8_t type, uint8_t subtype, const char *message)
Definition: main.cpp:308
object_remove
void object_remove(object *op)
Definition: object.cpp:1833
pets_move
void pets_move(object *ob)
Definition: pets.cpp:317
freedir
int freedir[SIZEOFFREE]
Definition: object.cpp:317
OB_TYPE_MOVE_BLOCK
#define OB_TYPE_MOVE_BLOCK(ob1, type)
Definition: define.h:432
archt::name
sstring name
Definition: object.h:473
PETMOVE
#define PETMOVE
Definition: define.h:501
obj::attacktype
uint32_t attacktype
Definition: object.h:350
attack_ob
int attack_ob(object *op, object *hitter)
Definition: attack.cpp:919
freearr_x
short freearr_x[SIZEOFFREE]
Definition: object.cpp:299
oblnk
Definition: object.h:449
pl::party
partylist * party
Definition: player.h:202
FLAG_CHANGING
#define FLAG_CHANGING
Definition: define.h:263
AT_GHOSTHIT
#define AT_GHOSTHIT
Definition: attack.h:85
liv::sp
int16_t sp
Definition: living.h:42
rv_vector::direction
int direction
Definition: map.h:377
obj::resist
int16_t resist[NROFATTACKS]
Definition: object.h:349
FLAG_SLEEP
#define FLAG_SLEEP
Definition: define.h:307
FOR_INV_PREPARE
#define FOR_INV_PREPARE(op_, it_)
Definition: define.h:670
fix_summon_pet
static object * fix_summon_pet(archetype *at, object *op, int dir)
Definition: pets.cpp:434
AT_DRAIN
#define AT_DRAIN
Definition: attack.h:83
obj::level
int16_t level
Definition: object.h:359
llevDebug
@ llevDebug
Definition: logger.h:13
DIRY
#define DIRY(xyz)
Definition: define.h:464
determine_holy_arch
archetype * determine_holy_arch(const object *god, const char *type)
Definition: gods.cpp:675
dragon_attune.force
force
Definition: dragon_attune.py:45
level
Definition: level.py:1