.-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-. | | | | | | | 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; }