!==============================================================================
! autodoor.h -- implements an autodoor class which allows the use of
! autoopening and autounlocking doors (including testing whether the
! appropriate key is in scope).  Also includes functionality of L. Ross
! Raszewski's doors.h for easy door definition.
!
! $Id$
!
! Version 1.0 (Feb04) by Michael Coyne (coyne@mts.net)
! 
! Compatibility: for Inform 6.3 (Zcode v5/8 and Glulx).
!
! Dependencies: requires infglk.h (for Glulx), and auto-includes it as needed.
!
! License: The Creative Commons Attribution-NonCommercial-ShareAlike License
! (http://creativecommons.org/licenses/by-nc-sa/1.0/) applies to this software.
! In summary: you must credit the original author(s); you may not use the
! software for commercial purposes; if you alter, transform, or build upon this
! software, you may distribute the SOURCE FORM of the resulting work only under
! a license identical to this one. Note that the ShareAlike clause does not
! affect the way in which you distribute the COMPILED FORM of works built upon
! this software. Copyright remains with the original author(s), from whom you
! must seek permission if you wish to vary any of the terms of this license.
!
! ----------------------------------------------------------------------------
! INSTALLATION:
!
! Place these lines anywhere after VerbLib.h:
!
!   Constant NO_AUTOTEXT;   ! optional: disables autotext (see Note 2)
!   Constant NO_AUTO_OPEN;   ! optional: disables door autoopening (see Note 3)
!   Constant NO_AUTO_UNLOCK; ! optional: disables door autounlock (see Note 3)
!   Constant NO_LOOK_THROUGH;! optional: disables look through (see Note 5)
!   Constant MAX_LOCATION_LENGTH = <some-number>;  ! optional (see Note 6)
!
!   Include "autodoor";
!
! If you also wish to include smart "lock door" and "unlock door"
! functionality, then issue a second:
!
!   Include "autodoor";
!
! after including Grammar.h.  This will define the following verb definitions:
!
!   Extend  'lock'  first
!       * noun                  -> Lock;    ! optional (see Note 4)
!   Extend  'unlock'    first
!       * noun                  -> Unlock;  ! optional (see Note 4)
!
! ----------------------------------------------------------------------------
! USAGE:
! (paraphrased from L. Ross Raszewski's original doors.h notes, below)
!
! Create a basic door like this:
!
!   Autodoor door "door"
!       with name "door",
!           e_to roomA,
!           w_to roomB,
!           auto_open false; ! disable autoopening for this door (see Note 3)
!
! This simple syntax is equivalent to the regular Inform syntax:
!
!   Object door "door"
!       with name 'door',
!           door_to [; if (location == roomA) return roomB; return roomA;],
!           door_dir [; if (location == roomB) return e_to; return w_to;],
!           found_in roomA roomB,
!   has openable door static;
!
! This, obviously, saves a lot of typing and will, in the long run, cut down
! on door-related bugs.
!
! ----------------------------------------------------------------------------
! NOTES:
!
! 1.    DOOR BIAS:
!       In locations with multiple doors, commands such as OPEN DOOR will
!       parse to the correct door if only one of them is CLOSED. Likewise for
!       CLOSE, LOCK and UNLOCK.
!
! 2.    NO_AUTOTEXT:
!       This module generates "appropriate levels of detail" messages when
!       stepping through the doors, e.g.:
!           "You pass through the open doorway into the library."
!           "You open the huge double doors and pass through into the library."
!           "You unlock the huge double doors with the large steel key, open
!           them, and pass through into the library."
!       These may be disabled globally by defining NO_AUTOTEXT, or on a
!       door-by-door basis by setting autotext to false for specific doors
!       (note the lack of underscore in the individual property).  The
!       drawback to disabling this is you will get the standard Inform
!       messages such as "You open the door.". This means that when auto-opening
!       is in use, the fact that the player has opened *and* walked through
!       the door will not be conveyed.
!   
!       If you have locations such as Foo's Bar, you should give the
!       location the 'proper' attribute, so that the class can correctly state:
!           "You open the glass-fronted door and step into Foo's Bar."
!       rather than:
!           "You open the glass-fronted door and step into the Foo's Bar."
!
! 3.    NO_AUTO_UNLOCK, NO_AUTO_OPEN:
!       These may be disabled globally by defining NO_AUTO_UNLOCK and
!       NO_AUTO_OPEN, or on a door-by-door basis by setting auto_open or
!       auto_unlock to false for specific doors.
!
! 4.    (UN)LOCK DOOR:
!       This is optional functionality which allows the player to type
!           UNLOCK DOOR or LOCK DOOR
!       without specifying a key, and have the Autodoor class search for a
!       matching key in scope (and picking it up if necessary).
!
!       Some people may not like this, preferring to force the player to check
!       all keys himself.  Leaving out the second inclusion of autodoor.h
!       will make >(UN)LOCK DOOR go through the normal "What do you want to
!       (un)lock the door with?" procedure.
!
!       Note that even without these verb definitions, attempting to walk
!       through the locked door will still make the Autodoor class search for
!       a matching key automatically (unless NO_AUTO_UNLOCK is defined).
!
! 5.    LOOK_THROUGH:
!       This is optional functionality which allows the player to
!       "look through" doors to the room beyond.  Not much detail other than
!       the name of the room beyond is provided.  This functionality can be
!       disabled globally by defining NO_LOOK_THROUGH or individually by
!       setting look_through to false for a given door object (note the
!       lack of underscore in the individual property).
!
! 6.    MAX_LOCATION_LENGTH:
!       If you have location names longer than the default 50 characters, and
!       are using the AUTOTEXT or LOOK_THROUGH feature (see Notes 2 & 5), you
!       should define:
!           Constant MAX_LOCATION_LENGTH = <larger-number>;
!       in your code before including this module.  Otherwise the lowercase
!       routine will truncate the names at MAX_LOCATION_LENGTH.
!
! ---------------------------------------------------------------------------- !
! ORIGINAL DOORS.H NOTES
! Instructions on creating a door using this module
! (Ross's words, taken from doors.h, with minor changes):
! Suppose you need a door between roomA and roomB, where roomA is to the east 
! of roomB.
!
!
!  Autodoor door "door"
!      with name "door",
!           e_to roomA,
!           w_to roomB,
!
! Voila!  This simple syntax is equivalent to the conventional syntax:
!
! Object door "door"
!      with name "door",
!           door_to [; if (location == roomA) return roomB; return roomA;],
!           door_dir [; if (location == roomB) return e_to; return w_to;],
!           found_in roomA roomB;
!      has openable door static;
!
! I can hear you now.  "What?  I save three lousy lines?"  Well, yeah, but in
! a game with a lot of doors, you've saved yourself some syntax. And it 
! certaintly is more intuitive.
! 
! ---------------------------------------------------------------------------- !
! end of instructions taken from L. Ross Raszewski's doors.h
!
!==============================================================================

system_file;

#Ifdef _AUTODOOR_H_2;
Message warning "AUTODOOR already Included.";
#Ifnot;
#Ifdef _AUTODOOR_H_;
Constant _AUTODOOR_H_2 10;
#Ifnot;
Constant _AUTODOOR_H_ 10;
#Ifdef DEBUG; Message "[Including AUTODOOR]"; #Endif;
#Iffalse	LIBRARY_STAGE >= AFTER_GRAMMAR;	! if grammar is not yet defined, this bit is good.
! ---------------------------------------------------------------------------- !

Default period = ".";
Default MAX_LOCATION_LENGTH = 50;

! used to lowercase the names of locations.
! leave room for length word at start of buffer.
Array   Case_Array buffer (MAX_LOCATION_LENGTH + WORDSIZE);

! ---------------------------------------------------------------------------- !

#Ifdef Ad_Item;
Class   Autodoor class Ad_Item class Askable
#Ifnot;
Class   Autodoor
#Endif;
    with    name 'door',
            description [;
                if (self has pluralname)
                    print "These ";
                else
                    print "This ";
                print (name)self, " leads to ";
                if (self.door_to() hasnt proper)
                    print "the ", (ToLower)self.door_to();
                else
                    print (name)self.door_to();
                print (string)period;
                if (self has pluralname)
                    print " They are";
                else print " It is";
                print " currently ";
                if (self has open) print "open";
                else print "closed";
                print_ret (string)period;
            ],
#Ifdef Ad_Item;
            parse_name [num;
                num = AdnameParser(self);        ! Calling another routine like this
                if (num == 0) return num;
                if (action_to_be == ##Open && self hasnt open)
                  num = num + 10;
                else if (action_to_be == ##Close && self has open)
                  num = num + 10;
                else if (action_to_be == ##Unlock && self has locked)
                  num = num + 10;
                else if (action_to_be == ##Lock && self hasnt locked)
                  num = num + 10;
#Ifnot;
            parse_name [wd num;
                wd = NextWord();
                while (WordInProperty(wd, self, name)) {
                    if (action_to_be == ##Open && self hasnt open)
                        num = num + 10;
                    else if (action_to_be == ##Close && self has open)
                        num = num + 10;
                    else if (action_to_be == ##Unlock && self has locked)
                        num = num + 10;
                    else if (action_to_be == ##Lock && self hasnt locked)
                        num = num + 10;
                    else
                        ++num;
                    wd = NextWord();
                }
#Endif;
                return num;
            ],
            door_dir [;
                if (real_location == self.sidea)
                    return self.dira;
                return self.dirb;
            ],
            door_to [;
                if (real_location == self.sidea)
                    return self.sideb;
                return self.sidea;
            ],
            ! initialise sides to 0
            sidea 0,
            dira 0,
            sideb 0,
            dirb 0,
            auto_open true,
            auto_unlock true,
            look_through true,
            autotext true,
            found_in 0 0,
            react_before [;
              !TODO: for illum only, remove before publishing.
              if (globalvision == true)
                rfalse;
              Go:
                if (noun.door_dir() == self.door_dir())
                  return self.AutoOpen();
              Enter:
                if (noun == self)
                  return self.AutoOpen();
              Lock:
                if (noun == self && self.auto_unlock == true)
                  return self.AutoLock();
              Unlock:
                if (noun == self && self.auto_unlock == true)
                  return self.AutoUnlock();
            ],
            before [;
                Search:
                    if (self.look_through == false) rfalse;
                    if (self has open) {
                      print "Looking through ", (the) noun, " ";
                      if (self.door_dir() == u_to)
                        print "above";
                      else if (self.door_dir() == d_to)
                        print "below";
                      else {
                        print "to the ";
                        LanguageDirection(self.door_dir());
                      }
                      print ", you see the ";
                        if (self.door_to() hasnt proper)
                            print (ToLower)self.door_to();
                        else
                            print "interior of ", (name)self.door_to();
                        print_ret ", but you'll have to go through to make out
                            any further details.";
                    }
                    <<Examine self>>;
            ],
            AutoLock [ks_save;
                if (self hasnt lockable)
                    print_ret (The) self, " ", (isorare)self, " not the locking kind.";
                if (self has locked) {
                    print_ret (The) self, " is already locked.";
                }
                if (second ~= nothing) {
                    if (second ~= self.with_key)
                        print_ret (The) second, " doesn't seem to fit the lock of ", (the) self, (string)period;
                }
                ! This is a special case to handle things like deadbolt doors that can be locked or unlocked without a key
                if (self.with_key == 0) {
                    if (self has open) {
                        print "(first closing ", (the) self, ")^";
                        ks_save = keep_silent;
                        keep_silent = true;
                        <Close self>;
                        ! restore state of keep_silent
                        keep_silent = ks_save;
                    }
                    ! if it's still open, return false
                    if (self has open) rfalse;
                    ! now lock the door
                    give self locked;
                    print_ret "You lock ", (the) self, " by turning the deadbolt.";
                }
                if (TestScope(self.with_key)) {
                    if (self.with_key notin player) {
                        print "(first taking ", (the) self.with_key, ")^";
                        <Take self.with_key>;
                        }
                    ! if key wasn't successfully taken, we'll have to abort
                    if (self.with_key notin player) rfalse;
                    if (self has open) {
                        print "(first closing ", (the) self, ")^";
                        ks_save = keep_silent;
                        keep_silent = true;
                        <Close self>;
                        ! restore state of keep_silent
                        keep_silent = ks_save;
                    }
                    ! if it's still open, return false
                    if (self has open) rfalse;
                    ! now lock the door
                    give self locked;
                    print_ret "You lock ", (the) self, " with ",
                        (the)self.with_key, (string) period;
                }
                else
                    "You don't have a key that fits the lock of ", (the) self, (string) period;
            ],
            AutoUnlock [;
                if (self hasnt lockable) {
                    print_ret (The) self, " ", (isorare)self, " not the locking kind.";
                }
                if (self hasnt locked) {
                    "But ", (the) self, " ", (isorare)self, " already unlocked!";
                }
                if (second ~= nothing) {
                    if (second ~= self.with_key)
                        print_ret (The) second, " doesn't seem to fit the lock of ", (the) self, (string)period;
                }
                ! This is a special case to handle things like deadbolt doors that can be locked or unlocked without a key
                if (self provides with_key && self.with_key == 0) {
                  give self ~locked;
                  print_ret "You unlock ", (the) self, " by turning the deadbolt.";
                }
                else if (TestScope(self.with_key)) {
                    if (self.with_key notin player) {
                        print "(first taking ", (the) self.with_key, ")^";
                        <Take self.with_key>;
                        }
                        ! if key wasn't successfully taken, we'll have to abort
                        if (self.with_key notin player) rfalse;
                        give self ~locked;
                        print_ret "You unlock ", (the) self, " with ",
                            (the)self.with_key, (string) period;
                }
                else
                    "You don't have a key that fits the lock of ",
                    (the) self, (string) period;
            ],
            AutoOpen [ks_save;
                ! do this here rather than later, to avoid compiler warnings
                ! when using NO_AUTO_OPEN feature
                ks_save = keep_silent;
                if (self has open) {
                    if (self.autotext == false) rfalse;
                    print "You pass through the open ", (name)self, " into ";
                    if (self.door_to() hasnt proper)
                        print "the ", (ToLower)self.door_to();
                    else
                        print (name)self.door_to();
                    print (string) period;
                    new_line;
                    rfalse;
                }
                ! if NO_AUTO_OPEN is defined, and the door isn't open, don't
                ! open it, just rfalse and let standard processing happen
                if (self.auto_open == false) rfalse;
                if (self has locked) {
                    ! if auto_unlock is false, and the door is locked, don't
                    ! unlock it. Just rfalse and let standard processing
                    ! take place
                  if (self.auto_unlock == false) rfalse;
                  ! This is a special case to handle things like deadbolt doors that can be locked or unlocked without a key
                  if (self provides with_key && self.with_key == 0) {
                    if (self.autotext == true) {
                        print "You unlock ", (the) self, " by turning the deadbolt, open ", (itorthem) self, " and pass through into ";
                            if (self.door_to() hasnt proper)
                                print "the ", (ToLower)self.door_to();
                            else
                                print (name)self.door_to();
                            print (string) period;
                            new_line;
                        }
                        give self ~locked;
                  }
                  else if (TestScope(self.with_key)) {
                      if (self.with_key notin player) {
                        print "(first taking ", (the) self.with_key, ")^";
                        <Take self.with_key>;
                      }
                      ! if key wasn't successfully taken, we'll have to abort
                      if (self.with_key notin player) rfalse;
                      if (self.autotext == true) {
                        print "You unlock ", (the) self, " with ", (the)self.with_key, ", open ", (itorthem) self, " and pass through into ";
                        if (self.door_to() hasnt proper)
                          print "the ", (ToLower)self.door_to();
                        else
                          print (name)self.door_to();
                        print (string) period;
                        new_line;
                      }
                      give self ~locked;
                    }
                    else {
                        ! door locked, key not in scope
                        if (self.autotext == false) rfalse;
                        print "You attempt to open ", (the) self, " and pass
                            through nonchalantly, but ";
                        if (self has pluralname)
                            print "they ";
                        else
                            print "it ";
                        print_ret (isorare) self, " locked.";
                    }
                }
                else {
                    ! door is not locked
                    if (self.autotext == true) {
                        print "You open ", (the) self, " and pass through
                            into ";
                        if (self.door_to() hasnt proper)
                            print "the ", (ToLower)self.door_to();
                        else
                            print (name)self.door_to();
                        print (string) period;
                        new_line;
                    }
                }
                ! set keep_silent to true, unless we're in NO_AUTOTEXT mode
                if (self.autotext == true)
                    keep_silent = true;
                <Open self>;
                ! restore state of keep_silent
                keep_silent = ks_save;
                if (self hasnt open) rtrue;
            ],
    has     scenery door openable;
! ---------------------------------------------------------------------------- !
! LibraryExtensions object to handle initialising doors

Object "(Autodoors)"    LibraryExtensions
    with    ext_initialise [ o i j;
                objectloop (j ofclass Autodoor) { 
                    objectloop (o in compass) {
                        i=o.door_dir;
                        if (j provides i) {
                            j.sidea=j.i;
                            j.dirb=i;
                        };
                    };
                    objectloop (o in compass) {
                        i=o.door_dir;
                        if ((j provides i) && (j.dirb~=i)) {
                            j.sideb=j.i;
                            j.dira=i;
                        };
                    };
                    j.&found_in-->0=j.sidea;
                    j.&found_in-->1=j.sideb;
                    #Ifdef NO_AUTO_UNLOCK;  j.auto_open = false,
                    #Ifnot;                 j.auto_open = true;     #Endif;
                    #Ifdef NO_AUTO_UNLOCK;  j.auto_open = false;
                    #Ifnot;                 j.auto_open = true;     #Endif;
                    #Ifdef NO_AUTO_UNLOCK;  j.auto_unlock = false;
                    #Ifnot;                 j.auto_unlock = true;   #Endif;
                    #Ifdef NO_LOOK_THROUGH; j.look_through = false;
                    #Ifnot;                 j.look_through = true;  #Endif;
                    #Ifdef NO_AUTOTEXT;     j.autotext = false;
                    #Ifnot;                 j.autotext = true;      #Endif;
                };
            ];
                
! ---------------------------------------------------------------------------- !
[ToLower word i j;

    j = PrintToBuffer(Case_Array, MAX_LOCATION_LENGTH, word);
    for (i = WORDSIZE:(i < (j + WORDSIZE))
        && (i < MAX_LOCATION_LENGTH + WORDSIZE): i++) {
        Case_Array->(i) = LowerCase(Case_Array->(i));
        print (char) Case_Array->(i);
    }

];

#Endif; ! LIBRARY_STAGE >= AFTER_GRAMMAR
#Endif; ! _AUTODOOR_H_

#Iftrue 	LIBRARY_STAGE >= AFTER_GRAMMAR;	! if grammar is defined, this bit is good.
Extend	'lock'	first
    * noun					-> Lock;
Extend	'unlock'	first
    * noun					-> Unlock;
#Endif; ! LIBRARY_GRAMMAR

#Endif; ! _AUTODOOR_H_2
