How do you use 'affinity' - TADS3.1

My actor will have both a khaki shirt and khaki shorts. Both items will have large, deep pockets. He will also be wearing a large backup. I would like to setup ‘affinities’ for these ‘bags of holding’. My one question is: are the affinity statements attached to each ‘bag’ or must they be attached to each and every ‘thing’ that I plan to pickup and store?

I would like to see some techniques on how to handle this idea. I’ve read the TADS manuals and checked out some pieces of source code but I’m still not sure exactly how to go about this.

Would appreciate any ideas.

RonG

You can adjust the bag’s capacity, as well as how much space each object takes up. I forgot the property names that hold those settings. Looking at the properties of the Thing class should tell you which one it is.

Attach affinityFor method to each ‘bag’.

+ bag: BagOfHolding 'bag' 'bag'
    "Bag. "
    affinityFor(obj) { return 200; }
;

I’m still somewhat confused about this. All the source examples I’ve looked at for affinityFor show an actual object where the word ‘object’ is located. Such examples are affinities for wallets, key rings, etc.

Are there any further examples, explanations how all of this is all done :question: :question:

RonG

Expanding on tomasb’s example…

+ bag: BagOfHolding 'bag' 'bag'
    "Bag. "
    affinityFor(obj) {
        switch(obj) {
            case carKeys:
            case spareChange:
                return 200;

            case cellPhone:
            default:
                return 10000;
        }
    }
;

You just test the obj value in your BagOfHolding’s affinityFor method, to see if the object is something that bag wants.

But you can do it other ways, too. For example you can set a property on the object and check that in the bag’s affinityFor method.

carKeys: Thing
    prefersBag = nil
;

spareChange: Thing
    prefersBag = nil
;

cellPhone: Thing
    prefersBag = bag
;

+ bag: BagOfHolding 'bag' 'bag'
    "Bag. "
    affinityFor(obj) {
        if (obj.prefersBag == self)
            return 10000;
        else
            return 200;
    }
;

I think I’m starting to understand. If I pick up a thing that has an affinity for a bag of holding. These things will find their way to one of my bags that I mentioned earlier; providing my hands get full. I could also choose to place an object directly into one of the bags providing that bag can handle it. I guess that’s why the character has only a bulk capacity of ten in my story.

Rong

That’s because wallet is actually a bag (kind of). Affinity of pocket tells, that its suitable for a wallet and wallet’s affinity tells it’s suitable for credit cards or for money, but not so much for any other things. Same for keyrings.

I’m back again with ‘bag of holding’ and ‘affinity’ problems. I’ve been fighting this for days. For testing purposes, I have some small items that I try to put into either a pack I’m wearing or in pockets in my shirt or shorts. The items will go into the pack with no problems but I’m told that they will not go into any pockets. This is driving me crazy. I’ve tried adjusting bulks and affinities but none of that helps. I’ve included the source if anyone would care to look at it

RonG


/ Show actor. ****************************************************************/
me: BagOfHolding, Actor
    location = Beach     
    VocabWords = 'Tom Thomas Tom/Thomas/Leavens'
    desc
 {
  "You're Thomas Leavens. You are known as Dr. Tom to your friends and
  associates. Your ex-wife calls you names that can't be repeated! Along with
  your underwear and socks, you're wearing your usual working attire. ";
 } 

affinityFor(obj)
{
  return (obj != self && obj.ofKind(Wearable) ? 50 : 0);
} 
 
inventoryLister: actorInventoryLister
    {
        showInventoryEmpty(parent)
        {
            /* empty inventory */
            "<<buildSynthParam('The/he', parent)>> {is} holding absolutely
            nothing. ";
        }
        showInventoryWearingOnly(parent, wearing)
        {
            /* we're carrying nothing but wearing some items */
            "<<buildSynthParam('The/he', parent)>> {is}n\'t holding anything,
            but {is} sporting <<wearing>>. ";
        }
        showInventoryCarryingOnly(parent, carrying)
        {
            /* we have only carried items to report */
            "<<buildSynthParam('The/he', parent)>> {is} holding <<carrying>>. ";
        }
        showInventoryShortLists(parent, carrying, wearing)
        {
            local nm = gSynthMessageParam(parent);
            
            /* short lists - combine carried and worn in a single sentence */
            "<<buildParam('The/he', nm)>> {is} holding <<carrying>>,
            and <<buildParam('it\'s', nm)>>{subj} sporting <<wearing>>. ";
        }
        showInventoryLongLists(parent, carrying, wearing)
        {
            local nm = gSynthMessageParam(parent);
            
            /* long lists - show carried and worn in separate sentences */
            "<<buildParam('The/he', nm)>> {is} holding <<carrying>>.
            <<buildParam('It\'s', nm)>> sporting <<wearing>>. ";
        }
        /*
         *   For 'tall' listings, we'll use the standard listing style, so we
         *   need to provide the framing messages for the tall-mode 
         *   listing.  
         */
        showListPrefixTall(itemCount, pov, parent)
            { "<<buildSynthParam('The/he', parent)>> {is} holding:"; }
        showListContentsPrefixTall(itemCount, pov, parent)
            { "<<buildSynthParam('A/he', parent)>>, who {is} holding:"; }           
    }    
/* Use person-sized Bulk. */
   bulk = 10
;

+watch: AlwaysWorn 'wrist wristwatch/watch' 'wristwatch'
 "It is an expensive, rugged and accurate digital watch that you have had for
  years. It currently reads <<clockManager.checkTimeFmt('[am][pm]h:mm a')>>. "
 dobjFor(Open) { verify() { illogical( 'Only a jeweler could open that
  watch ');}}                                    
 dobjFor(LookIn) asDobjFor(Open)
 cannotDoffMsg = "Don't do that! It's crucial that you have your watch at this
                 time."
;   

+ khakis: AlwaysWorn
    'pair/khakis/pants/pant/slacks/trousers/shorts' 'pair of tough khaki shorts'
    "It's a pair of khakis with large, usefull pockets."
; 

++ khakipockets: BagOfHolding, Component, RestrictedContainer
    '(khaki) pocket/pockets' 'pocket'
    "One of the things you like best about these khakis is the nice, deep
    pockets. "
    bulkCapacity = 200
    affinityFor(obj){return 200;}
    theName = 'your khaki pockets'
    aName = 'your khaki pockets'
    disambigName = 'your khaki pockets';
    
   /* we're restricted to pocketable items. */
  // canPutIn(obj) { return obj.okayForKhakiPockets; }
 //  cannotPutInMsg(obj) { return '{The dobj/he} wouldn\'t fit very well in your
 //  khaki pockets. ';}
 
  /* We have a high affinity for for best-for-khaki-pocket items */
  //   affinityFor(obj) {return '{obj.bestForKhakiPockets ? 75 : 0; };

+ AlwaysWorn 'khaki shirt' 'rugged khaki shirt'
  " The tough shirt matches your khaki shorts and also has some useful
    pockets. "
; 

++ shirtpockets: BagOfHolding, Component, RestrictedContainer
     '(shirt) pocket/pockets' 'pocket'
     "Like your shorts, the shirt also furnishes some handy pockets. "
     bulkCapacity=200
     affinityFor(obj){return 200;}
     theName = 'your shirt pockets'
     aName = 'your shirt pockets'
     disambigName = 'your shirt pockets';

   /* we're restricted to pocketable items. */
  // canPutIn(obj) { return obj.okayForKhakiPockets; }
 //  cannotPutInMsg(obj) { return '{The dobj/he} wouldn\'t fit very well in your
 //  khaki pockets. ';}
 
  /* We have a high affinity for for best-for-khaki-pocket items */
  //   affinityFor(obj) {return '{obj.bestForKhakiPockets ? 75 : 0; };

+ myBoots: AlwaysWorn 'leather left right boot/boots/pair'
  'pair of hiking boots'
 "Your boots are made for both heavy duty hiking and all-terrain climbing."
; 

/* Room0001: The Actor Starts Game Here. **************************************/

  Beach : OutdoorRoom 'At The Beach' 'At The Beach'
  roomFirstDesc()
  { "This is first description.";
  }
  shortDesc = "You are at the sandy beach.";
 
/* Add wooden box to beach. ***************************************************/

+box: ComplexContainer, Fixture 'large slatted wooden (wood) box*boxes' 'large
 slatted-wood box' 
 "The large wooden box, left here from a precious helicopter flight, lies on
  some small rocks. At its top, there is a metal hasp that can unlock or lock
  the box. "
 cannotTakeMsg = (backpack.seen ? 'The backpack would be a more convenient
 container. You would be better off using that instead.' : 'The box is much too
 heavy to lift or carry around.')
 initSpecialDesc = "A large slatted-wood box is lying nearby on some small
                    rocks."
 subContainer: ComplexComponent, LockableContainer
 {
 currentlyLockedMsg = 'The hasp is currently at the locked position. '
   currentlyUnlockedMsg = 'The hasp is currently at the unlocked position. '
     dobjFor(Lock)
     {
       action()
       {
         "You twist the hasp, and lock the box shut.";
          inherited;
       }
     }
      dobjFor(Unlock)
      {
       action()
        {
          "You turn the hasp, allowing the box to be opened.";
          inherited;
        }
    }
    }
;

++ Component 'hasp' 'hasp'
"A simple hasp that does the job of locking and unlocking the box."
  dobjFor(Turn) { remap = [box.subContainer.isLocked ? UnlockAction : 
  LockAction, box.subContainer]}
;

/* Add backpack to box. *******************************************************/
   
backpack: BagOfHolding, Attachable, Wearable, OpenableContainer
   'standard military pack/backpack' 'backpack'
   "It is a standard military backpack, allowing a sleeping bag to be attached
   to it at the bottom. Its large main compartment can be opened or closed from
   the top, and, if you wear this pack, you can store many objects in it,
   allowing you to possess a much larger number of items than if you carried
   them in your hands. "
   location = box.subContainer
   affinityFor(obj){return 100;}
   bulkCapacity = 4000
   minBulk = 1
   
   dobjFor(Take) {
      action() {
         inherited();
         makeWornBy(gActor);
         "You sling the pack over your shoulders. ";
         wearpackAchievement.awardPointsOnce();
      }
   }

   dobjFor(Doff) {
      action() {
         inherited();
         gActor.getDropDestination(self, nil).receiveDrop(self, dropTypeDrop);
         mainReport(&okayDoffMsg);
      }
   }

   dobjFor(Wear) asDobjFor(Take)
   dobjFor(Drop) asDobjFor(Doff)

   wearpackAchievement : Achievement { +5 "wearing the backpack"}
;

/* Add HELP sign to beach. ****************************************************/

class Sign : Readable
;
+sign: Sign, Fixture 'large metal sign' 'metal sign'
  "The sizeable metal sign is firmly attached to a tree near the start of the
   eastern path. It is quite rusty and worn but the printing on it can still be
   read. It just might provide some information about The Magic Forest."
 location = Beach
 initSpecialDesc = "Attached to a tree at the beginning of the eastern path is
   a large metal sign."
 cannotTakeMSG = 'Sorry, but it is very rigidly attached to the tree. Besides,
   there is no reason to take it with you.'
 readDesc = "The sign is written in both English and Spanish and  it reads:
   'DANGER! Restricted property beyond this point. Enter at your own risk.'\b
   Property Manager,\b
   Melenkurian\b"
 dobjFor(Read)
 {
  action()
  {
   readsignAchievement.awardPointsOnce();
   inherited;
  }
 } 
 readsignAchievement : Achievement { +5 "reading the sign at the beach" }
; 
 
/* Add matchbook to backpack. *************************************************/

modify Matchbook
  vocabWords = 'book'
  name = 'matchbook'
 ;    

matchbook : Dispenser, Matchbook 'matchbook' 'matchbook'
location = backpack
desc()
{
 "It is an ordinary book of matches that bears no mark or brand of any type. The
  matchbook is";
    if(contents.length> 14 || matchesCreated < maxMatchesToCreate/2)
    " full of matches. ";
    else if(contents.length < 1 && matchesCreated == maxMatchesToCreate)
    " empty. ";
    else if(matchesCreated < 3 * maxMatchesToCreate/4)         
    " about half full of matches. ";
    else         
    " running out of matches. ";
}
myItemClass = MyMatchstick
canReturnItem = nil
notifyRemove(obj)
{
if(contents.length < 2 && matchesCreated < maxMatchesToCreate)
{
local cur = new MyMatchstick;    
matchesCreated++;
cur.moveInto(self);
}
}
matchesCreated = 0
maxMatchesToCreate = 15
dobjFor(LookIn) asDobjFor(Examine)
;        

+ MyMatchstick;
  modify Matchstick 
  vocabWords = 'match/matchstick'
  name = 'match' 
  location = matchbook
  isEquivalent = true
    burnLength = 2
  brightnessOn = 1
     collectiveGroups = [MatchstickGroup]
; 

class MyMatchstick: Dispensable, Matchstick
isListedInContents = (!isIn(matchbook))
myDispenser = matchbook
;

MatchstickGroup : CollectiveGroup
    vocabWords = '*matches*matchsticks'
    desc = "The matchsticks all look alike."

    isCollectiveAction(action, whichObj) { 
        if (action.ofKind(ExamineAction) ) 
          return true; 

        /* it's not one of ours */ 
        return nil; 
     }
;

/* Add compass to box. ********************************************************/

compass: Wearable 'round small compass' 'small compass'
"It is an ordinary, small compass, with a silver hand that is always pointing,
 unsurprisingly, to the north. It is made to be worn around the neck. "
location = box.subContainer
bulk = 10
dobjFor(Wear)
{
action()
{
"You bow your head, putting the compass around your neck.";
inherited;
}
}
dobjFor(Remove)
{
action()
{
"Carefully, you remove the compass from around your neck.";
inherited;
}
}
;

/* Add sleepingbag to box. ****************************************************/

btw: The objects I’m testing with are the compass and the box of matches.

khakipockets and shirtpockets are RestrictedContainers.

Since you’ve commented out their canPutIn(obj) methods, they will use the default implementation, which checks the validContents list, which is empty by default. So nothing is allowed to go inside.

Turn them into regular Containers and it will work.

++ khakipockets: BagOfHolding, Component, Container
...
;

++ shirtpockets: BagOfHolding, Component, Container
...
;

That did it! I continue to be amazed at how quickly and accurately my posts are answered. I can’t thank all of you enough times.

RonG