collective.t

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


/* 
 * COLLECTIVE EXTENSION
 * 
 */

/* ------------------------------------------------------------------------ */
/*
 *   Collective - this is an object that can be used to refer to a group of
 *   other (usually equivalent) objects collectively.  In most cases, this
 *   object will be a separate game object that contains or can contain the
 *   individuals: a bag of marbles can be a collective for the marbles, or
 *   a book of matches can be a collective for the matchsticks.
 *   
 *   A collective object is usually given the same plural vocabulary as its
 *   individuals.  When we use that plural vocabulary, we will filter for
 *   or against the collective, as determined by the noun phrase
 *   production, when the player uses the collective term. 
 *   [COLLECTIVE EXTENSION]
 */
class Collective: Thing
    /* 
     *   A list of one or more tokens, any of which might be used to refer to me
     *   as a plural object. E.g. if the Collective is a bunch of grapes,
     *   pluralTokes might be ['grapes']. This is used so that I'm chosen in
     *   preference to individual objects (e.g. grapes) when the player's
     *   command includes one of these plural tokens, but an individual object
     *   (e.g. a grape) is preferred otherwise.
     *
     *   By default (if this is left as nil by the user) the library populates
     *   this list from our name property, which in the majority of cases should
     *   achieve what's needed. This property only needs to be user-defined in
     *   cases where using the name won't work, even in conjunction with the
     *   extraToks property.
     */
    collectiveToks = nil
   
    /*   
     *   A list of additional tokens added to our collectiveToks at preInit.
     *   This might be needed, for example, if a Collective called 'stack of
     *   cans' also answers to 'pile of tins' (i.e., if it has 'pile' and 'tins'
     *   defined as additional nouns it its vocab property), in which case we'd
     *   need to defined these tokens ['pile', 'tins'] here.
     */
    extraToks = nil
    
    
    /* Did the player's command match any of our collectiveToks ? [COLLECTIVE EXTENSION] */
    collectiveDobjMatch = nil
    
	/* Did the player's command match any of our collectiveToks ? [COLLECTIVE EXTENSION] */
    collectiveIobjMatch = nil
    
    /* The number of dispensible items being requested. */
    numberWanted = 0
    
	/* For the COLLECTIVE EXTENSION  decide whether to select the collective object (ourselves) or an individual item. */
    filterResolveList(np, cmd, mode)
    {
        /* Carry out any inherited handling */
        inherited(np, cmd, mode);
        
        local num = np.quantifier == nil ? 1 : np.quantifier;
        
        local collectiveMatch = np.tokens.overlapsWith(collectiveToks) 
            && num == 1;
        
        /* 
         *   Note whether we have a collective match in the particular role
         *   being considered.
         */
        if(np.role == DirectObject)
        {
            collectiveDobjMatch = collectiveMatch;
            
            /* Note the numberWanted */
            numberWanted = num;
        }
        if(np.role == IndirectObject)
            collectiveIobjMatch = collectiveMatch;
        
        /* Go through each of the NPMatch objects in our list. */
        foreach(local cur in np.matches)
        {
            /* 
             *   Only worry about it if the NPMatch object's associated object
             *   is one for which we're a collective. If it is, we need to make
             *   a decision.
             */
            if(isCollectiveFor(cur.obj))
            {
                /*  
                 *   If the player's command included one of our collectiveToks,
                 *   then we're the preferred object to use, so remove cur from
                 *   the list of possible matches.
                 */
                if(collectiveMatch || collectiveAction(np, cmd))
                    np.matches -= cur;
                
                /*  Otherwise prefer the individual item. */
                else
                {                    
                    /* So remove our NPMatch object from the list of matches. */
                    np.matches = np.matches.subset({m: m.obj != self});
                    
                    /* 
                     *   Then stop looping through the match list, since there's
                     *   no more to do.
                     */
                    break;
                }
            }
        }
        
    }
    
    /*
     *   Determine if I'm a collective object for the given object.
     *
     *   In order to be a collective for some objects, an object must have
     *   vocabulary for the plural name, and must return true from this method
     *   for the collected objects.
     *
     *   The cmd parameter can be use to determine the current action
     *   (cmd.action), in case we want to vary our decision according to the
     *   action.
     *
     *   The np parameter can be used, inter alia, to determine the role
     *   (np.role), e.g. as DirectObject or IndirectObject.
	 *
	 *   [COLLECTIVE EXTENSION]
     */     
    isCollectiveFor(obj) { return nil; }
    
    
    collectiveAction(np, cmd) { return nil; }
    
    /* Overidden for COLLECTIVE EXTENSION */
    preinitThing
    {
        inherited();
        
        /* 
         *   If collectiveToks hasn't been defined, populate it with tokens
         *   drawn from our name plus any user-defined extraToks.
         */
        if(collectiveToks == nil)
            collectiveToks = name.toLower.split(' ') + valToList(extraToks);
    }
;

/*   
 *   A DispensingCollective is a Collective that dispenses objects when the
 *   player takes from it; e.g. a bunch of grapes that dispenses grapes.
 *   [COLLECTIVE EXTENSION]
 */
class DispensingCollective: Collective
    
    /*  
     *   If definesd the class of object that is created and dispensed when an
     *   actor takes from this DispensingCollective. [COLLECTIVE EXTENSION]
     */
    dispensedClass = nil
    
    /*   
     *   Alternatively, a list of objects that are taken in turn when we take
     *   from this DispensingCollective. [COLLECTIVE EXTENSION]
     */
    dispensedObjs = nil
    
    /*   The number of objects we have dispensed so far. [COLLECTIVE EXTENSION] */
    dispensedCount = 0
    
    /*   
     *   The total number of objects we can dispense. If this is nil, there is
     *   no limit. [COLLECTIVE EXTENSION]
     */
    maxToDispense = nil
    
    /*   
     *   The number of objects we have left to dispense. This is updated by the
     *   canDispense method, and shouldn't be overridden by user code. It may,
     *   however, for use code to consult this property, e.g. to vary our
     *   description. [COLLECTIVE EXTENSION]
     */         
    numLeft = 0
    
    /*  
     *   In principle a DispensingCollective can supply additional items on
     *   demand. This property is used by the parser to prevent it from throwing
     *   an error when the player asks for more of the items we dispense than
     *   are currently in scope. [COLLECTIVE EXTENSION]
     */
    canSupply = true
    
    /*   Is it possible (or allowed) to dispense any more objects from us? [COLLECTIVE EXTENSION] */
    canDispense()
    {
        /* 
         *   If we have a list of dispensedObjs, we can continue to dispense
         *   until we reach the end of the list.
         */
        if(dispensedObjs)
        {
            numLeft = valToList(dispensedObjs).length - dispensedCount;       
        }
        
        /*  
         *   Otherwise, if we have a dispensed class we can continue to dispense
         *   either if there is no limit to the number we can dispense
         *   (maxToDispense = nil) or until we have dispensed maxToDispense
         *   objects.
         */
        if(dispensedClass)
        {
            /* 
             *   There's no limit to the number we can dispense, so simply
             *   return true.
             */
            if(maxToDispense == nil)
                return true;
            
            numLeft = maxToDispense - dispensedCount;     
        }
        
        /*  
         *   We can dispense the number of objects asked provided that number
         *   is no greater than the number we have left.
         */
        return numberWanted <= numLeft;;           
    }
    
    /*  Dispense an object from this DispensingCollective. [COLLECTIVE EXTENSION]*/
    dispenseObj()
    {
        for(local i in 1..numberWanted)
        {
            /* Increase the count of the number of objects dispensed. */
            dispensedCount++;
            
            /* If we have a list of dispensedObjs, select the next one. */
            if(dispensedObjs)
            {
                obj = valToList(dispensedObjs)[dispensedCount];            
            }
            /*  Otherwise create a new object of our dispensedClass class. */
            else
                obj = dispensedClass.createInstance();
            
            
            /* 
             *   Check that the actor has room to hold obj, and stop the action
             *   if not.
             */
            if(gOutStream.watchForOutput({: obj.checkRoomToHold() }))
                exit;
            
            /*  Move the object into the actor's inventory. */
            obj.actionMoveInto(gActor);           
            
            
            /*  
             *   If we have now dispensed all the objects we can, carry out any
             *   appropriate adjustments
             */
            
            if(dispensedCount == maxToDispense)
                exhaustDispenser();
        }
        
         /*  Say that we have dispensed the object. */
        sayDispensed(obj);
    }
    
    /* Display a message saying that the actor has taken an object from us. [COLLECTIVE EXTENSION]*/
    sayDispensed(obj)
    {               
        local objDesc = numberWanted == 1 ? obj.aName :
        spellNumber(numberWanted) + ' ' + obj.pluralNameFrom(obj.name);
        
        DMsg(say dispensed, '{I} {take} {1} from {2}. ', objDesc, theName);
        
    }
    
    
    /*  
     *   Game code can override this method on specific objects to carry out the
     *   effects of dispensing the maximum number of objects we're going to
     *   dispense, e.g. by changing the description, or replacing a bunch of
     *   bananas by a single banana (when it's the last one left). We do nothing
     *   here in the library, since what's needed will vary with the specifics
     *   of the game. [COLLECTIVE EXTENSION]
     */
    exhaustDispenser()
    {
    }
    
    /* 
     *   The TAKE action applied to a DispensingCollective might mean one of two
     *   things: it might be an attempt to take the DispensingCollective (e.g.
     *   the bunch of grapes) itself, or it may be an attempt to take a single
     *   item (e.g. a single grape from the bunch). We assume it's the former if
     *   what the player typed matches the plural vocab (e.g. 'grapes') and the
     *   latter otherwise. [COLLECTIVE EXTENSION]
     */
    dobjFor(Take)
    {
        verify()
        {
            /* 
             *   If it's an attempt to take us, rather than an item from us, use
             *   the inherited handling.
             */
            if(collectiveDobjMatch)
                inherited;
        }
        
        check()
        {
            /* 
             *   If it's an attempt to take us, rather than an item from us, use
             *   the inherited handling.
             */
            if(collectiveDobjMatch)
                inherited;
            
            /*  
             *   Otherwise see if we're able to dispense another item and
             *   complain if we can't.
             */
            else if(!canDispense)
                sayCannotDispense();
        }
        
        action()
        {
            /* 
             *   If it's an attempt to take us, rather than an item from us, use
             *   the inherited handling.
             */
            if(collectiveDobjMatch)
                inherited;
            
            /*  
             *   Otherwise, dispense an item (e.g. take a single grape from the
             *   bunch of grapes.
             */
            else
                dispenseObj();
        }
    }
    
    /*  
     *   We need to be able to handle commands like TAKE GRAPE FROM BUNCH where
     *   the DispensingCollective represents both objects in the command (since
     *   until we actually take the grape - or whatever the dispensed object is
     *   to be - it doesn't yet exist in scope to be the object of the command.)
	 *   [COLLECTIVE EXTENSION]
     */
    iobjFor(TakeFrom)
    {
        verify()
        {
            if(dispensedObjs && gTentativeDobj.overlapsWith(dispensedObjs))
                logical;
            
            else if(dispensedClass && gTentativeDobj.indexWhich({o:
                o.ofKind(dispensedClass) } ))
                logical;
            
            else if(gTentativeDobj.indexOf(self))
                logicalRank(120);    
            
            else
                illogical(cannotTakeFromHereMsg);                   
        }
    }
    
	[COLLECTIVE EXTENSION]
    cannotTakeFromHereMsg = BMsg(cant take from dispenser, '{I} {can\'t} take {a
        dobj} from {the iobj}. ')
    
	[COLLECTIVE EXTENSION]
    dobjFor(TakeFrom)
    {
        verify()
        {
            if(gIobj == self)
                logicalRank(120);
            else
                inherited;
        }
        
        check() { checkDobjTake(); }
        action() { actionDobjTake(); }
    }
    
	[COLLECTIVE EXTENSION]
    sayCannotDispense()
    {
        if(numLeft < 1)
            say(cannotDispenseMsg);
        else
            say(notEnoughLeftMsg);
    }
    
    /* The message to display when there's no more items to dispense from us. [COLLECTIVE EXTENSION]*/
    cannotDispenseMsg = BMsg(cannot dispense, '{I} {can\'t} take any more from
        {the dobj}. ')
    
    /* 
     *   The message to display when the player has asked us for more items than
     *   we have left. [COLLECTIVE EXTENSION]
     */
    notEnoughLeftMsg = BMsg(not that many left, 'There{plural} {aren\'t} that
        many left to take. ')
    
    /* Are we the Collective for obj? [COLLECTIVE EXTENSION] */
    isCollectiveFor(obj) 
    {         
        
        /* We are not the Collective for obj if obj */
        if(dispensedObjs && valToList(dispensedObjs).indexOf(obj) != nil)
            return true;
        
        if(dispensedClass && obj.ofKind(dispensedClass))
            return true;
             
        return nil; 
    
    }
    
    /* The TakeFrom action should always act on us, the Collective. [COLLECTIVE EXTENSION] */
    collectiveAction(np, cmd) 
    { 
        return cmd.action == TakeFrom; 
    }
    
    /* Overidden for COLLECTIVE EXTENSION */
    preinitThing
    {
        inherited();
        
        /* 
         *   Force an initial calculation of our numLeft property, the
         *   number of items we have left to dispense.
         */
        canDispense();
    }
;

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