Defining complex NPCs with arms, legs as separate parts

Hi,
Newbie in TADS here, playing around to see what I can do. Loving the flexibility so far!

My current goal is to define “body parts” for a Person class so I can track stats like injuries in arms, legs and head separatedly.
Ideally I would be able to do commands in game like “examine my arm” or “use medikit on John’s leg”

So far I am trying to do it with nested objects, like this:

BodyPart : ComplexComponent
{    
    dobjFor(Examine)
    {
        preCond = [objVisible]
        action() { mainReport(desc); }
    }       
}

HeadPart : BodyPart
{
    vocabWords = 'head*heads'    
    name = 'head'
    desc = 'A normal looking human head'                 
}

sPerson : Person
    Head : HeadPart
    {          
    }
;

Now, couple of questions:

  1. What do you think about this way of doing it? Can I do better?
  2. How would I go in making support of commands that use my/his/her/John’s before a body part
    e.g. “examine John’s arm” or “Bandage Jane’s leg” or “Stab his leg”
    Should I define a new verb that recognizes names of NPCs and possessive adjectives? Or is there a better way of doing things that I am missing?
  3. Perhaps a better way of supporting this kind of commands would be defining verb that would iterate through Person’s BodyParts to see if there is any match? I mean command like “examine my left arm” --> would iterate through body parts until it finds a body part that has it’s name defined as “left arm”
  4. How would you approach this kind of goal?

Any feedback on what I’m doing would be great.

There is no need to do anything out of ordinary, TADS understand possessives for items owned by Actor and there is no need to manually iterate over body parts nor do any adjustments to parser. Just make them part of the actor same way as with ordinary objects. Then you can define new verbs for actions like “bandage” in the usual way. For example below is definition of actor from Mike Robert’s game Return to Ditch Day, there are only few adjustments most importantly to meetsObjHeld:

me: BagOfHolding, Actor
    vocabWords = 'doug douglas doug/douglas/mittling'
    // shortened
;

+ myHands: CustomImmovable 'left right hand/hands' 'your hand'
    "You know it like the back of your hand. "
    isQualifiedName = true

    /* our hands add no bulk, obviously */
    bulk = 0

    /* these are just here for decoration; no need to include in ALL */
    hideFromAll(action) { return true; }

    /*
     *   we're a body part, so we don't need to be held to carry out
     *   actions, even when they have an objHeld condition
     */
    meetsObjHeld(actor) { return actor == location; }

    /*
     *   The hands are here because we have at least one object in the game
     *   that's specifically wearable on one's hands.  PUT X ON HAND maps
     *   to WEAR X, but only if X is a hand-wearable.
     */
    iobjFor(PutOn)
    {
        verify()
        {
            /* only hand-wearables can be put on one's hands */
            if (gDobj != nil && !gDobj.ofKind(HandWearable))
                illogical('{You/he} can\'t put {the dobj/him} on
                    your hands. ');
        }
        action()
        {
            /* treat this as WEAR dobj */^M
            replaceAction(Wear, gDobj);^M
        }
    }

    dobjFor(Move)
    {
        verify() { }
        action() { "You move your hand around a little. "; }
    }

    cannotTakeMsg = 'There\'s no need to fiddle with your hands. '
    dobjFor(Take) { verify() { illogical(cannotTakeMsg); } }
    dobjFor(TakeFrom) asDobjFor(Take)
    dobjFor(Open) asDobjFor(Take)
    dobjFor(Close) asDobjFor(Take)
;

thanks for your answer!
After playing with the code you’ve sent I see that it works when I want to have player character with specific part.
something like the following indeed works.

+ me: Person 'john/man*men'  'John'
    isProperName = true
    isHim = true
    pcReferralPerson = FirstPerson
;

++ myHead : HeadPart
;

However, what I would like to achieve is a generalization of your proposed solution.
I mean - I would like to define a new base class, let’s say extPerson
This class would have a defined head property, so PC and all NPCs that derive from extPerson would have head parts.

This is the point I’ve run into trouble.

If I use the following definition of extPerson

class extPerson : Person, Container     
    subHead : HeadPart { }
;

And then define two characters

+ me: extPerson 'john/man*men'  'John'
    isProperName = true
    isHim = true
    pcReferralPerson = FirstPerson
;

+ jane: extPerson 'jane/woman*women'  'Jane'
    isProperName = true
    IsHer= true
;

Then, using look at my head yields this result:

The same answer with look at jane’s head

After some fiddling, I found out that
a. Both PC and NPC receive the same instance of HeadPart for some reason. This looks weird to me, since they are supposed to be different instances of the same base class, so they should receive different instances of HeadPart, no?
b. For some reason, in the use-case I described, the look verb does not recognize HeadPart as part of both PC and NPC. I am likely missing something fundamental here. (normally I would read source code in such cases, but I do not have deep enough understanding of TADS yet… :confused: )

subHead is reference to the static object HeadPart { }, therefore when you make an instance of extPerson class, you are effectivelly making a copy of extPerson which shares the same reference to the same HeadPart object. You can use perInstance macro like subHead = perInstance(new HeadPart).

Moreover subHead doesn’t mean anything for adv3 library so unless you do something with it you won’t get anything out of it. The plus sign is a shorthand notation for setting container hierarchy. You should read chapter 5 of Learning TADS 3 manual to better understand this. Using plus sign is the same as setting location property of an object. You should use container hierarchy for the body parts to make them work. Instead of declaring actor statically with all body parts you can make body parts dynamically. Use constructor of extPerson to instantiate body parts and insert them in container hierarchy bellow its actor.

Hmm… that explains the shared instances.
I guess I started hacking too soon, back to reading :laughing:

Thanks for pointing the direction! Your answer clarifies couple of things.

So do I understand correctly that verb with syntax “look at my [object in container]” would refer to correct object in correct character’s container hierarchy? (assuming I construct the hierarchy properly)

Dynamical construction of objects is definitely more advanced topic and it’s always better to start with something simple and master basics first.

TADS understands possessive qualifiers in any command and treats objects which are currently in actors container hierarchy (objects taken, worn or object part of the actor itself) and also any object explicitly marked as owned by actor wherever they are that they are in possession of actor for the purpose of parser resolution.

I see, good to know, thanks!

After some more reading and experimenting I finally got it working! :smiley:

If anyone are curious, this is what I came up with:

BodyPart : Component
    bulk = 0  
    hideFromAll(action) { return true; }
    meetsObjHeld(actor) { return actor == location; }        
    dobjFor(Examine)
    {
        preCond = [objVisible]
        action()
        { 
            //this is just to test the container hierarchy
            mainReport(self.desc + ' -> ' + self.location.name); 
        }
    }    
;

HeadPart : BodyPart 'head' 'head'
    desc = 'A normal looking human head'
    isQualifiedName = true  
;

class extPerson : Person, Container, PreinitObject          
    head = perInstance(new HeadPart)          
    execute()
    {
        head.moveInto(self);
    }   
;

I was a bit baffled why construct() wasn’t being executed, but then I saw that it executes only when the object is created with new statement. So I used PreinitObject for one-time initialization of container hierarchy.

Does this way of dynamically creating complex object makes sense?

Also, huge thanks for your help, tomasb!

Sorry about the construct, it didn’t occurred to me. Instead PreinitObject you can also use initializeThing method (don’t forget to call inherited when overriding methods) which will be called during preinit phase for every Thing derived objects, this method is used internally for similar needs.

It makes sense if you want many copies of essentially same objects. However in text adventure games you typically want unique objects with unique descriptions and unique details so typical work-flow is to use statically declared objects and their components and TADS is much more optimized on these. You won’t see many examples of dynamic objects in documentation or sample games.

I see, great to know. The missing piece of puzzle for me - I assumed that if you define an object like the following, the interpreter under the hood will dynamically create it. After you mentioned static object declarations, it was like finding a missing piece of a puzzle that immediately clicks into place :smiley:

+ me: extPerson 'john/man*men'  'John'
    isProperName = true
    isHim = true
    pcReferralPerson = FirstPerson
;

I think it is a good place to ask another question : I am thinking about procedurally generating a game world using predefined objects for locations, and some randomly generated “filler” rooms that connect between them. (it’s an end-goal, I am really far away from it :slight_smile: )

How efficient TADS in dealing with large amount of dynamically created objects? Is there any example of a large game so I can take a look at it’s source code?

I think you understand it well. Static objects are pre-created by the compiler even before the game is first executed by the interpreter. Compiled game file contains code (instructions to be executed for the virtual machine) and image of data memory where objects and variables are stored.

I don’t know of any examples of particularly large games. It depends what is large game for you. More often you’ll find games that has tens of locations and quite deep implementation (lots of objects and subobjects). TADS should deal well with any game that is in the sane range, ie. when the game is large so the player is able to explore the whole game.

Of course if you have in mind some MMORPG like game where there is entire world modelled with thousands of locations and objects then you would probably need to do some tests and probably when such world will be procedurally generated then you won’t necessarily need to generate whole world at once but just generate and keep visited locations on demand and destroy when not needed.

For me a large game means somewhere between dozens and hundreds of objects.
The end-goal of my fiddling (and I am not sure I will ever get to the end of it, but still it is nice to have a clear goal :slight_smile: ) is to do a simulation of pen & paper rpg campaign, as close as possible to an actual system, with die rolls etc. (currently I’m thinking about implementing OpenD6 as it has Open Game License)

Ok, then no need to worry, that’s normal sized game. There is no performance difference between working with statically and dynamically created objects. But you would need to assign properties to new instances manually without help of templates so the work-flow will be little different.

Good to know!

About different workflow - I am used to writing c# and c++, so manually assigning property values to new instances feels to me more natural than using templates - they feel a kind of magicky to me.