Difference for server/monster.c from version 1.65 to 1.66


version 1.65 version 1.66
Line 1
 
Line 1
 /*  /*
  * static char *rcsid_monster_c =   * static char *rcsid_monster_c =
  *    "$Id: monster.c,v 1.65 2003/06/26 11:27:43 gros Exp $";   *    "$Id: monster.c,v 1.66 2003/09/13 05:02:11 mwedel Exp $";
  */   */
   
 /*  /*
Line 33
 
Line 33
 #include <skills.h>  #include <skills.h>
 #endif  #endif
   
 #if 0  
 /*  
  * When parsing a message-struct, the msglang struct is used  
  * to contain the values.  
  * This struct will be expanded as new features are added.  
  * When things are stable, it will be parsed only once.  
  */  
   
 typedef struct _msglang {  
     char **messages; /* An array of messages */  
     char ***keywords; /* For each message, an array of strings to match */  
 } msglang;  
   
 #endif  
   
 #define MIN_MON_RADIUS 3 /* minimum monster detection radius */  #define MIN_MON_RADIUS 3 /* minimum monster detection radius */
   
Line 641
 
Line 627
         if(QUERY_FLAG(op,FLAG_RUN_AWAY))          if(QUERY_FLAG(op,FLAG_RUN_AWAY))
         {          {
      part->stats.wc+=10;       part->stats.wc+=10;
      (void)skill_attack(enemy,part,0,NULL);       (void)skill_attack(enemy,part,0,NULL, NULL);
      part->stats.wc-=10;       part->stats.wc-=10;
  }   }
         else          else
             (void)skill_attack(enemy,part,0,NULL);              (void)skill_attack(enemy,part,0,NULL, NULL);
     } /* if monster is in attack range */      } /* if monster is in attack range */
   
     if(QUERY_FLAG(part,FLAG_FREED))    /* Might be freed by ghost-attack or hit-back */      if(QUERY_FLAG(part,FLAG_FREED))    /* Might be freed by ghost-attack or hit-back */
Line 659
 
Line 645
     }      }
     return 0;      return 0;
 }  }
   
 int can_hit(object *ob1,object *ob2, rv_vector *rv) {  int can_hit(object *ob1,object *ob2, rv_vector *rv) {
     object *more;      object *more;
     rv_vector rv1;      rv_vector rv1;
Line 687
 
Line 672
  * in fact cast the spell (sp dependencies and what not.)  That is because   * in fact cast the spell (sp dependencies and what not.)  That is because
  * this function is also sued to see if the monster should use spell items   * this function is also sued to see if the monster should use spell items
  * (rod/horn/wand/scroll).   * (rod/horn/wand/scroll).
    * Note that there are certainly other offensive spells that could be
    * included, but I decided to leave out the spells that may kill more
    * monsters than players (eg, disease).
    *
    * This could be a lot smarter - if there are few monsters around,
    * then disease might not be as bad. Likewise, if the monster is damaged,
    * the right type of healing spell could be useful.
  */   */
 static int monster_should_cast_spell(object *monster, int sp)  
 {  
     /* Not 'useful' spells. */  
     if (spells[sp].path&(PATH_INFO|PATH_TRANSMUTE|PATH_TRANSFER|PATH_LIGHT)) return 0;  
   
     /* We should really put a check in for PATH_RESTORE - if the monster has nothing  static int monster_should_cast_spell(object *monster, object *spell_ob)
      * wrong with it, no reason for it to cast one of those.  But 'wrong with it'  {
      * is a very broad term.      if (spell_ob->subtype == SP_BOLT || spell_ob->subtype == SP_BULLET ||
      */   spell_ob->subtype == SP_EXPLOSION || spell_ob->subtype == SP_CONE ||
    spell_ob->subtype == SP_BOMB || spell_ob->subtype == SP_SMITE ||
    spell_ob->subtype == SP_MAGIC_MISSILE || spell_ob->subtype == SP_SUMMON_GOLEM ||
    spell_ob->subtype == SP_MAGIC_WALL || spell_ob->subtype == SP_SUMMON_MONSTER ||
    spell_ob->subtype == SP_MOVING_BALL || spell_ob->subtype == SP_SWARM ||
    spell_ob->subtype == SP_INVISIBLE)
   
     /* Presumably useful. */  
     if (spells[sp].onself) return 2;  
     return 1;      return 1;
   
       return 0;
 }  }
   
   
 #define MAX_KNOWN_SPELLS 20  #define MAX_KNOWN_SPELLS 20
   
 /* Returns a randomly selected spell.    This logic is still  /* Returns a randomly selected spell.    This logic is still
Line 711
 
Line 704
  * wizard spells, as the check is against sp, and not grace.   * wizard spells, as the check is against sp, and not grace.
  * can mosnters know cleric spells?   * can mosnters know cleric spells?
  */   */
   
 object *monster_choose_random_spell(object *monster) {  object *monster_choose_random_spell(object *monster) {
     object *altern[MAX_KNOWN_SPELLS];      object *altern[MAX_KNOWN_SPELLS];
     object *tmp;      object *tmp;
     int i=0,j;      int i=0;
   
     for(tmp=monster->inv;tmp!=NULL;tmp=tmp->below)      for(tmp=monster->inv;tmp!=NULL;tmp=tmp->below)
  if(tmp->type==ABILITY||tmp->type==SPELLBOOK) {   if (tmp->type==SPELLBOOK || tmp->type==SPELL) {
      /*  Check and see if it's actually a useful spell */       /* Check and see if it's actually a useful spell.
      if (monster_should_cast_spell(monster, tmp->stats.sp)) {        * If its a spellbook, the spell is actually the inventory item.
  if(tmp->stats.maxsp)        * if it is a spell, then it is just the object itself.
      for(j=0;i<MAX_KNOWN_SPELLS&&j<tmp->stats.maxsp;j++)        */
  altern[i++]=tmp;       if (monster_should_cast_spell(monster, (tmp->type==SPELLBOOK)?tmp->inv:tmp)) {
  else  
      altern[i++]=tmp;       altern[i++]=tmp;
  if(i==MAX_KNOWN_SPELLS)   if(i==MAX_KNOWN_SPELLS)
      break;       break;
      }       }
  }   }
   
     if(!i)      if(!i)
  return NULL;   return NULL;
     return altern[RANDOM()%i];      return altern[RANDOM()%i];
 }  }
   
   /* This checks to see if the monster should cast a spell/ability.
    * it returns true if the monster casts a spell, 0 if he doesn't.
    * head is the head of the monster.
    * part is the part of the monster we are checking against.
    * pl is the target.
    * dir is the direction to case.
    * rv is the vector which describes where the enemy is.
    */
   
 int monster_cast_spell(object *head, object *part,object *pl,int dir, rv_vector *rv) {  int monster_cast_spell(object *head, object *part,object *pl,int dir, rv_vector *rv) {
     object *spell_item;      object *spell_item;
     spell *sp;  
     int sp_typ, ability;  
     object *owner;      object *owner;
     rv_vector rv1;      rv_vector rv1;
   
Line 762
 
Line 759
     if(QUERY_FLAG(head,FLAG_CONFUSED))      if(QUERY_FLAG(head,FLAG_CONFUSED))
  dir = absdir(dir + RANDOM()%3 + RANDOM()%3 - 2);   dir = absdir(dir + RANDOM()%3 + RANDOM()%3 - 2);
   
     /*  If the monster hasn't already chosen a spell, choose one */      /* If the monster hasn't already chosen a spell, choose one
        * I'm not sure if it really make sense to pre-select spells (events
        * could be different by the time the monster goes again).
        */
     if(head->spellitem==NULL) {      if(head->spellitem==NULL) {
  if((spell_item=monster_choose_random_spell(head))==NULL) {   if((spell_item=monster_choose_random_spell(head))==NULL) {
      LOG(llevMonster,"Turned off spells in %s\n",head->name);       LOG(llevMonster,"Turned off spells in %s\n",head->name);
      CLEAR_FLAG(head, FLAG_CAST_SPELL); /* Will be turned on when picking up book */       CLEAR_FLAG(head, FLAG_CAST_SPELL); /* Will be turned on when picking up book */
      return 0;       return 0;
  }   }
    if (spell_item->type == SPELLBOOK) {
        spell_item=spell_item->inv;
        if (spell_item->inv) {
    LOG(llevError,"spellbook %s does contain a spell?", spell_item->name);
        } else
    spell_item=spell_item->inv;
    }
     }      }
     else      else
  spell_item=head->spellitem;    spell_item=head->spellitem;
   
       if (!spell_item) return 0;
   
     if(spell_item->stats.hp) {      /* Best guess this is a defensive/healing spell */
  /* Alternate long-range spell: check how far away enemy is */      if (spell_item->range<=1 || spell_item->stats.dam < 0)
  if(rv->distance>6)  
      sp_typ=spell_item->stats.hp;  
  else  
      sp_typ=spell_item->stats.sp;  
     } else  
  sp_typ=spell_item->stats.sp;  
   
     if((sp=find_spell(sp_typ))==NULL) {  
  LOG(llevError,"Warning: Couldn't find spell in item.\n");  
  return 0;  
     }  
     if (sp->onself) /* Spell should be cast on caster (ie, heal, strength) */  
  dir = 0;   dir = 0;
      
     /* Monster doesn't have enough spell-points */      /* Monster doesn't have enough spell-points */
     if(head->stats.sp<SP_level_spellpoint_cost(head,head,sp_typ))      if(head->stats.sp<SP_level_spellpoint_cost(head,spell_item, SPELL_MANA))
  return 0;   return 0;
   
     ability = (spell_item->type==ABILITY && !(spell_item->attacktype&AT_MAGIC));      if(head->stats.grace<SP_level_spellpoint_cost(head,spell_item, SPELL_GRACE))
    return 0;
   
     /* If we cast a spell, only use up casting_time speed */      head->stats.sp-=SP_level_spellpoint_cost(head,spell_item, SPELL_MANA);
     head->speed_left+=(float)1.0 - (float) spells[sp_typ].time/(float)20.0*(float)FABS(head->speed);       head->stats.grace-=SP_level_spellpoint_cost(head,spell_item, SPELL_GRACE);
   
     head->stats.sp-=SP_level_spellpoint_cost(head,head,sp_typ);      /* set this to null, so next time monster will choose something different */
     /* choose the spell the monster will cast next */      head->spellitem = NULL;
     /* choose the next spell to cast */  
     head->spellitem = monster_choose_random_spell(head);  
          
     return cast_spell(part,part,dir,sp_typ,ability, spellNormal,NULL);      return cast_spell(part,part,dir, spell_item, NULL);
 }  }
   
   
Line 833
 
Line 828
  dir = absdir(dir + RANDOM()%3 + RANDOM()%3 - 2);   dir = absdir(dir + RANDOM()%3 + RANDOM()%3 - 2);
   
     for (scroll=head->inv; scroll; scroll=scroll->below)      for (scroll=head->inv; scroll; scroll=scroll->below)
  if (scroll->type == SCROLL && monster_should_cast_spell(head, scroll->stats.sp)) break;   if (scroll->type == SCROLL && monster_should_cast_spell(head, scroll->inv)) break;
   
     /* Used up all his scrolls, so nothing do to */      /* Used up all his scrolls, so nothing do to */
     if (!scroll) {      if (!scroll) {
Line 841
 
Line 836
  return 0;   return 0;
     }      }
   
     if (spells[scroll->stats.sp].onself) /* Spell should be cast on caster (ie, heal, strength) */      /* Spell should be cast on caster (ie, heal, strength) */
       if (scroll->inv->range==0)
  dir = 0;   dir = 0;
   
     apply_scroll(part, scroll, dir);      apply_scroll(part, scroll, dir);
Line 893
 
Line 889
  return 0;   return 0;
     }      }
     /* use skill */      /* use skill */
     return do_skill(head, part,dir,NULL);      return do_skill(head, part, head->chosen_skill,dir,NULL);
 }  }
   
 /* Monster will use a ranged spell attack. */  /* Monster will use a ranged spell attack. */
Line 923
 
Line 919
  CLEAR_FLAG(head, FLAG_READY_RANGE);   CLEAR_FLAG(head, FLAG_READY_RANGE);
  return 0;   return 0;
     }      }
       if (!wand->inv) {
    LOG(llevError,"Wand %s lacks spell\n", wand->name);
    return 0;
       }
     if (wand->type == WAND) {      if (wand->type == WAND) {
  if(wand->stats.food<=0) {   if(wand->stats.food<=0) {
      manual_apply(head,wand,0);       manual_apply(head,wand,0);
Line 937
 
Line 937
  }   }
     }      }
     else {      else {
  if(wand->stats.hp<spells[wand->stats.sp].sp)   if(wand->stats.hp<MAX(wand->inv->stats.sp, wand->inv->stats.grace))
      return 0; /* Not recharged enough yet */       return 0; /* Not recharged enough yet */
     }      }
   
     /* Spell should be cast on caster (ie, heal, strength) */      /* Spell should be cast on caster (ie, heal, strength) */
     if (spells[wand->stats.sp].onself)      if (wand->inv->range==0)
  dir = 0;   dir = 0;
   
     if(cast_spell(part,wand,dir,wand->stats.sp,0,spellMisc,NULL)) {      if(cast_spell(part,wand,dir,wand->inv,NULL)) {
  if (wand->type==WAND)   if (wand->type==WAND)
      wand->stats.food--;       wand->stats.food--;
  return 1;   return 1;
Line 1238
 
Line 1238
     /* Eating food gets hp back */      /* Eating food gets hp back */
     else if (item->type == FOOD && mon->will_apply & WILL_APPLY_FOOD) flag=1;      else if (item->type == FOOD && mon->will_apply & WILL_APPLY_FOOD) flag=1;
     else if (item->type == SCROLL && QUERY_FLAG(mon, FLAG_USE_SCROLL)) {      else if (item->type == SCROLL && QUERY_FLAG(mon, FLAG_USE_SCROLL)) {
  if (monster_should_cast_spell(mon, item->stats.sp))   if (monster_should_cast_spell(mon, item->inv))
      SET_FLAG(mon, FLAG_READY_SCROLL);       SET_FLAG(mon, FLAG_READY_SCROLL);
  /* Don't use it right now */   /* Don't use it right now */
  return;   return;
Line 1459
 
Line 1459
     }      }
 }  }
   
 #if 0   
 static void free_messages(msglang *msgs) {  
   int messages, keywords;  
   
   if (!msgs)  
     return;  
   for(messages = 0; msgs->messages[messages]; messages++) {  
     if(msgs->keywords[messages]) {  
       for(keywords = 0; msgs->keywords[messages][keywords]; keywords++)  
         free(msgs->keywords[messages][keywords]);  
       free(msgs->keywords[messages]);  
     }  
     free(msgs->messages[messages]);  
   }  
   free(msgs->messages);  
   free(msgs->keywords);  
   free(msgs);  
 }  
   
 static msglang *parse_message(char *msg) {  
   msglang *msgs;  
   int nrofmsgs, msgnr, i;  
   char *cp, *line, *last;  
   char *buf = strdup_local(msg);  
   
   /* First find out how many messages there are.  A @ for each. */  
   for (nrofmsgs = 0, cp = buf; *cp; cp++)  
     if (*cp == '@')  
       nrofmsgs++;  
   if (!nrofmsgs)  
     return NULL;  
   
   msgs = (msglang *) malloc(sizeof(msglang));  
   msgs->messages = (char **) malloc(sizeof(char *) * (nrofmsgs + 1));  
   msgs->keywords = (char ***) malloc(sizeof(char **) * (nrofmsgs + 1));  
   for(i=0; i<=nrofmsgs; i++) {  
     msgs->messages[i] = NULL;  
     msgs->keywords[i] = NULL;  
   }  
   
   for (last = NULL, cp = buf, msgnr = 0;*cp; cp++)  
     if (*cp == '@') {  
       int nrofkeywords, keywordnr;  
       *cp = '\0'; cp++;  
       if(last != NULL)  
         msgs->messages[msgnr++] = strdup_local(last);  
       if(strncmp(cp,"match",5)) {  
         LOG(llevError,"Unsupported command in message.\n");  
         free(buf);  
         return NULL;  
       }  
       for(line = cp + 6, nrofkeywords = 0; *line != '\n' && *line; line++)  
         if(*line == '|')  
           nrofkeywords++;  
       if(line > cp + 6)  
         nrofkeywords++;  
       if(nrofkeywords < 1) {  
         LOG(llevError,"Too few keywords in message.\n");  
         free(buf);  
         free_messages(msgs);  
         return NULL;  
       }  
       msgs->keywords[msgnr] = (char **) malloc(sizeof(char **) * (nrofkeywords +1));  
       msgs->keywords[msgnr][nrofkeywords] = NULL;  
       last = cp + 6;  
       cp = strchr(cp,'\n');  
       if(cp != NULL)  
         cp++;  
       for(line = last, keywordnr = 0;line<cp && *line;line++)  
         if(*line == '\n' || *line == '|') {  
           *line = '\0';  
           if (last != line)  
             msgs->keywords[msgnr][keywordnr++] = strdup_local(last);  
    else {  
          if (keywordnr<nrofkeywords)  
  {  
     /* Whoops, Either got || or |\n in @match. Not good */  
     msgs->keywords[msgnr][keywordnr++] = strdup_local("xxxx");  
     /* We need to set the string to something sensible to    *  
      * prevent crashes later. Unfortunately, we can't set to *  
      * NULL, as that's used to terminate the for loop in     *  
      * talk_to_npc.  Using xxxx should also help map         *  
      * developers track down the problem cases.              */  
     LOG (llevError, "Tried to set a zero length message in parse_message\n");  
     /* I think this is a error worth reporting at a reasonably *  
      * high level. When logging gets redone, this should       *  
      * be something like MAP_ERROR, or whatever gets put in    *  
      * place. */  
     if (keywordnr>1)  
     /* This is purely addtional information, should *  
      * only be gieb if asked */  
     LOG(llevDebug, "Msgnr %d, after keyword %s\n",msgnr+1,msgs->keywords[msgnr][keywordnr-2]);  
     else  
     LOG(llevDebug, "Msgnr %d, first keyword\n",msgnr+1);  
  }  
    }  
           last = line + 1;  
         }  
    /*  
     * your eyes aren't decieving you, this is code repetition.  However,  
     * the above code doesn't catch the case where line<cp going into the  
     * for loop, skipping the above code completely, and leaving undefined  
            * data in the keywords array.  This patches it up and solves a crash  
     * bug.  garbled 2001-10-20  
     */  
    if (keywordnr < nrofkeywords) {  
    LOG(llevError, "Map developer screwed up match statement"  
        " in parse_message\n");  
     if (keywordnr>1)  
     /* This is purely addtional information, should *  
      * only be gieb if asked */  
     LOG(llevDebug, "Msgnr %d, after keyword %s\n", msgnr+1,  
     msgs->keywords[msgnr][keywordnr-2]);  
     else  
     LOG(llevDebug, "Msgnr %d, first keyword\n",msgnr+1);  
 #if 0  
 /* Removed this block - according to the compiler, this has no effect,  
  * and looking at the if statement above, the certainly appears to be the  
  * case.  
  */  
        for(keywordnr; keywordnr <= nrofkeywords; keywordnr++)  
        msgs->keywords[msgnr][keywordnr] = strdup_local("xxxx");  
 #endif  
    }  
       last = cp;  
     }  
   if(last != NULL)  
     msgs->messages[msgnr++] = strdup_local(last);  
   free(buf);  
   return msgs;  
 }  
   
 static void dump_messages(msglang *msgs) {  
   int messages, keywords;  
   for(messages = 0; msgs->messages[messages]; messages++) {  
     LOG(llevDebug, "@match ");  
     for(keywords = 0; msgs->keywords[messages][keywords]; keywords++)  
       LOG(llevDebug, "%s ",msgs->keywords[messages][keywords]);  
     LOG(llevDebug, "\n%s\n",msgs->messages[messages]);  
   }  
 }  
   
 #endif  
   
 /* This replaces all the msglang stuff about which seems to be a lot of  /* This replaces all the msglang stuff about which seems to be a lot of
  * unneeded complication - since the setup of that data is never re-used   * unneeded complication - since the setup of that data is never re-used
  * (say 'hi' to monster, then 'yes', it would re-do the entire parse-message)   * (say 'hi' to monster, then 'yes', it would re-do the entire parse-message)
Line 2013
 
Line 1869
     return 0;      return 0;
 }  }
   
   
 /* assuming no walls/barriers, lets check to see if its *possible*   /* assuming no walls/barriers, lets check to see if its *possible*
  * to see an enemy. Note, "detection" is different from "seeing".   * to see an enemy. Note, "detection" is different from "seeing".
  * See can_detect_enemy() for more details. -b.t.   * See can_detect_enemy() for more details. -b.t.
    * return 0 if can't be seen, 1 if can be
  */   */
   
 int can_see_enemy (object *op, object *enemy) {  int can_see_enemy (object *op, object *enemy) {
Line 2052
 
Line 1910
  * and making it a conditional makes the code pretty ugly.   * and making it a conditional makes the code pretty ugly.
  */   */
  if (!QUERY_FLAG(looker,FLAG_SEE_INVISIBLE)) {   if (!QUERY_FLAG(looker,FLAG_SEE_INVISIBLE)) {
      if (is_true_undead(looker) && QUERY_FLAG(enemy, FLAG_INVIS_UNDEAD)) return 0;       if (makes_invisible_to(enemy, looker)) return 0;
      else if (!is_true_undead(looker) && !QUERY_FLAG(enemy, FLAG_INVIS_UNDEAD)) return 0;  
  }   }
   } else if(looker->type==PLAYER) /* for players, a (possible) shortcut */    } else if(looker->type==PLAYER) /* for players, a (possible) shortcut */
       if(player_can_view(looker,enemy)) return 1;        if(player_can_view(looker,enemy)) return 1;
Line 2074
 
Line 1931
   
   return 1;    return 1;
 }  }
   


Legend:
line(s) removed in v.1.65 
line(s) changed
 line(s) added in v.1.66

File made using version 1.98 of cvs2html by leaf at 2011-07-21 17:37