attachables.t

documentation
#charset "us-ascii"
#include "advlite.h"

/*
 *   ***************************************************************************
 *   attachables.t
 *
 *   This module forms part of the adv3Lite library (c) 2012-13 Eric Eve
 *
 *
 *
 *   The attachables file provides a framework for simple and common case
 *   attachables.
 *
 *   It treats attachment as an asymmetric relationship. ATTACH A TO B is not
 *   the same as ATTACH B TO A. It also treats it as a many-to-one relationship:
 *   the object that is attached can only be attached to one object at a time,
 *   but the object it's attached to can have multiple objects attached to it.
 *
 */

/* 
 *   A SimpleAttachabe is a Thing that can be attached to other things or have
 *   other things attached to it in the special case in which the attached
 *   objects are located in the object to which they are attached.
 *   SimpleAttachable is also the base class for other types of attachable.
 */
class SimpleAttachable: Thing
    
    /* 
     *   Can an object be attached to this one? By default we return true if obj
     *   is on our list of allowableAttachments.
     */
    allowAttach(obj)
    {
        return valToList(allowableAttachments).indexWhich(
            {o: o.ofKind(obj) != nil});
    }
    
    /* A list of the objects or classes of objects that can be attached to me */
    allowableAttachments = []
    
    /* A list of the objects that are attached to me */
    attachments = []
    
    /* A SimpleAttachable can be attached to (some) other things. */
    isAttachable = true
    
    /* A SimpleAttachable can have (some) other things attached to it */
    canAttachToMe = true
    
    /* A SimpleAttachable can have (some) other things detached from it */
    canDetachFromMe = true    
    
    /* The list of things I'm currentl attached to */
    attachedToList = []
    
    /* The object, if any, I'm currently attached to*/
    attachedTo = (attachedToList == [] ? nil : attachedToList[1])
    
    /* 
     *   The object, if any, to which I'm initially attached (if I start out attached to anything).
     *   If this is defined, it should be another SimpleAttachable or subclass thereof.
     */
    initiallyAttachedTo = nil
    
    
    /* 
     *   The location this object should be moved to when it's attached to
     *   another object. A SimpleAttachment should normally be moved into the to
     *   which it's attached.
     */
    attachedLocation = (attachedTo)
    
    /*   
     *   The location this object should be moved to when it's detached. A
     *   SimpleAttachment should normally be moved into the location of the
     *   object it's just been detached from.
     */
    detachedLocation = (attachedTo.location)
    
    /* Attach this object to obj */
    attachTo(obj)
    {
        /* Note that we're now attached to the iobj of the attach action */
        makeAttachedTo(obj);         
        
        /* Add us to the iobj's list of attachments. */
        obj.attachments = obj.attachments.appendUnique([self]);
        
        /* 
         *   If we're already in our attached location, there's no need to move,
         *   otherwise move us into our attached location.
         */
        if(location != attachedLocation)
            actionMoveInto(attachedLocation);
    }
    
    /* Add the object we're now attached to to our attachedToList */
    makeAttachedTo(obj)
    {
        attachedToList = attachedToList.appendUnique([obj]);
    }
    
    /* Detach this item from obj */
    detachFrom(obj)
    {
        /* If we're not attached to obj, then there's nothing to do */
        if(!isAttachedTo(obj))
            return;
        
        /* If we're not already in our detachedLocation, move us there. */
        if(location != detachedLocation)
            moveInto(detachedLocation);
        
        /* 
         *   Remove us from the list of attachements of the object to which we
         *   were formerly attached.
         */
        obj.attachments -= self;
        
        /* Note that we're no longer attached to obj. */
        makeDetachedFrom(obj);
    }
    
    /* FRemove obj the list of objeccts attached to us */
    makeDetachedFrom(obj)
    {
        attachedToList -= obj;
    }
    
    
    /*  Handling for the ATTACH TO action */
    dobjFor(AttachTo)
    {
        preCond = [objHeld]
        
        verify()
        {
            /* Carry out the inherited handling. */
            inherited; 
            
            /* We can't be attached while we're already attached */
            if(attachedTo == gVerifyIobj)
                illogicalNow(alreadyAttachedMsg);
            else if(attachedTo != nil)
                illogicalNow(cannotAttachToMoreMsg);
        }
        
        action()
        {            
            attachTo(gIobj);
        }
        
        report()
        {
            say(okayAttachMsg);
        }
    }
    
   
    
    
    okayAttachMsg = BMsg(okay attach, '{I} attach{es/ed} {1} to {the iobj}. ',
                         gActionListStr) 
    
    alreadyAttachedMsg = BMsg(already attached, '{The subj dobj} {is} already
        attached to {1}. ', attachedTo.theName)
    
    cannotAttachToMoreMsg = BMsg(cannot attach to more, '{I} {can\'t} attach
        {the dobj} to anything else while {he dobj}{\'s} attached to {1}. ',
                                 makeListStr(attachedToList, &theName))      
    
    iobjFor(AttachTo)
    {
        preCond = [touchObj]
        
        verify()
        {
            /* Carry out the inherited handling. */
            inherited;
            
            /* 
             *   If we don't allow the dobj to be attached to us, rule out the
             *   attachment.
             */
            if(!allowAttach(gVerifyDobj))
                illogical(cannotBeAttachedMsg);
        }
    }
    
    notAttachedMsg = BMsg(not attached, '{The subj dobj} {is}n\'t attached to
        anything. ')
    
    dobjFor(Detach)
    {
        verify()
        {                   
            /* We can't be detached if we're not attached to anything */
            if(attachedToList.length == 0)
                illogicalNow(notAttachedMsg);           
        
            /* We can't be detached if we're not detachable */
            else if(!isDetachable)
                implausible(cannotDetachMsg);
        }
        
        action()
        { 
            for(local cur in attachedToList)
               detachFrom(cur);                   
        }
        
        report()   {  say(okayDetachMsg);   }
        
    }
    
    okayDetachMsg = BMsg(okay detach, '{I} detach{es/ed} {1}. ', gActionListStr)
    
    
    dobjFor(DetachFrom)
    {
        verify()
        {
            /* 
             *   We can't be detached from anything if we're not attached to
             *   anything.
             */
            if(attachedToList.length == 0)
                illogicalNow(notAttachedMsg);  
            
            /* We can't be detached from the iobj if we're not attached to it */
            else if(!isAttachedTo(gVerifyIobj))
                illogicalNow(notAttachedToThatMsg);
            
            /* Carry out the inherited handling. */
            inherited;
        }
        
        
        
        action()
        {
            detachFrom(gIobj);            
        }
        
        report()
        {
            say(okayDetachFromMsg);            
        }
    }
    
    okayDetachFromMsg = BMsg(okay detach from, '{I} detach{es/ed} {1} from
        {the iobj}. ',  gActionListStr)
    
    cannotDetachMsg = BMsg(cannot detach this, '{The subj dobj} {cannot} be
        detached from {1}. ', location.theName)
    
    iobjFor(DetachFrom)
    {
        verify()
        {
            /* 
             *   Since we resolve the iobj first we can't tell whether the dobj
             *   is attached, but we can check whether anything at all is
             *   attached to this object.
             */
            if(attachments.length == 0)
                illogicalNow(nothingAttachedMsg);
            
            /* 
             *   Otherwise we can check whether our list of attachments overlaps with the list of
             *   tentative direct objects; if it does, we're probably a good choice of indirect
             *   object.             
             */
            
            else if(attachments.overlapsWith(gTentativeDobj))
                logicalRank(120);
            
                
            /* Carry out the inherited handling */
            inherited;
                
        }
    }
    
    cannotDetachFromMsg = BMsg(cannot detach from this , '{The {ubj dobj}
        {can\'t} be detached from {the iobj}. ')
    
    notAttachedToThatMsg = BMsg(not attached to that, '{The subj dobj} {isn\'t}
        attached to {the iobj}. ')
    
    nothingAttachedMsg = BMsg(nothing attached, 'There {dummy} {isn\'t} anything
        attached to {the iobj}. ')

    /* Treat Fasten and Unfasten as equivalent to Attach and Detach */
    dobjFor(FastenTo) asDobjFor(AttachTo)
    iobjFor(FastenTo) asIobjFor(AttachTo)
    dobjFor(UnfastenFrom) asDobjFor(DetachFrom)
    iobjFor(UnfastenFrom) asIobjFor(DetachFrom)
    dobjFor(Unfasten) asDobjFor(Detach)
    
    /* 
     *   We can't take this object while it's attached. Note that this is
     *   assymetric: it applies only to the attached object (the one for which
     *   attachedTo != nil) not to the object it's attached to (which can be
     *   taken with the attached object still attached to it.
     */    
    dobjFor(Take)
    {
        preCond = [objDetached]
    }
                                
 
    /* 
     *   Check while any of my attachments object to my being moved while they
     *   are attached to me. If so, prevent the attempt to move me as the result
     *   of a player command.
     */        
    actionMoveInto(loc)
    {
       /* 
        *   See if there's an object among our attachments that objects to our
        *   being moved while we're attached to it.
        */
          
        local other = attachments.valWhich({o:
                                           !o.allowOtherToMoveWhileAttached});
        
        /* 
         *   If there is, display a message saying we can't be moved while the
         *   object object is attached, then stop the action. 
         */
        if(other != nil)
        {            
            sayCannotMoveWhileAttached(other);
            exit;
        }
        
        
        
        /* Carry out the inherited handling */   
        inherited(loc);
    }
    
    /* 
     *   Display a message saying we can't be moved while we're attached to
     *   other.
     */
    sayCannotMoveWhileAttached(other)
    {
        gMessageParams(other);            
        DMsg(cannot move while attached, '{The subj cobj} {cannot} be moved
            while {he cobj} {is} attached to {the other}. ');   
    }
    
    
    cannotBeAttachedMsg = BMsg(cannot be attached, '{The subj dobj} {cannot} be
        attached to {the iobj}. ')
    
    
    
    /* 
     *   By default I'm not listed as a separate object if I'm attached to
     *   something else.
     */
    isListed = (attachedTo == nil && inherited)
    
    /* 
     *   Is obj attached to me? By default it is if it's in my list of
     *   attachements.
     */
    isAttachedToMe(obj)
    {
        return attachments.indexOf(obj) != nil;
    }
    
    
    /* 
     *   Is there an attachment relationship between myself and obj; there is
     *   either is obj is attached to me or if I'm attached to obj.
     */
    isAttachedTo(obj)
    {
        return isAttachedToMe(obj) || attachedTo == obj;
    }
    
    
    /* Our locType is Attached if we're attached so something. */
    locType()
    {
        if(attachedTo != nil)
            return Attached;
        else
            return inherited;
    }
    
    /* 
     *   If anything's attached to us, list our attachements when we're
     *   examined.
     */    
    examineStatus()
    {
        /* Carry out the inherited handling. */
        inherited;
        
        /* List our attachments, if we have any. */
        if(attachments.length > 0)
            attachmentLister.show(attachments, self);
    }
    
    /* The lister to be used for listing our attachments. */
    attachmentLister = simpleAttachmentLister
    
    /* 
     *   If I'm attached, do I become firmly attached (so that I can't be
     *   removed without an explicit detachment)?
     */
    isFirmAttachment = true
    
    /*   
     *   Allow detachment through a simple DETACH command. (If this is nil
     *   detachment might still be programatically possible, e.g. by UNSCREWing
     *   something).
     */    
    isDetachable = true
    
    /*   
     *   Determine whether the object I'm attached to can be moved while I'm
     *   attached to it. For a SimpleAttachable we normally do allow this, since
     *   I simply move with the other object.
     */    
    allowOtherToMoveWhileAttached = true
    
 
    /* Preinitialize a SimpleAttachable */
    preinitThing()
    {
        /* carry out the inherited handling */
        inherited();
        
        /* if I'm attached to anything, add me to its attachment list */
        if(initiallyAttachedTo != nil)
        {
            /* 
             *   Ensure that the object we're attached to has an attachements property defined as a
             *   list so we don't get a runtime error if our initiallyAttachedTo object isn't a
             *   SimpleAttachable, although it should be.)
             */
            if(initiallyAttachedTo.attachments == nil)
                initiallyAttachedTo.attachments = [];
            
            /* Attach us to the other object. */
            attachTo(initiallyAttachedTo);          
        }
    }
;

/* 
 *   An AttachableComponent is an item that effectively becomes a component of
 *   the object it's attached to, or is treated as a component if it starts out
 *   attached.
 */
class AttachableComponent: SimpleAttachable
   
    /* A Component is firmly attached to its parent. */
    isFirmAttachment = true
    
    /* A Component's locType is PartOf while it's attached */
    locType()
    {
        if(attachedTo != nil)
            return PartOf;
        else
            return inherited;
    }
    
    /* Preinitialize a Component. */
    preinitThing()
    {
        /* Carry out the inherited handling. */
        inherited Thing;
        
        /* 
         *   If we start out initiallyAttached, note that we're attached to our
         *   location and add us to our location's list of attachments.
         */
        if(initiallyAttached)
        {
            /* A Component is attached to its immediate location. */
            attachedToList = [location];
            
            /* 
             *   It's possible that we're a component of something that isn't an
             *   Attachable and doesn't provide the attachments property; in
             *   which case, initialize its attachments property now.
             */
            if(location.attachments == nil)
                location.attachments = [];
            
            /* Add ourselves to our locations list of attachments. */
            location.attachments = location.attachments.appendUnique([self]);
        }
    }
    
    /* 
     *   We can't normally detach a Component with a straightforward DETACH
     *   command.
     */
    isDetachable = nil
    
    /* 
     *   A Component is generally fixed in place (i.e. not separately takeable)
     *   if it's attached to something.
     */
    isFixed = (attachedTo != nil)
    
    /* 
     *   Assume that most components start out attached to their containers
     */    
    initiallyAttached = true
    
       
    cannotTakeMsg = (delegated Component) 
    
;

/* 
 *   A NearbyAttachable is (optionally) placed in the same location as the
 *   object to which it is attached, and moves with the object it's attached to
 *   (or, alternatively, can prevent the other object being moved while it's
 *   attached to it).
 */
class NearbyAttachable: SimpleAttachable
    
    /* 
     *   Our location when attached; by default this is the location of the item
     *   we're attached to (if there is one)
     */
    attachedLocation = (attachedTo == nil ? location : attachedTo.location)
    
    
    /*  
     *   Our location when detached; by default this is simply the location of
     *   the item to which we're attached, if there is one.
     */         
    detachedLocation = (attachedTo == nil ? location : attachedTo.location)
    
    /* 
     *   Before any action takes place when we're in scope, make a note of our
     *   current location if we're attached to anything
     */
    beforeAction()
    {
        if(attachedTo != nil)
            oldLocation = location;
    }
    
    /*  
     *   After any action takes place when we're attached to something, move us
     *   into our attachedLocation if we're not already there.
     */
    afterAction()
    {
        if(attachedTo != nil && attachedLocation != oldLocation)
            moveInto(attachedLocation);
    }
    
    /* 
     *   Our location just before an action takes place when we're attached to
     *   something.
     */
    oldLocation = nil
;

/* 
 *   An Attachable is a NearbyAttachable that can be attached to more than one
 *   thing at a time, like a length of cable connecting two devices.
 */
class Attachable: NearbyAttachable
    
    /* 
     *   The maximum number of things I can be attached to at once. By default
     *   this is 2, since probably the most commonly use for an Attachable is to
     *   join or link two other things, but this can easily be overridden as
     *   required. If you want there to be no limit to the number of things I
     *   can be attached to at once, make maxAttachedTo nil.
     */
    maxAttachedTo = 2
   
    
    /* 
     *   Since an ATTACHABLE could potentially be in a many-to-many
     *   relationship, we may sometimes want to control the order of connection
     *   (i.e. which is considered to be the connected object and which the
     *   object it's connected to). If reverseConnect(obj) returns true then
     *   we'll turn CONNECT obj TO self into CONNECT self TO obj.
     */
    reverseConnect(obj)
    {
        return nil;
    }
    
    /*   
     *   We allow attachment to another obj either for the inherited reason
     *   (that obh is in my list of allowable attachements) or, if we want to
     *   reverse the connection with obj, that obj can be attached to us.
     */
    allowAttach(obj)
    {
        return inherited(obj) 
            || (reverseConnect(obj) && obj.allowAttach(self));
    }
    
    dobjFor(AttachTo)
    {
        preCond = [touchObj]
        
        verify()
        {
            inherited Thing;  
            
            if(isAttachedTo(gVerifyIobj))
            {                            
                illogicalNow(alreadyAttachedToIobjMsg);
            }
            
            else if(maxAttachedTo != nil && attachedToList.length >= maxAttachedTo)
                illogical(cannotAttachToMoreMsg);
        }       
        
    }        
   
                      
    
    alreadyAttachedToIobjMsg = BMsg(alreadt attached to iobj, '{The subj dobj} {is} already
        attachd to {1}. ', gVerifyIobj.theName) 
    
    /* 
     *   I'm attached to obj in the broad sense of having an attachement
     *   relationship with obj either if obj is attached to me or if obj is in
     *   the list of things to which I'm attached.
     */
    isAttachedTo(obj)
    {
        return isAttachedToMe(obj) || attachedToList.indexOf(obj);
    }
    
    /* 
     *   By default an Attachable can be plugged into more than one thing at a
     *   time
     */
    multiPluggable = true
;

reverseAttachableDoer: Doer 'attach SimpleAttachable to Attachable'
    execAction(c)
    {
        if(gIobj.reverseConnect(gDobj))
            doInstead(Attach, gIobj, gDobj);
        else
            inherited(c);            
    }    
;


/* 
 *   PlugAttachable is a mix-in class for use in conjunction with either
 *   SimpleAttachable or NearbyAttachable, enabling the commands PLUG X INTO Y,
 *   UNPLUG X FROM Y, PLUG X IN and UNPLUG X, treating ATTACH and DETACH
 *   commands as equivalent to these, and describing an object's attachments as
 *   being plugged into it.
 */
class PlugAttachable: object
    
    /* A PlugAttachable can be plugged into things. */
    isPlugable = true
    
    /* A PlugAttachable can have other things plugged into it. */
    canPlugIntoMe = true
    
    /* 
     *   Objects attached to this object should be described as plugged into it,
     *   so we need to use the appropriate lister.
     */
    attachmentLister = plugAttachableLister
    
    /*   
     *   Plugable objects could either be implemented so that an explicit socket
     *   needs to be specified (e.g. PLUG CABLE INTO SOCKET) or so that the
     *   socket can be left unspecified (e.g. PLUG TV IN). For the former case,
     *   make this property true; for the latter, make it nil.
     */
    needsExplicitSocket = true
    
    /*   Is this item plugged in to anything? */
    isPluggedIn = nil    
    
    /*   
     *   If this object represents the socket side of a plug-and-socket
     *   relationship, then the socketCapacity defines the total number of items
     *   that can be plugged into it once. By default we'll assume that a socket
     *   can only have one thing plugged into it at a time, but this can readily
     *   be overridded for items that can take more.
     */
    socketCapacity = 1
    
    /* Note whether we're plugged our unplugged. */
    makePlugged(stat)
    {
        isPluggedIn = stat;
    }
    
    dobjFor(PlugInto)    
    {
        verify()
        {
            /* Carry out the inherited handling. */
            inherited;
            
            /* We can't be plugged in if we're already plugged in. */
            if(isPluggedIn && !multiPluggable)
                illogicalNow(alreadyAttachedMsg);
            
        }
        
        action() 
        { 
            /* 
             *   Carry out the action to attach us to the object we're being
             *   attached to.
             */
            actionDobjAttachTo(); 
            
            /* Note that we're now plugged in. */
            makePlugged(true);
        }
        
        report() { reportDobjAttachTo(); }        
    }
    
    okayAttachMsg = BMsg(okay plug, '{I} plug{s/?ed} {1} into {the iobj}. ',
                         gActionListStr) 
    
    alreadyAttachedMsg = BMsg(already plugged in, '{The subj dobj} {is} already
        plugged into {1}. ', attachedTo.theName)
    
    alreadyPluggedInMsg = BMsg(already plugged in vague, '{The subj {dobj} {is}
        already plugged in. ')
    
    iobjFor(PlugInto)
    {
        preCond = [touchObj]     
        
        verify()
        {
            /* Carry out the inherited handling. */
            inherited;
            
            /* 
             *   If the direct object is not one that can be plugged into us,
             *   rule out the action with an appropriate message.
             */                 
            if(!allowAttach(gVerifyDobj))
                illogical(cannotBeAttachedMsg);
        }
        
        check()
        {
            /* 
             *   If plugging anything else into us would exceed our
             *   socketCapacity, rule out the action with an appropriate
             *   message,
             */
            if(attachments.length >= socketCapacity)
                say(cannotPlugInAnyMoreMsg);
        }
    }
    
    cannotPlugInAnyMoreMsg = BMsg(cannot plug in any more, '{I} {can\'t} plug
        any more into {the iobj}. ')
    
    
    iobjFor(AttachTo)
    {
        check()
        {
            /* Carry out the inherited handling. */
            inherited;
            
            /* 
             *   Make sure we don't exceed our socketCapacity if the player uses
             *   ATTACH TO rather than PLUG INTO; use the check method for
             *   PlugInto.
             */
            checkIobjPlugInto();
        }
    }
    
    cannotBeAttachedMsg = BMsg(cannot be plugged in, '{The subj dobj} {can\'t}
        be plugged into {the iobj}. ')
    
    
    dobjFor(UnplugFrom)
    {
        verify()
        {
            /*  Carry out the inherited checks */
            inherited;
            
            /*  If we're not plugged in we can't be unplugged from anything. */
            if(!isPluggedIn)
                illogicalNow(notAttachedMsg);  
            
            /*  
             *   If we're not attached to the direct object of the command, we
             *   can't be unplugged from it.
             */
            else if(attachedTo != gVerifyIobj)
                illogicalNow(notAttachedToThatMsg);
            
        }
        
        action() 
        { 
            /* Carry out the action handling for detaching from. */
            actionDobjDetachFrom(); 
            
            /* Note that we're no longer plugged in to anything. */
            makePlugged(nil);
        }
        report() { reportDobjDetachFrom(); }
    }
    
    okayDetachFromMsg = BMsg(okay unplug from, '{I} unplug{s/?ed} {1} from
        {the iobj}. ', gActionListStr)
    
    notAttachedMsg = BMsg(not plugged in, '{The subj dobj} {isn\'t} plugged into
        anything. ') 
    
    notAttachedToThatMsg = BMsg(not plugged into that, '{The subj dobj} {isn\'t}
        plugged into {the iobj}. ')
    
    
    dobjFor(Unplug)
    {
        verify()
        {
            /* Carry out the inherited checks. */
            inherited;
            
            /* If we're not plugged into anything, we can't be unplugged. */
            if(!isPluggedIn)
                illogicalNow(notAttachedMsg);   
        }
        
        action()
        {
            /* Note that we're no longer plugged in. */
            makePlugged(nil);
            
            /* 
             *   If plugging/unplugging this item requires an explicit socket to
             *   plug into/unplug from, then detach this item from whatever it's
             *   currently attached to.
             */
            if(needsExplicitSocket)
                actionDobjDetach();
        }
        
        report() { say(okayDetachMsg); }
    }
    
    okayDetachMsg = BMsg(okay unplug, '{I} unplug{s/?ed} {1}. ', gActionListStr)
    
    dobjFor(PlugIn)
    {
        verify()
        {
            /* Carry out the inherited handling. */
            inherited;
            
            /* 
             *   If we're already plugged in we can't be plugged in now, but the
             *   message to display depends on whether we require a specific
             *   socket to be plugged into.
             */
            if(isPluggedIn)
                illogicalNow(needsExplicitSocket ? alreadyAttachedMsg :
                             alreadyPluggedInMsg);
        }        
                
        
        action()
        {
            /* 
             *   If we need to specify a specific socket for this item to be
             *   plugged into, convert this action into a PlugInto action and
             *   ask for its indirect object (i.e. the socket to plug into).
             */
            if(needsExplicitSocket)
                askForIobj(PlugInto);
            
            /* Otherwise simply note that we're now plugged in. */
            else
                makePlugged(true);          
            
        }
        
        report() { DMsg(okay plug in, '{I} plug{s/?ed} in {1}. ', gActionListStr); }
        
    }
;

Adv3Lite Library Reference Manual
Generated on 01/03/2024 from adv3Lite version 1.6.2