Dispenser: a never-ending supply

Hello again! I’m trying to fill a dispenser with an uncounted amount of identical items. Here’s my little example, a jar full of golden rings:

class ring : Dispensable, Wearable 'ring' 'ring'
    "One of those golden rings from the jar."
    isEquivalent = true
    location = jar    
;

entryway: Room 'Entryway'
    "A room. "
;

+ jar : Dispenser 'jar' 'jar'
    "A jar full of golden rings."
    canReturnItem = true
  myItemClass = ring
;

++ring;
++ring;
++ring;
++ring;
++ring;
++ring;
++ring;

+ button : Button 'Button' 'button'
    "A button labelled <q>Magic!</q>"
    dobjFor(Push) {
        action() {
            if (ring.isIn(me))
            {
                if (ring.isWornBy(me))
                 "Yes, you are wearing a ring!\b";
                else
                 "You have a ring, but don't wear it!\b";
            }
            else
            {
                 "No, you have no ring!\b";
            }
        }
    }
;

My questions:

  1. How can I allow the play to take as many rings from the jar as he wants? So, instead of a couple of “++ring;” lines (sure, he won’t need as many as that in the game), whenever he goes “get ring” a new ring instance is moved to the player?
  2. How would I crack the old “count rings” → “there are 69105 rings here” joke, without adding 69105 “++ring;” lines?
  3. How would I restrict the number of rings the player can take from the jar? So, if he has five rings and tries to take another one, “You have enough rings now, you greedy person!”
  4. I have no idea how to test whether the player has a ring, and if so, whether he is wearing it or not. The button-pushing action always tells me I have no ring.

Once again, sorry if this is a silly question; if there is any documentation which could tell me just that and I should definitely have read before asking, please don’t hesitate to let me know!

I have some examples I can give; no time at the moment but I’ll check back later if no one else has gotten you the info…

Great, thank you in advance! No hurry, I can keep myself busy with other parts in the meantime (…implementing verbs…)

Feel free to ask; TADS is a large system with a huge amount of documentation. It’s always good and advisable to search and read on one’s own, but it’s no problem to ask for help, either. :slight_smile:

There’s a dispenser example with a box of candles in the TADS 3 Tour Guide, under Containers → Dispenser. New candle objects are created dynamically on demand, and the box also keeps track of the number of candles created and only allows it up to a definable maximum. That example might help to partially answer questions 1 and 3. Link: Dispenser

2 Likes

Here’s a simple “game” that illustrates several of the things you’re trying to do. Specifically, it contains a bowl which dispenses infinite widgets, which the player (or any other actor) can only hold two of at a time:

#charset "us-ascii"
#include <adv3.h>
#include <en_us.h>

widget: Dispensable 'widget*widgets' 'widget'
        // Logic in the description is just to allow us to distinguish between
        // when we're looking at widget in the inventory and one in the bowl
        // (or elsewhere).
        "<<if (getCarryingActor() == gActor)>>A generic widget, which you're
                carrying.
        <<else>>A generic widget.
        <<end>> "
        isEquivalent = true
        dobjFor(Take) {
                verify() {
                        // Check how many widgets the gActor already has
                        if(getWidgetCount(gActor) >= 2) {
                                reportFailure('{You/he} already {have} as many
                                        widgets as {you/he} can hold.');
                                exit;
                        }
                        // Twiddle the likelihood of the parser picking a
                        // widget we're already carrying for a "take" command.
                        // This prevents the "Which widget do you mean...?"
                        // prompt when the player already has one or more
                        // widgets.
                        if(getCarryingActor() == gActor) {
                                logicalRank(50, 'carrying');
                        }
                }
        }
        // Returns number of widgets carried by the passed actor
        getWidgetCount(actor) {
                local n;

                n = 0;
                actor.contents.forEach(function(o) {
                        if(o.ofKind(widget)) n += 1;
                });

                return(n);
        }
;

startRoom:      Room 'Void'
        "This is a featureless void. "
;
+ me:   Person;
+ bowl: Dispenser 'bowl (full) (of) (widgets)' 'bowl full of widgets'
        "A bowl of widgets."
        myItemClass = widget

        // We set these to nil because we don't want the room description
        // to show something like "You see a bowl (which contains a widget)
        // here.", which is what we'd get otherwise.  There are other
        // approaches, but if there are infinite widgets (and so we never
        // have to tweak the appearance of the bowl to reflect the level
        // of widgets) it's probably easier to just bake the widgets into
        // canonical name for the bowl.
        contentsListed = nil
        contentsListedInExamine = nil

        // Create a new widget and add it to the bowl whenever one is
        // removed.
        notifyRemove(obj) {
                local tmp;

                if(contents.length < 2) {
                        tmp = new widget();
                        tmp.baseMoveInto(self);
                }
        }
;
++ widget;

versionInfo:    GameID
        name = 'sample'
        byline = 'nobody'
        authorEmail = 'nobody <foo@bar.com>'
        desc = '[This space intentionally left blank]'
        version = '1.0'
        IFID = '12345'
;
gameMain:       GameMainDef
        initialPlayerChar = me
;

Most of this is just the barebones code necessary to accomplish what it accomplishes. The only chrome added here is the tweak to TAKE’s logicalRank in the verify() method. That’s there (as indicated in the comments) so >TAKE WIDGET won’t prompt with “Which widget do you mean…?” if the player is already carrying a widget.

Adding a custom response to a hypothetical >COUNT WIDGETS command is easy by itself, but there are a bunch of different ways you might be implementing countable things. Have you decided how you’re handing all the isEquivalent objects? I.e., are you planning on using a CollectiveGroup, how are you planning on handling actions with counts on stacks of equivalent objects, and so on.

1 Like

Many thanks! The code by @jbg put me on the right track to accomplish what I had in mind. About the counting thing, it’s not important, would just be a little easter egg for the Zork fans. So, implementing a transitive “count” verb which returns the “69105” joke for the dispenser, is totally enough.

Oh, just wondering, I have used “gActor” and “me” (my Actor) randomly in my code. I gather if I never plan to switch the perspective to a different person (Maniac Mansion style), this is totally fine?

Some random musings about TADS, perhaps carrying a bit too far, but my first impression: I’ve started my project in Inform6 a while ago but got stuck… Decided to port it to TADS, because the C-like syntax feels much more comfortable to me, and once I got past some little hiccups, I feel more and more confident in it.

Just aside, a funny typical newbie confusion: Why do you need sometimes a “=”, sometimes a “:”, sometimes nothing? Like in these three lines:

    north = kitchen
    east asExit(out)
    out : OneWayRoomConnector  {  blah blah

But that’s just a question of understanding the property assignments vs. anonymous objects (or however you call them).

In general, I like the powerful library, there is such a plethora of properties and classes, but sometimes it’s difficult to understand the subtle differences. Sometimes the descriptions are a little bit weird, but I’ll leave the fine-tuning for later, when I’ll be more at home in the language.

Incidentally, do I really need to use this strange “{The sarah/she}” syntax, or can I forget about that for the time being?

Well, just implementing a simple >COUNT [things] verb that normally does nothing is easy enough:

modify npcActionMessages
        countFailed = '{You/He} is not the counting type.'
;
modify playerActionMessages
        countFailed = 'You have lost your mind.'
;
DefineTAction(Count);
VerbRule(Count)
        'count' (singleDobj | dobjList)
        : CountAction
        verbPhrase = 'count/counting (what)'
;
modify Thing
        dobjFor(Count) {
                action() {
                        reportFailure(&countFailed);
                }
        }
;

…and then you can just add your joke with something like…

        dobjFor(Count) {
                action() {
                        if(gActor.isPlayerChar() && (self.location == bowl)) {
                                "It looks like there are exactly 69.105 widgets. ";
                                exit;
                        }
                        inherited;
                }
        }

…to the object declaration. But that behaves very badly in most cases:

>count widgets
(the widget)
It looks like there are exactly 69.105 widgets.

>take widget;take widget
Taken.

You take the widget.

>count widgets
your widget: You have lost your mind.
the widget in the bowl full of widgets: It looks like there are exactly 69.105
widgets.
your widget: You have lost your mind.

There are also weirdnesses like

>take 2 widgets
You don't see that many widgets here.

>take widget;take widget
Taken.

You take the widget.

>count 2 widgets
You can't use multiple objects there.

…and so on. Which is the reason you probably need to make a decision about how you’re going to handle stacks of your equivalent things.

Using me is usually safe if you’re not planning on switching protagonists, but there are plenty of places that gActor might be used where it is an actor other than the player. Unless there are no other actors in your game at all, or if they’re all totally static.

The “safer” version of me is really gPlayerChar, not gActor. gActor is just whatever actor happens to be taking an action, which means that gActor will sometimes be NPCs when they’re taking actions.

If you’re defining verbs or adding action handlers to objects and you know that no actor other than the player will ever be able to do [whatever it is], then you can usually get away with using me everywhere…but keep in mind that even if the player is always me, gActor will not necessarily be me unless there are no other actors/NPCs in the game.

In properties, a “=” generally means what you probably intuitively think it means: you’re saying that in the future when you refer to the property…the thing on the left…you want it to mean the thing on the right. There are a number of gotchas here because TADS is sometimes peculiar about these things (foo = [ 'bar' ] does not create an array containing bar and assign it to the property foo, it means “create a new array containing bar every time the property foo is used”; foo = static [ 'bar' ] is what you need to create the array only once), but that’s the basic idea.

When you see a colon in a declaration like that means that the value is an object, and the stuff after the colon is the superclass list followed by the object definition.

In east asExit(out) the asExit() bit is a macro, which means that somewhere (in this case in adv3.h) there’s a #define statement that tells the TADS compiler to replace the asExit() bit with something else when building the story file.

If your game is completely static except for actions directly taken by the player then you can probably get away with ignoring
message parameter substitutions. Otherwise it’s something you have to pay attention to.

Yes, I’ve seen such bad behavior / weirdness before. Mostly, the game does what it should, bun then it gives a rather weird answer, and I’m still too noob to understand where it comes from.

For the time being, I’ve decided to shrug it off, hoping that such things can be fine-tuned later, in the alpha-testing phase (and hopefully don’t need a whole lot of restructuring). For my motivation, it’s better to make some progress than to polish such little things.