# gadget.t | documentation

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

    /*
     *   ***************************************************************************
     *   gadget.t
     *
     *   This module forms part of the adv3Lite library (c) 2012-13 Eric Eve
     *
     *
     *
     *   This module contains definitions for various control gadgets like buttons,
     *   levers and dials.
     */

    /*  A Button is an object that does something when pressed */
    class Button: Thing

        /* a button is usually fixed to something */
        isFixed = true

        /* Handle the Push command */
        dobjFor(Push)
        {
            /* A Button is a good choice for a PUSH command */
            verify() { logicalRank(120); }

            /* Execute our makePushed method when we're pushed */
            action() { makePushed(); }

            /* If nothing else happens, just say 'Click!' */
            report() { DMsg(click, 'Click!'); }
        }

        /*
         *   Carry out the effects of pushing the button here. Particular Button
         *   objects will need to override this method to carry out the effect of
         *   pushing the button.
         */
        makePushed() { }
    ;

    /*
     *   A Lever is an object that can be in one of two positions: pulled (isPulled
     *   = true) or pushed (isPulled = nil), and which can be pulled and pushed
     *   between those two positions.
     */
    class Lever: Thing

        /* a lever is usually fixed to something */
        isFixed = true

        /* is this lever in the pulled or pushed position. */
        isPulled = nil

        /*
         *   By default we make isPushed the opposite of isPulled, but we defined
         *   them as separate properties in case we want a lever that can be in more
         *   than two positions, and so might be in an intermediate position that is
         *   neither pushed nor pulled.
         */
        isPushed = (!isPulled)

        /*
         *   Carry out pushing or pulling the lever. Note that this would need to be
         *   overridden on a Lever that can be in more than two states.
         */
        makePulled(stat)
        {
            /* Set isPulled to stat */
            isPulled = stat;
        }

        /* Handle Pulling this Lever */
        dobjFor(Pull)
        {
            verify()
            {
                /*
                 *   A Lever can't be pulled any further if it's already in the
                 *   pulled position
                 */
                if(isPulled)
                    illogicalNow(alreadyPulledMsg);
            }

            /* Use the makePulled() method to handle pulling the lever */
            action() { makePulled(true); }

            /* The default report to display after pulling one or more levers */
            report() { DMsg(okay pulled, 'Done|{I} pull{s/ed} {1}', gActionListStr); }

        }

        /* The message to display when we can't be pulled any further */
        alreadyPulledMsg = BMsg(already pulled, '{The subj dobj} {is} already in the
            pulled position. ')

        /* Handle Pushing this Lever */
        dobjFor(Push)
        {
            verify()
            {
                /*
                 *   A Lever can't be pushed any further if it's already in the
                 *   pushed position
                 */
                if(isPushed)
                    illogicalNow(alreadyPushedMsg);
            }

            /* Use the makePulled() method to handle pushing the lever */
            action() { makePulled(nil); }

            /* The default report to display after pushing one or more levers */
            report() { DMsg(okay pushed, 'Done|{I} push{es/ed} {1}', gActionListStr); }
        }

        /* The message to display when we can't be pushed any further */
        alreadyPushedMsg = BMsg(already pushed, '{The subj dobj} {is} already in the
            pushed position. ');

    ;

    /*
     *   A Settable is anything that can be set to particular values, such as a
     *   slider control or a dial. Various types of dial descending from Settable
     *   are defined below.
     */
    class Settable: Thing

        /*
         *   a list of the valid settings this Settable can have, given as list of
         *   single-quoted strings.
         */
        validSettings = []

        /* our current setting */
        curSetting = nil

        /*
         *   Put the setting into a standard form so it can be checked for validity.
         *   By default we turn it into lower case so that it doesn't matter what
         *   case the player types the desired setting in. We also strip any
         *   enclosing quotes that might have been used to pass an awkward value
         *   like "1.4" that the parser would otherwise misinterpret.
         */
        canonicalizeSetting(val)
        {
            return stripQuotesFrom(val.toLower());
        }

        /*  Set this Settable to its new setting, val */
        makeSetting(val)
        {
            /* Update our current setting to the canonicalized version of val */
            curSetting = canonicalizeSetting(val);
        }

        /*
         *   Check whether the proposed setting is valid. By default it is if the
         *   canonicalized version of val is present in our list of valid settings.
         */
        isValidSetting(val)
        {
            /* Convert val into its canonicalized equivalent. */
            val = canonicalizeSetting(val);

            /*
             *   Determine whether val is present in our list of valid settings and
             *   return true or nil accordingly
             */
            return validSettings.indexOf(val) != nil;
        }

        /* A Settable is something that can be set to various values */
        canSetMeTo = true

        /* Handle a SET TO command targeted at this Settable */
        dobjFor(SetTo)
        {
            /* Check whether we're being set to a valid setting */
            check()
            {
                /*
                 *   If the player is trying to set us to our current setting,
                 *   display a message to that effect (which will halt the action).
                 */
                if(curSetting == canonicalizeSetting(gLiteral))
                    say(alreadySetMsg);

                /*
                 *   If the player is trying to set us to an invalid setting,
                 *   display a message to that effect (which will halt the action).
                 */
                if(!isValidSetting(gLiteral))
                    say(invalidSettingMsg);
            }

            /* Note that the action() handling for SET TO is defined on Thing */
        }

        invalidSettingMsg = BMsg(invalid setting, 'That {dummy} {is} not a valid
            setting for {the dobj}. ')

        alreadySetMsg = BMsg(already set, '{The subj dobj} {is} already set to {1}.
            ', curSetting)

        /*
         *   Most gadgets of this sort are part of or attached to something else, so
         *   we make them fixed in place by default
         */
        isFixed = true
    ;

    /* A Dial is Simply a Settable we can turn as well as set */
    class Dial: Settable
        dobjFor(TurnTo) asDobjFor(SetTo)
        canTurnMeTo = true
    ;

    /*
     *   A Numbered Dial is a Dial that can be turned to any integer in a defined
     *   range of numbers.
     */
    class NumberedDial: Dial

        /* The lowest number to which this dial can be turned. */
        minSetting = 0

        /* The highest number to which this dial can be turned. */
        maxSetting = 100

        /*
         *   If the spelledToInt() function is defined then allow the dial to be
         *   turned to a spelt-out number as well as a number given in digits, e.g.
         *   TURN DIAL TO FORTY-THREE as well as TURN DIAL TO 43.
         */
        canonicalizeSetting(val)
        {
            /* Get the inherited value */
            val = inherited(val);

            /* Try to convert it to a number */
            local num = defined(spelledToInt) ? spelledToInt(val) : nil;

            /*
             *   If the conversion was successful, convert val to a string
             *   representation of the number (e.g. '43').
             */
            if(num)
                val = toString(num);

            /* Return val, changed as need be. */
            return val;
        }

        /* Is val a valid setting for this dial? */
        isValidSetting(val)
        {
            /* Convert val into its canonicalized equivalent. */
            val = canonicalizeSetting(val);

            /*
             *   Try converting val to either a real number or an integer depending
             *   on whether allowDecimal is true or nil.
             */
            val = allowDecimal ? tryNum(val) : tryInt(val);

            /*
             *   Val is valid if it lies between our minimum and maximum settings
             *   (inclusively)
             */
            return val != nil && (val >= minSetting && val <= maxSetting);
        }

        allowDecimal = nil
    ;

    /*
     *   Mix-in class to help with inventory management. A BagOfHolding can be mixed
     *   in with a Container (or, less usually, Surface, RearContainer or Underside)
     *   to provide an object which, if held by the player character, will be used
     *   to move objects in the player character's inventory to if his/her hands
     *   become too full to pick up another object.
     */
    class BagOfHolding: object

        /*
         *   The affinity for this BagOfHolding for obj. This can be used to
         *   determined how 'willing' a particular BagOfHolding is to contain obj. A
         *   value of less than 1 means that the BagOfHolding can't contain obj at
         *   all. The higher the affinity, the better the choice this BagOfHolding
         *   is for obj. The default value is 100, or 0 for a BagOfHolding's
         *   affinity for itself.
         */
        affinityFor(obj)
        {
            return obj == self ? 0 : 100;
        }

        /*
         *   To be suitable to contain obj a BagOfHolding must have enough spare
         *   capacity for it. If it has, its suitability is its affinity for obj;
         *   otherwise it's 0. A BagOfHolding is also unsuitable if it's locked.
         */
        suitabilityFor(obj)
        {
            if(obj.bulk > bulkCapacity - getBulkWithin || obj.bulk > maxSingleBulk
               || isLocked || obj.isFixed)
                return 0;

            return affinityFor(obj);
        }

        /*
         *   Class method to determine whether the actor is carrying a suitable
         *   BagOfHolding that could be used to move something from his inventory
         *   into, and then to move items from the actor's inventory into an
         *   appropriate bag of holding.
         */
        tryHolding(obj)
        {
            /* Obtain a Vector containing the BagsOfHolding carried by the actor. */
            local bohVec = gActor.contents.subset({x: x.ofKind(BagOfHolding)});

            /*
             *   If the actor is not carrying a BagOfHolding, there's nothing more
             *   we can do, so just stop here.
             */
            if(bohVec.length == 0)
                return;

            /*  The amount of bulk we need to free up */
            local bulkToFree = gActor.getCarriedBulk + obj.bulk -
                gActor.bulkCapacity;

            local idx = 1;
            local carriedList = gActor.contents.subset({o: o.wornBy == nil
                                                       && o.isFixed == nil});

            while(bulkToFree > 0 && carriedList.length >= idx)
            {
                local objToMove = carriedList[idx];

                /*
                 *   If we have more than one BagOfHolding available, sort our
                 *   vector of BagsOfHolding in descending order of suitability
                 */
                if(bohVec.length > 1)
                    bohVec.sort(SortDesc, {a, b: a.suitabilityFor(objToMove) -
                                b.suitabilityFor(objToMove) });

                /*
                 *   Choose the first one in the Vector, which will be the most
                 *   suitable.
                 */
                local bagToUse = bohVec[1];

                /*
                 *   If the most suitable bag of holding for the object we're trying
                 *   to move isn't suitable, try again with another object.
                 */
                if(bagToUse.suitabilityFor(objToMove) < 1)
                {
                    idx++;
                    continue;
                }

                /*
                 *   Get the action needed to move an object into the selected
                 *   BagOfHolding.
                 */
                local action = bagToUse.moveAction();

                /*
                 *   If the action is nil, then there's something wrong with the
                 *   selected BagOfHolding. Break of of the loop so we don't get
                 *   stuck in an infinite loop.
                 */

                if(action == nil)
                    break;

                /*
                 *   Try moving the selected object into the selected bag using the
                 *   appropriate action depending on the bag's contType
                 */
                tryImplicitAction(action, objToMove, bagToUse);

                /*
                 *   Reset the index into the contents list to 1 so that if we need
                 *   to select another object we start from the beginning again.
                 */
                idx = 1;

                /*  Recalculate the amount of bulk left to free */

                bulkToFree = gActor.getCarriedBulk + obj.bulk -
                gActor.bulkCapacity;

                /*
                 *   Remove objToMove from the carried list. (Even if it wasn't
                 *   actually moved for any reason, we don't want to try moving it
                 *   again).
                 */
                carriedList -= objToMove;
            }

        }

        /* The action needed to move an object into me. */
        moveAction()
        {
            switch(contType)
            {
            case In:
                return PutIn;
            case On:
                return PutOn;
            case Under:
                return PutUnder;
            case Behind:
                return PutBehind;
            }

            if(remapIn)
                return PutIn;
            if(remapOn)
                return PutOn;
            if(remapUnder)
                return PutUnder;
            if(remapBehind)
                return PutBehind;

            return nil;
        }

    ;

Adv3Lite Library Reference Manual
Generated on 15/03/2023 from adv3Lite version 1.6.1