! SENSES                An Inform Library by L. Ross Raszewski
!                       To handle sensory perception.  Written in 5.5,
!                       Easily portable to 6.0 (I hope)
!                       Version 5/1  9-14-96
!
! This is a library to handle perception of the senses of Hearing, Smell, Touch
! and Taste.  (I figured sight is pretty well handled by the library.)
! Instalation is fairly simple;  This file should be included before Grammar,
! and the following lines shoudl appear before Verblib:
!
!  Replace TouchSub;
!  Replace ListenSub;
!  Replace TastSub;
!  Replace SmellSub;
!
! The following lines should be added to the LibraryMessages:
! (These are rerally optional, without them, attempts to smell a visible but
! unsmellable object, for example, will yield the library default.)
!
!  Smell:   if (lm_n==4) print_ret "You can't smell ", (the) noun, " from here.";
!  Listen:  if (lm_n==4) print_ret "You can't hear ", (the) noun, " from here.";
!  Taste:   if (lm_n==4) print_ret "You can't taste ", (the) noun, " from here.";
!  Touch:   if (lm_n==4) print_ret "You can't touch ", (the) noun, " from here.";
!
! This library uses four attributes, which are sense-versions of the library's
! "Transparent" attribute.  It also uses six properties (sorry), one for each
! sense, and two to handle sense "muting".  One Global variable (Sensedepth)
! is used.  It could have probably been done by using return values, but this
! was was more convenient for me.
!
! To use this library, give each object a string or routine for the properties
! "feel" "scent" "sound" and "taste".  Objects without these will return
! the usual Library message.  If an object's description depends on how deeply
! it is buried, a routine could check the sensedepth variable.  Sensedepth is
! the degree of separation between the player and the object, an object in the
! room with the player has a sensedepth of 0.  For example, a radio's sound
! routine could print "You hear some classical music" at a sensedepth of 0,
! and "You hear some muted music" at a sensedepth of 3.
! MaxSenseDepth is the deepest something can be "burried" while the parser
! still detects it.  An object with a sensedepth greater than the MaxSenseDepth
! will not be noticed.  This is important mostly for recursive senses, so that
! you don't get an anomalty like "Within the box you hear nothing unexpected"
!
! Smell and Listen recurse automatically; listening to an object will also
! listen to its contents, provided they are audible. The RecurseSense routine
! is provided to handle sense recursing.
!
! MaxSenseDepth has a default of 5, but can also be a routine, which checks
! the action varable, thus a perfumed radio (?) might only be smellable at
! a depth of 2, but could be heard as deep as 6.
!
! The Opacity of a container is its resistance to sensory transmittion.
! the default opacity is 1, but can be a routine as well.  Opacity affects
! sensedepth:  A radio in a box of opacity 2, which is inside a box of
! opacity 1 has a sensedepth of 3.  Sensedepth recurses down the object
! tree, but not up, except to the parent of the player.
! Open containers do not affect sensedepth
!
! Because of the inclusion location, the player can not refer to an object
! that is not in normal scope.  This is fairly logical, by my reconing, but
! I've made it easy if you want to add the ability to listen to the
! invisible radio: Add these lines AFTER Grammar:
!
! Extend "touch" * scope=TouchScope       ->Touch;
! Extend "smell" * scope=SmellScope       ->Smell;
! Extend "listen" * scope=HearScope       ->Listen;
! Extend "taste" * scope=TasteScope       ->Taste;
!
! There are two oddities I should mention;  Unlike the other senses, containers
! are by default trasnsparent to sound.  Give them soundproof to reverse this.
! Also, an object that is touchthru or tastethru is also smellthru, as I feel
! this is reasonably close to the way the world works.  If you don't like this,
! have opacity return some huge number for action==##Smell.
!


Attribute smellthru;    !Transparent to smell
Attribute touchthru;    !Transparent to touch eg. has holes in it
Attribute tastethru;    !Transparent to taste
Attribute soundproof;   !Opaque to sound
property feel;          !Response to "Touch" or routine to print one
property scent;         !Same for smell
property sound;         !Same for listen
property taste;         !Same for taste
!These next two can also be properties which return a number
property opacity 1;     !The amount that it obscures whatever's inside it
property MaxSenseDepth 5;  !The maximum opacity at which it can be observed
Global sensedepth;      !The sum of the opacities of the objects between the
                       !player and the thing (s)he is examining

! First, a routine to check whether or not the object can be sensed in the
! needed way

! InSenseScope(thing,Object,Reason) Determines whether Object is in scope of
! Thing by the sense Reason

[ InSenseScope thing obj reason i;
       sensedepth=0;
       i=SenseScope(thing,obj,reason,fl);
       if (sensedepth<=ValueOrRun(obj,MaxSenseDepth)) return i;
       rfalse;
];

! In order for sensedepth to work right, I needed two routines, one to rezero
! sensedepth, and a recursing one to measure it.  DO NOT call SenseScope
! directly, use InSenseScope instead

[ SenseScope thing obj reason o flag i;
       o = parent(obj);
       i = parent(thing);
       if (o==thing) rtrue;
       if (IndirectlyContains(i,obj)==0 && i~=obj) rfalse;
       ! (Just in case a call is made to something that
       ! isn't in the room with the player)
       if (o==i or thing) rtrue;
       if (i==o or thing) rtrue;
       if (o hasnt open)
       sensedepth=sensedepth+ValueOrRun(o,opacity);
       flag = 0;
       switch(reason)
               { ##Touch: if (o has open || o has touchthru) flag=1;
                 ##Smell: if (o has open || o has touchthru || o has smellthru || o has tastethru)
                               flag=1;
                 ##Taste: if (o has open || o has tastethru) flag=1;
                 ##Listen: if (o has open || o hasnt soundproof) flag=1;
                 };
       if (flag==1) return SenseScope(thing,o,reason);
       rfalse;
];

! RecurseSense(obj,sense) recurses through the tree from obj, printing their
! sense response, if they are in sensescope.

[ RecurseSense obj sense o i;
if (parser_trace>=3) print "[Recursing over the ", (name) obj, "]^";
if (ZRegion(obj.sense)==2 or 3 && InSenseScope(player,obj,action)==1
        {PrintOrRun(obj,sense); i=1;};
objectloop (o in obj)
{if (RecurseSense(o,sense)==1) i=1;};
if (i~=0) rtrue;
rfalse;
];

! The new Routines for Touch,Smell,Taste, and Listen

[ TouchSub;
 if (InSenseScope(player,noun,##Touch)==0) return L__M(##Touch,4);
 if (ZRegion(noun.feel)==2 or 3) { PrintOrRun(noun,feel); rtrue;};
 if (noun==player) return L__M(##Touch,3);
 if (noun has animate) return L__M(##Touch,1);
 L__M(##Touch,2); ];
[ SmellSub;
 if (noun==0) <<Smell location>>;
 if (InSenseScope(player,noun,##Smell)==0) return L__M(##Smell,4);
 if (RecurseSense(noun,scent)==1) rtrue;
 L__M(##Smell);
];
[ ListenSub;
 if (noun==0) <<Listen location>>;
 if (InSenseScope(player,noun,##Listen)==0) return L__M(##Listen,4);
 if (RecurseSense(noun,sound)==1) rtrue;
 L__M(##Listen);
];
[ TasteSub;
 if (InSenseScope(player,noun,##Taste)==0) return L__M(##Taste,4);
if (ZRegion(noun.taste)==2 or 3) { PrintOrRun(noun,taste); rtrue;};
L__M(##Taste); ];


! Scope-Checking routines in case you want to extend verb grammar

[ TouchScope i;
       if (scope_stage==1) rfalse;
       if (scope_stage==2) { for(i=selfobj+1:i<=top_object:i++)
       if (InSenseScope(player,i,##Touch)==1) PlaceInScope(i);};
];
[ SmellScope i;
       if (scope_stage==1) rfalse;
       if (scope_stage==2) { for(i=selfobj+1:i<=top_object:i++)
       if (InSenseScope(player,i,##Smell)==1) PlaceInScope(i);};
];
[ HearScope i;
       if (scope_stage==1) rfalse;
       if (scope_stage==2) { for(i=selfobj+1:i<=top_object:i++)
       if (InSenseScope(player,i,##Listen)==1) PlaceInScope(i);};
];
[ TasteScope i;
       if (scope_stage==1) rfalse;
       if (scope_stage==2) { for(i=selfobj+1:i<=top_object:i++)
       if (InSenseScope(player,i,##Taste)==1) PlaceInScope(i);};
];