.-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-. 
 |                                                                       | 
 |                                                                       | 
 |                                                                       | 
 |                        IMPROVED DETECT POISON                         | 
 |                                                                       | 
 !                                                                       ! 
 :                         A Snippet written by                          : 
 :                              Valcados                                 : 
 .                           for SMAUG MUDS                              . 
 .                                                                       . 
 :                                                                       : 
 :                                                                       : 
 !                                                                       ! 
 |                                                                       | 
 |                                                                       | 
 |                                                                       | 
 |                                                                       | 
 |                                                                       | 
 `-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-'

DESCRIPTION:
     I asked myself:  "what part of SMAUG most desperately needs a
     facelift?"  The answer was obvious:  the spell, detect poison.
     This snippet does just that.

IN-GAME MECHANICS:
     Here's an exhaustive list of changes this does.
     - Works on all items, not just edibles.
     - Appropriate special message for casting detect poison on
       the black poison powder object used for poisoning weaps.
     - Detects if a drinkcon is empty
     - Detects alcoholic beverage and responds humorously.
     - Detects rotting food, warns of food poisoning
     - Takes cooking code into account
     - For pills/potions/salves, checks whether one of the spells
       case is poison, sleep, curse, blindness, gas breath, or
       chill touch.  Responds accordingly.
     - Checks if a weapon is poisoned.
     - For weapons, armor, lights, staves, and wands, checks
       to see if, when worn, the item has poison/curse/sleep as
       an effect.
     - Appropriate reply when cast on blood or quills
     - When cast on NPC corpse, check if the mob was poisonous
       while it lived.
     - When cast on a container, count the number of poison
       objects inside.
     - Detects poison-themed traps.

KNOWN PROBLEMS/BUGS:
     None at this time.    

HOW TO INSTALL:

1.  In magic.c, just above spell_detect_poison, add this:

DECLARE_SPEC_FUN( spec_poison );

2.  Comment out, or delete, the original spell_detect_poison,
    and replace it with this:

ch_ret spell_detect_poison( int sn, int level, CHAR_DATA *ch, void *vo )
{
    OBJ_DATA *obj = (OBJ_DATA *) vo;

    set_char_color( AT_MAGIC, ch );

    /* Handle a certain special case */
    if ( obj->pIndexData->vnum == OBJ_VNUM_BLACK_POWDER )
    {
      send_to_char( "You are OVERWHELMED by the amount of poison in this object!\n\r", ch );
      set_char_color( AT_CYAN, ch );
      send_to_char( "You convulse in disgust.\n\r", ch );
      act( AT_MAGIC, "As $n casts detect poison on the black powder, $e recoils in horror!", ch, NULL, NULL, TO_ROOM );
      return rNONE;
    }

    /* Do a switch based on item type */

    switch( obj->item_type )
    {
      case ITEM_DRINK_CON:

      if ( obj->value[1] <= 0 )
      {
        send_to_char( "You notice the container is empty.\n\r", ch );
        break;  /* Even though it's empty, it might be booby-trapped, so don't return yet */
      }

         /* For drink_cons, check for alcohol in addition */

      if ( obj->value[2] >= LIQ_MAX )
      {
        bug( "Spell_detect_poison: bad liquid number %d on item %d",
             obj->value[2], obj->pIndexData->vnum );
      }
      else if ( liq_table[obj->value[2]].liq_affect[COND_DRUNK] >= 10 )
        send_to_char( "You sense some very strong alcohol.\n\r", ch );
      else if ( liq_table[obj->value[2]].liq_affect[COND_DRUNK] >= 5 )
        send_to_char( "You sense some strong alcohol.\n\r", ch );
      else if ( liq_table[obj->value[2]].liq_affect[COND_DRUNK] >= 1 )
        send_to_char( "You sense some alcohol.\n\r", ch );

      case ITEM_FOOD:   /* Includes case ITEM_DRINK_CON, for lack of break */
      if ( obj->value[3] != 0 )
        send_to_char( "You smell poisonous fumes.\n\r", ch );
      else
      {
        /* If food is decaying, it is possible it can poison the player
            despite not being flagged as poisonous... see do_eat */
        if ( obj->item_type == ITEM_FOOD
        &&   obj->timer > 0
        &&   obj->value[1] > 0 )
        {
          switch( ( obj->timer * 10 ) / obj->value[1] )
          {
            case 0:
            send_to_char( "You smell poisonous fumes.\n\r", ch );
            break;
            case 1:
            send_to_char( "This item is thoroughly rotten.\n\r", ch );
            send_to_char( "There's a high chance it'll give you food poisoning.\n\r", ch );
            break;
            case 2:
            send_to_char( "This item is very rotten.\n\r", ch );
            send_to_char( "There's a decent chance it'll give you food poisoning.\n\r", ch );
            break;
            case 3:
            send_to_char( "This item is somewhat rotten.\n\r", ch );
            send_to_char( "There's a chance it'll give you food poisoning.\n\r", ch );
            break;
            default:
            send_to_char( "It looks very delicious.\n\r", ch );
            break;
          }
        }
        else
          send_to_char( "It looks very delicious.\n\r", ch );
      }
      break;

      case ITEM_COOK:
      if ( obj->value[2] == 0 )
        send_to_char( "It looks undercooked.\n\r", ch );
      else if ( obj->value[3] != 0 )
        send_to_char( "You smell poisonous fumes.\n\r", ch );
      else if ( obj->value[2] > 2 )
        send_to_char( "It looks burnt to a crisp... but edible.\n\r", ch );
      else if ( obj->value[2] == 2 )
        send_to_char( "It looks overcooked... but edible.\n\r", ch );
      else
        send_to_char( "It looks very delicious.\n\r", ch );
      break;

      case ITEM_PILL:
      case ITEM_POTION:
      case ITEM_SALVE:

         /* For pills and potions and salves, examine the spells they cast */
         /* We use a trick so that a single for-loop suffices for all 3, even
              though salves have different objectvalue structure */
      {
        int psn = 0, sleep = 0, curse = 0, sn;
        bool fPoison = FALSE, fSleep = FALSE, fCurse = FALSE;
        int objvals[4], *objval;
        SKILLTYPE *skill;

        if ( obj->item_type == ITEM_SALVE )
          objvals[0] = 4, objvals[1] = 5, objvals[2] = -1;  /* objvalues with spell sn's */
        else
          objvals[0] = 1, objvals[1] = 2, objvals[2] = 3, objvals[3] = -1;

        for ( objval = objvals; *objval != -1; objval++ )
        {
          sn = obj->value[*objval];

          if ( sn == -1 )
          continue;

          if ( sn == gsn_poison )
          {
            psn += obj->value[0];  /* v0 = spell level */
            fPoison = TRUE;
            continue;
          }

          if ( sn == gsn_sleep )
          {
            sleep += obj->value[0];
            fSleep = TRUE;
            continue;
          }

          if ( sn == gsn_curse )
          {
            curse += obj->value[0];
            fCurse = TRUE;
            continue;
          }

          if ( sn == gsn_blindness )   /* Count this as a minor poison */
          {
            psn += obj->value[0] / 3;
            fPoison = TRUE;
            continue;
          }

          if ( sn == gsn_chill_touch ) /* Count this as a minor depressant */
          {
            sleep += obj->value[0] / 10;
            fSleep = TRUE;
            continue;
          }

          skill = get_skilltype(sn);

          if ( !skill || !skill->spell_fun )
          continue;

          /* Count gas breath as a weak poison, even though it's not
                   the _eater_ who suffers.... ;) */
          if ( skill->spell_fun == spell_gas_breath )
          {
            psn += obj->value[0] / 2;
            fPoison = TRUE;
          }
          else if ( skill->spell_fun == spell_remove_curse )
          curse = 0;
          else if ( skill->spell_fun == spell_cure_poison )
          psn = 0;
          else if ( skill->spell_fun == spell_dispel_magic )
          {
            psn /= 10;
            curse /= 10;
            sleep /= 10;
          }

        } /* for loop */

        if ( !fPoison && !fCurse && !fSleep )
        {
          if ( obj->item_type != ITEM_SALVE )
          send_to_char( "It looks very delicious.\n\r", ch );
          send_to_char( "It looks very delicious.\n\r", ch );
          else
          send_to_char( "It looks very healthy.\n\r", ch );
        }
        else
        {
          if ( fPoison && psn == 0 )
            send_to_char( "This bizarre substance combines toxins and antidotes.\n\r", ch );
          else if ( psn > 0 )
          {
            if ( psn >= 50 )
              send_to_char( "This item is full of poison!\n\r", ch );
            else if ( psn >= 30 )
              send_to_char( "This item contains a lot of poison!\n\r", ch );
            else if ( psn >= 10 )
              send_to_char( "This item contains poison!\n\r", ch );
            else
              send_to_char( "This item contains trace amounts of poison!\n\r", ch );
          }
          if ( fCurse && curse == 0 )
            send_to_char( "This bizarre substance combines spiritual toxins with spiritual antidotes.\n\r", ch );
          else if ( curse > 0 )
          {
            if ( curse >= 50 )
              send_to_char( "This item is full of spiritual poison!\n\r", ch );
            else if ( curse >= 30 )
              send_to_char( "This item contains a lot of spiritual poison!\n\r", ch );
            else if ( curse >= 10 )
            else if ( curse >= 10 )
              send_to_char( "This item contains spiritual poison!\n\r", ch );
            else
              send_to_char( "This item contains trace amounts of spiritual poison!\n\r", ch );
          }
          if ( fSleep && sleep == 0 )
            send_to_char( "This bizarre substance combines depressants with their antidotes.\n\r", ch );
          else if ( sleep > 0 )
          {
            if ( sleep >= 50 )
              send_to_char( "This item is full of depressants!\n\r", ch );
            else if ( sleep >= 30 )
              send_to_char( "This item contains a lot of depressants!\n\r", ch );
            else if ( sleep >= 10 )
              send_to_char( "This item contains depressants!\n\r", ch );
            else
              send_to_char( "This item contains trace amounts of depressants!\n\r", ch );
          }
        }
      } /* ends case ITEM_PILL/POTION/SALVE */
      break;

      /* Report poisoned weapons */

      case ITEM_WEAPON:
      if ( xIS_SET( obj->extra_flags, ITEM_POISONED ) )
      send_to_char( "This weapon is coated in a deadly poison.\n\r", ch );

      case ITEM_ARMOR:  /* Includes case ITEM_WEAPON for lack of a break*/
      case ITEM_LIGHT:
      case ITEM_STAFF:
      case ITEM_WAND:
      /* For equipment, examine affects */
      {
        AFFECT_DATA *paf;
        int poisons = 0;  /* This is a flag of individual bits */
        SKILLTYPE *skill;
        char * const equipment_poison_report[] = {
                                      "no poison",
                                      "deadly poisons",
                                      "spiritual poisons",
                                      "holistic poisons",
                                      "depressants",
                                      "poisonous depressants",
                                      "accursed depressants",
                                      "a truly vile mixture of toxins" };

        for ( paf = obj->pIndexData->first_affect; paf; paf = paf->next )
        {
          if ( paf->location == APPLY_AFFECT
          &&   IS_SET( paf->modifier, 1 << AFF_POISON ) )
            poisons |= 1;
          if ( paf->location == APPLY_AFFECT
          &&   IS_SET( paf->modifier, 1 << AFF_CURSE ) )
            poisons |= 2;
          if ( paf->location == APPLY_AFFECT
          &&   IS_SET( paf->modifier, 1 << AFF_SLEEP ) )
            poisons |= 4;
          if ( ( paf->location == APPLY_WEARSPELL || paf->location == APPLY_REMOVESPELL )
          &&   IS_VALID_SN( paf->modifier )
          && ( skill = skill_table[paf->modifier] ) != NULL
          &&   skill->spell_fun )
          {
            if ( skill->spell_fun == spell_poison )
              poisons |= 1;
            else if ( skill->spell_fun == spell_curse )
              poisons |= 2;
            else if ( skill->spell_fun == spell_sleep )
              poisons |= 4;
          }
        }

        for ( paf = obj->first_affect; paf; paf = paf->next )
        {
          if ( paf->location == APPLY_AFFECT
          &&   IS_SET( paf->modifier, 1 << AFF_POISON ) )
            poisons |= 1;
          if ( paf->location == APPLY_AFFECT
          &&   IS_SET( paf->modifier, 1 << AFF_CURSE ) )
            poisons |= 2;
          if ( paf->location == APPLY_AFFECT
          &&   IS_SET( paf->modifier, 1 << AFF_SLEEP ) )
            poisons |= 4;
          if ( ( paf->location == APPLY_WEARSPELL || paf->location == APPLY_REMOVESPELL )
          &&   IS_VALID_SN( paf->modifier )
          && ( skill = skill_table[paf->modifier] ) != NULL
          &&   skill->spell_fun )
          {
            if ( skill->spell_fun == spell_poison )
              poisons |= 1;
            else if ( skill->spell_fun == spell_curse )
              poisons |= 2;
            else if ( skill->spell_fun == spell_sleep )
              poisons |= 4;
          }
        }

        if ( obj->item_type != ITEM_WEAPON || !xIS_SET( obj->extra_flags, ITEM_POISONED ) || poisons > 1 )
        ch_printf( ch, "You manage to detect %s.\n\r", equipment_poison_report[poisons] );

      }  /* end case WEAPON/ARMOR */
      break;

      case ITEM_BLOOD:
      if ( IS_VAMPIRE( ch ) )
        send_to_char( "It looks extremely delicious.\n\r", ch );
      else
        send_to_char( "It is not in your nature to drink this.\n\r"
                      "But you sense no poison.\n\r", ch );
      break;


         /* For NPC corpses, check for poison attacks the mob had while alive,
             or if the mob was innately affected by poison */
      case ITEM_CORPSE_NPC:
      {
        MOB_INDEX_DATA *pMobIndex;

        pMobIndex = get_mob_index((sh_int) abs(obj->cost));

        if ( !pMobIndex )
        {
          send_to_char( "It looks very delicious.\n\r", ch );
          break;
        }

        if ( pMobIndex->spec_fun == spec_poison
        ||   xIS_SET( pMobIndex->affected_by, AFF_POISON )
        ||   xIS_SET( pMobIndex->affected_by, AFF_VENOMSHIELD )
        ||   xIS_SET( pMobIndex->attacks, ATCK_POISON )
        ||   xIS_SET( pMobIndex->attacks, ATCK_NASTYPOISON )
        ||   xIS_SET( pMobIndex->attacks, ATCK_GASBREATH ) )
          send_to_char( "You smell poisonous fumes.\n\r", ch );
        else
          send_to_char( "It looks very delicious.\n\r", ch );
      }
      break;

      case ITEM_PEN:
      if ( obj->value[1] >= 1 )
        send_to_char( "You smell ink.  It is probably not good to eat.\n\r", ch );
      else
        send_to_char( "It looks dry.\n\r", ch );
      break;

      case ITEM_CONTAINER:
        /* Count the number of poisonous foods and drinks */
      {
        OBJ_DATA *inside;
        int poison = 0;

        for ( inside = obj->first_content; inside; inside = inside->next_content )
        {
          if ( inside->item_type != ITEM_FOOD && inside->item_type != ITEM_DRINK_CON && inside->item_type != ITEM_COOK )
          continue;
          if ( inside->value[3] != 0 )
            poison += inside->count;
          else if ( inside->item_type == ITEM_COOK && inside->value[2] == 0 )
            poison += inside->count;
        }

        if ( !poison )
          send_to_char( "You don't sense any poisoned food or drink in this container.\n\r", ch );
        else
          ch_printf( ch, "You sense the presence of %d poisonous substance%s.\n\r", poison, poison != 1 ? "s" : "" );
      }
      break;

      default:
      send_to_char( "It doesn't look poisoned.\n\r", ch );
      break;

    }

    /* Check for poisonous booby-traps */
    if ( obj->first_content )
    {
      OBJ_DATA *trap;

      for ( trap = obj->first_content; trap; trap = trap->next_content )
      {
        if ( trap->item_type == ITEM_TRAP
        && ( trap->value[1] == TRAP_TYPE_POISON_GAS
        ||   trap->value[1] == TRAP_TYPE_POISON_DART
        ||   trap->value[1] == TRAP_TYPE_POISON_NEEDLE
        ||   trap->value[1] == TRAP_TYPE_POISON_DAGGER
        ||   trap->value[1] == TRAP_TYPE_POISON_ARROW ) )
        {
          send_to_char( "Your instincts alert you to the presence of a poisonous booby-trap!\n\r", ch );
          break;
        }
      }
    }

    return rNONE;
}