The Four-Eyed Guide to TADS 3 by David Welbourn index


TADS 3 Eye For The Inform 6 Guy: Attributes

absentclass PresentLater: object (extras.t)

Inform 6's absent attribute is used with a floating item to remove it from the game map, and, of course, clearing the absent flag brings it back. You might use this to remove the moon from outdoor locations when it's day, or bring it into the outdoor locations when it's night.

TADS 3 uses subclasses of BaseMultiLoc to handle floating items: MultiLoc for singular items that appear in their entirety in multiple locations (like the moon), MultiInstance for multiple items that are clones of each other (like trees in a forest), and MultiFaceted for singlular items that stretch into several locations and where the player only sees part of it at a time (like a river or long rope). These are all defined in objects.t.

Now, each subclass of BaseMultiLoc defines methods called moveIntoAdd(loc) and moveOutOf(loc) to selectively add and remove instances from locations, but that would be a clumsy way to move the entire set of instances in and out of the game. To the rescue comes the PresentLater mix-in class.

PresentLater lets you define your objects, including multi-objects, in their intended locations as normal. During game initialization, all PresentLater items are moved off the map. When you want to bring the moon in, call moon.makePresent(). And when you want to remove it, call moon.moveInto(nil).

PresentLater is clever enough to handle several different objects at once by using key values. For example, if you want to bring the moon, stars, and some ghosts into play all at once, give all those objects the same plKey value, e.g.: 13. Summon them all with PresentLater.makePresentByKey(13). Banish them just as easily with PresentLater.makePresentByKeyIf(13,nil).

animateclass Actor: Thing, Schedulable, Traveler, ActorTopicDatabase (actor.t)
Actor (actor.t)
  • UntakeableActor (actor.t)
    • Person (actor.t)

All creatures in TADS 3 should be declared with class Actor or one of its subclasses. If the creature can be picked up and moved, such as a baby or a friendly cat, use Actor. Any animal you can't pick up should be declared with UntakeableActor. And, of course, use class Person for humans, aliens, and humanoid robots.

clothingclass Wearable: Thing (objects.t)

This is straightforward: just define any clothing as an instance of the Wearable class, or one of Wearable's subclasses, should you define any.

concealedclass Hidden (objects.t); Thing.sightPresence, .suppressAutoSeen, .isListed*, .hideFromAll(action) (thing.t)

Oh, I know that you never used Inform 6's ill-defined concealed attribute. And you don't really want to duplicate its effects in TADS 3. You merely want to know how to hide objects from the player, correct? Well, of course you do.

TADS 3 defines a Hidden class, used to create an object hidden from sight until it's told that it's been discovered, which seems a handy way of handling a "look under the bed" puzzle. There's also a SecretDoor class for objects that act like doors but don't look like them, and HiddenDoor for an door that's invisible when closed.

Looking at how these classes handle hiding, we can pick up a few tips on how hiding is done more generally. For example, if you set a thing's sightPresence to nil, that means it can't be seen. This still lets the player detect it by some other sense, though, if applicable.

Or you might want the object to be visible but not immediately noticed when the player walks into the room, maybe because the object is small or hidden. In that case, you might want to set its suppressAutoSeen property to true.

You can also decide if and when an object gets listed in room descriptions, contents of containers/surfaces, or in a player's inventory. Set the object's isListed, isListedInContents, and isListedInInventory to return true or nil as appropriate.

Likewise, you can hide an object from being discovered accidently via use of the word ALL. The thing's method that can do this for you is hideFromAll(action).

containerclass Container: BasicContainer (objects.t)
Container (objects.t)
  • RestrictedContainer (objects.t)
  • SingleContainer (objects.t)
  • OpenableContainer (objects.t)
    • LockableContainer (objects.t)
    • KeyedContainer (objects.t)
  • Booth (travel.t)
  • StretchyContainer (extras.t)
  • Dispenser (extras.t)
    • Matchbook (extras.t)
BagOfHolding (extras.t)
  • Keyring (extras.t)
ComplexContainer (extras.t)

Inform 6's container attribute corresponds to TADS 3's Container class, which is the generic class for things you can put other things into.

The TADS 3 library has predefined several useful subclasses of Container. A RestrictedContainer will accept some items, but not others. A SingleContainer can only contain one item at a time. And, as you'd expect, an OpenableContainer is a container that can be opened and closed, a LockableContainer is one that can be locked and unlocked without needing a key, and a KeyedContainer is one that can be locked and unlocked with the appropriate key.

A Booth is a NestedRoom that an Actor can get into and stand in, but is also a container that stuff can be put into; examples of Booths are things like walk-in closets, large boxes, shallow pits, and of course, telephone booths. A StretchyContainer's outer bulk (size) increases as more things get stuffed into it. A Dispenser is a container that offers multiple similar or identical items, e.g.: a napkin dispenser, a bowl of sugar cubes, or a box of donuts.

The BagOfHolding class isn't a subclass of Container, but is a mix-in class used for making things like a player's rucksack that can hold unrealistic amounts of stuff and makes itself extra-useful by automatically putting inventory items into itself when the player needs his or her hands free.

(Oh, and if you'd like to make a dresser or microwave oven that is both a container and a surface, look at the ComplexContainer class, defined in extras.t.)

doorclass Door: Openable, ThroughPassage (travel.t)
ThroughPassage (travel.t)
  • Door (travel.t)
    • AutoClosingDoor (travel.t)
  • SecretDoor (travel.t)
    • HiddenDoor (travel.t)

Most doors in TADS 3 will be of class Door, and typically declared in pairs, one for each side. A one-sided Door is also okay.

An AutoClosingDoor is the same as a regular Door except it closes immediately after an actor goes through it. A SecretDoor acts like a door but doesn't look like a door; it looks like a bookcase or something else. A HiddenDoor is the same as a SecretDoor but is invisible when closed.

edibleclass Food: Thing (objects.t)

TADS 3's Food class is very simple, and like in Inform 6, when you eat a Food object, you get a bland acknowledgement of the act (e.g.: "You eat the banana.") and the item goes away.

enterableclass NestedRoom: BasicLocation (travel.t)
NestedRoom (travel.t)
  • HighNestedRoom (travel.t)
  • BasicChair (travel.t)
    • Chair (travel.t)
    • BasicBed (travel.t)
      • Bed (travel.t)
      • BasicPlatform (travel.t)
        • Platform (travel.t)
          • NominalPlatform (travel.t)
        • Booth (travel.t)
  • Vehicle (travel.t)

Careful! TADS 3's Enterable class, also in travel.t, is used for enterable things (like the outsides of buildings), but in the sense that when entered, the player goes to a new location. That is not what Inform 6 means by its enterable attribute, which is typically used for beds and chairs. Objects that the player can sit on, lie down on, and stand on without leaving the current room are modelled as "nested rooms" in TADS 3.

Quick run-down: Players can sit on a BasicChair; they can sit and lie down on a BasicBed; they can sit, lie down, and stand on a BasicPlatform. Chairs, Beds, and Platforms are the same except they also have a "surface" to put other things on. A Booth is an enterable container.

A HighNestedRoom is an elevated sublocation like a balcony or tree branch. A Vehicle is, um, a vehicle, e.g.: wheelchairs, horses, flying carpets, and cars. A NominalPlatform is normally used only by NPCs, and is an invisible psuedo-sublocation like "standing behind the counter".

femaleThing.isHer (en_us.t)

To declare something as female, set its isHer property to true.

Note that Thing.isHer is defined in the en_us.t file, not thing.t, because object gender is language specific. Also note that isHer only has to return a boolean value; it's okay to set it to a function that returns a boolean, if you need to.

generalnot applicable

In Inform 6, the general attribute has no predefined meaning. Authors are free to use it to mean any boolean concept that they might want to stick on an object; the most typical meaning is something like has-the-puzzle-associated-with-this-object-been-done-yet. For example, suppose a sofa has a coin hiding it in, which can be found if the player searches the sofa. You don't want the coin found every time the sofa is searched, so you might write the Inform 6 code as:

if (sofa hasnt general) { give sofa general; move coin to player; "You found a coin!"; }

But experienced Inform 6 authors avoid using general. Instead, they create a new property with a meaningful name. For example:

if (~~sofa.was_searched) { sofa.was_searched = 1; move coin to player; "You found a coin!"; }

Likewise, TADS 3 authors should also use meaningful property names:

if (!sofa.wasSearched) { sofa.wasSearched = true; coin.moveInto(gActor); "You found a coin! "; }

lightThing.brightness (thing.t); class LightSource: Thing (objects.t)
Lightsource (objects.t)
  • Flashlight (objects.t)
  • Matchstick (extras.t)
  • Candle (extras.t)

In Inform 6, any object can give off light; it's either lit or not. In TADS 3, any object can give off light but at different levels of illumination, from 0 to 4. For conversion purposes, convert "has ~light" to "brightness = 0" (which is pitch dark), and convert "has light" to "brightness = 3" (which is medium light, strong enough to read and search by).

For lit rooms, use either the Room or OutdoorRoom class as appropriate. For unlit rooms, use DarkRoom.

For lightsources like the ubiquitous lanterns, flashlights, and candles, use the LightSource class or one of its subclasses.

lockableclass Lockable: Linkable (objects.t)
Lockable (objects.t)
  • IndirectLockable (objects.t)
  • LockableWithKey (objects.t)
    • KeyedContainer (objects.t)
  • LockableContainer (objects.t)

For lockable objects, use the Lockable class or one of its subclasses. For example, if you want to define a lockable door that needs a key, you could make up a KeyedDoor class, e.g.: class KeyedDoor: LockableWithKey, Door

locked???.initiallyLocked (???.t); Lockable.isLocked(), .makeLocked(stat) (objects.t)

Authors should pretend that the Lockable.isLocked_ property is private and not use it directly. In your object's definition, set its initiallyLocked field to true or nil as appropriate. Then use the makeLocked(stat) method to lock or unlock the object, and use the isLocked() method to test whether the object is locked or not.

maleThing.isHim (en_us.t)

To declare something as male, set its isHim property to true.

Note that Inform 6 assumes that animates are male, but TADS 3 makes no such assumptions about its Actors. Also note that Thing.isHim is defined in the en_us.t file, not thing.t, because object gender is language specific. Finally, note that isHim only has to return a boolean value; it's okay to set it to a function that returns a boolean, if you need to.

movedThing.moved (thing.t)

A thing's moved property is either true or nil, depending on whether the thing was moved or not from its initial location. Pretty straightforward, and I assume that the policy with this property is look-but-don't-touch. That is, don't set the value yourself, but feel free to test it.

neuterThing.isIt (en_us.t)

By default, all TADS 3 objects are neuter. Thing.isIt is defined with a function that returns false if either isHim or isHer returns true, or true if both isHim and isHer return false. To explicitly declare something as neuter, set its isIt property to true.

Note that you can make an object, such as a cat, both female and neuter by explicitly setting both its isHer and isIt properties to true. Also note that Thing.isIt is defined in the en_us.t file, not thing.t, because object gender is language specific.

onOnOffControl.isOn, .makeOn(val) (objects.t)

Use the makeOn(val) method to turn the object on or off, and use the isOn property to test whether the object is on or off.

openBasicOpenable.initiallyOpen, .isOpen(), .makeOpen(stat) (objects.t)

To define an openable object as open, set its initiallyOpen property to true. If the object is linked to another object, e.g.: the two sides of a door, you only need to set the master side as open, and game initialization will take care of the slave side.

Authors should pretend that the BasicOpenable.isOpen_ property is private and not use it directly. Instead, use the makeOpen(stat) method to open or close the object, and use the isOpen() method to test whether the object is open or closed.

openableclass BasicOpenable: Linkable (objects.t)
BasicOpenable (objects.t)
  • Openable (objects.t)
    • OpenableContainer (objects.t)
      • LockableContainer (objects.t)
      • KeyedContainer (objects.t)
    • Door (travel.t)
      • AutoClosingDoor (travel.t)
  • SecretDoor (travel.t)
    • HiddenDoor (travel.t)
pluralnameThing.isPlural (en_us.t)
properThing.isProperName, .isQualifiedName (en_us.t)

If your object has a proper name, like "Richard", set its isProperName property to true. If your object has a qualified name, like "Richard's pants", set its isQualifiedName property to true. (Frankly, I'm not sure what the practical difference between the two are, since the default definition of isQualifiedName is whatever isProperName is, and only isQualifiedName seems to get tested anywhere.)

Note that you can tweak the effect of isProperName and isQualifiedName by explicitly defining the object's theName or aName properties, which are normally calculated for you. For example, if your item's name is "Metro Opera House", you might set its isProperName = true (to avoid 'a Metro Opera House') but also set theName = 'the Metro Opera House'.

sceneryclass Decoration: Fixture (objects.t); class Distant: Fixture (objects.t)

Most scenery objects in Inform 6 would likely be defined with either the Decoration or Distant classes in TADS 3. See the entry on the static attribute, below.

scored??? (score.t)

Hm. In Inform 6, the scored attribute means that, for an object, OBJECT_SCORE points are awarded when it's taken for the first time, and for a room, ROOM_SCORE points are awarded when it's visited for the first time. TADS 3's library doesn't define that exact mechanism, but it shouldn't be very difficult to add it in.

staticclass Fixture: NonPortable (objects.t)
NonPortable (objects.t)
  • Fixture (objects.t)
    • Component (objects.t)
    • Decoration (objects.t)
      • Unthing (objects.t)
    • Distant (objects.t)
  • Immovable (objects.t)
    • Heavy (objects.t)
Intangible (objects.t)
  • Vaporous (objects.t)
  • SensoryEmanation (objects.t)
    • Noise (objects.t)
      • SimpleNoise (objects.t)
    • Odor (objects.t)
      • SimpleOdor (objects.t)

I've often been confused on the exact difference between Inform 6's static and scenery attributes since both are used to designate objects as untakeable. TADS 3 uses several classes for untakeable objects, which differ in why the object is untakeable.

Fixtures are for things that obviously can't be taken or moved (like carpeting or a lamppost), and Immovables are things that might be movable but aren't (like furniture). A Heavy object is exactly like an Immovable one except that the Heavy object says it's too heavy when the player tries to take or move it.

Components are for parts of a larger object, like the controls on a machine. Decorations exist only to be examined; all other actions are rebuffed with "the object is not important". Unthings are for absent objects, so the player can refer to objects that aren't physically present.

Then there are the Intangibles, which by default can't be interacted with at all on the grounds that all such interactions are illogical. Subclasses like Vaporous, Noise, and Odor will perhaps allow you to examine, listen to, or smell them. SimpleNoises and SimpleOdors are for ambient and sourceless noises and odors.

(Note that hiding an object from room, contents, and inventory listings is an issue that can be tweaked separately for any object. You can also tweak whether or not an object is included in a reference to ALL; see hideFromAll(action) (thing.t).)

supporterclass Surface: BulkLimiter (objects.t)
Surface (objects.t)
  • Chair (travel.t)
  • Bed (travel.t)
  • Platform (travel.t)
    • NominalPlatform (travel.t)
ComplexContainer (extras.t)

Inform 6's supporter attribute corresponds to TADS 3's Surface class: a thing that can have other things on top of it. For example: tables, dinner trays, and floors are Surfaces.

Chairs, Beds, and Platforms are special surfaces that the player can also "enter", that is, they are also "nested rooms". You can sit on a Chair; you can sit or lie on a Bed; you can sit, lie, or stand on a Platform.

(Oh, and if you'd like to make a dresser or microwave oven that is both a container and a surface, look at the ComplexContainer class, defined in extras.t.)

switchableclass OnOffControl: Thing (objects.t)
OnOffControl (objects.t)
  • Switch (objects.t)
    • Flashlight (objects.t)
talkableclass SenseConnector: MultiLoc (sense.t)

Inform 6's talkable attribute is used for things like telephones and walkie-talkies, that is, things that aren't animates but you can still talk to them. TADS 3 doesn't have a Talkable class, and as far as I can tell, the library only lets you talk to Actors. And—I'm guessing here but it sounds right—you model the telephone or walkie-talkie with a SenseConnector, placed in both actor's locations. You'll want to define a new subclass of Material that is transparent to sound but opaque to all other senses, say "telephony", and use that as the SenseConnector's material.

Another approach may be to have the actor physically present, but invisible to most senses. For example, say you want the player to talk to God. Define a soundlike sense called prayer and add prayer to both the player's and God's communicationSenses. God can invisibly follow the player wherever he goes, and they could talk to each other at any time via the prayer sense.

transparentMaterial.seeThru (sense.t); BasicContainer.material (object.t)

The proper use of Inform 6's transparent attribute is to mark a container as transparent, such as a display case, in order to let the player to see the case's contents even when the case is closed. The reason that the container is transparent is because it's made of a transparent material such as glass, but Inform 6 doesn't include the notion of glass in its object model. TADS 3 does.

By default, all objects in TADS 3 are made of "adventium", which is opaque to all basic senses (sight, sound, smell, and touch). Other materials, such as glass, paper, fine mesh, and coarse mesh, are also predefined as either opaque or transparent to the appropriate senses. So all we really need to do is select (or define) an appropriate material, and define the container as made from that, e.g.: displayCase.material = glass

An common but improper use of Inform 6's transparent attribute is for non-containers that have subcomponents, e.g.: a TV with a screen, channel dial, and on/off switch. Inform 6 authors do this because Inform 6 doesn't include the notion of components in its object model. TADS 3 does. Use the Component class (defined in objects.t) for components.

(Note: None of this has anything to do with looking through a window or a glass door, in either language. For that, you need to use one of them thar newfangled SenseConnector objects; see sense.t.)

visitedThing.seen (thing.t); Actor.seenProp, .hasSeen(obj), .setHasSeen(obj) (actor.t)

Inform 6 uses the visited attribute to remember if the player has seen a room or not. TADS 3 uses the Thing.seen property to remember if the player has seen any object, including rooms.

Furthermore, TADS 3 lets you declare a new seen-property for any of your NPCs, so TADS 3 can remember what they've seen too. For example, your definition of Bob can declare “seenProp = &bobHasSeen”, and then Thing.bobHasSeen can be set and tested for what Bob has seen.

Authors should pretend that the Thing.seen property and any seen-properties declared via Actor.seenProp are private and not use them directly. Instead, use the setHasSeen(obj) method to set the appropriate seen-property and the hasSeen(obj) method to test it.

[NOTE: Doubt has crept in. With the existence of SenseConnectors to let one see into another room, is "seen" really the same as "visited"? Possibly. The definition of Thing.lookAroundWithin contains the code if (!actor.hasSeen(self)) { verbose = true; } to force a long room description, whether we're in Verbose mode or not.]

workflagnot applicable

Unless you wrote an Inform 6 library extension or your game needed every attribute you could get, you probably didn't use workflag very much, if at all. The typical use of workflag is to print a list of some objects; for example, you'd objectloop through several game objects, set the workflag to the ones you wanted to print, then loop through them all again to make the actual list while clearing the workflags.

But, of course, you wouldn't do that in TADS 3; there's a handy Lister class for making printed lists (see lister.t). And if you needed to make a list for some other reason, you'd create a temporary list with a statement like “local objList = [];” and add objects to it with a statement like “objList += obj;”, and you'd avoid the nuisance of looping through the larger set twice and not need a workflag at all.

wornWearable.isWorn(), .makeWornBy(actor), .isWornBy(actor) (objects.t)

In Inform 6, the worn property was reserved for the PC: only the PC can wear clothes and NPCs had to fake it. In TADS 3, all the actors can wear clothing. Yay equality.

Authors should pretend that the Wearable.wornBy property is private and not use it directly. Instead, use the makeWornBy(actor) method to set who's wearing the object, and use the isWorn() and isWornBy(actor) methods to test whether the object is worn at all by someone, or worn by a specific actor, respectively.