default conversation topic printed twice (adv3lite)

I’m in a room with an NPC. I say hello to initiate the conversation, then I tell the NPC to go take a flying…or anything else for which there is no defined topic.

The game responds by printing the text of the DefaultConversationTopic twice…

I’ve tried trapping the action in dobjFor(Te3ll) and dobjFor(TellTo), then iobjFor(…) and even tried Doer ‘tell’, Doer ‘tell Actor’, and Doer ‘tell Actor to’ but was not able to land on a breakpoint in any of these.

Here’s the code…

[code]#charset “us-ascii”

#include <tads.h>
#include “advlite.h”

versionInfo: GameID
IFID = ‘445C38A3-AD1B-4729-957A-F584600DE5C1’
name = ‘test’
byline = ‘by Jerry Ford’
htmlByline = ‘by
Jerry Ford

version = ‘1’
authorEmail = ‘Jerry Ford jerry.o.ford@gmail.com
desc = ‘Testing default conversation topic.’
htmlDesc = ‘Testing default conversation topic.’

;

gameMain: GameMainDef
initialPlayerChar = me
paraBrksBtwnSubcontents = nil

;

me: Actor ‘me’ @room
“The main man.<.p>”
isHim = true

person = 2

;
otherPerson: Actor ‘other person’ @room
“The other person. <.p>”
;

  • HelloTopic
    “Hello, you say. \b
    The other person does not respond.<.p>”
    ;
  • DefaultConversationTopic
    “The other person does not answer you. The other person doesn't say much,
    ever. <.p>”
    ;

room: Room ‘room’
“In the room. <.p>”

;
[/code]

Jerry

Okay, I see what is happening (though not why or how to stop it). The parser is viewing tell Actor to do something as two commands—tell actor is processed as one command and, oddly, to do something gets sent through the parser as a command on its own.

At least, a breakpoint on the default topic’s text output code gets hit twice. The first time, gCommand.action.topics is a list containing the actor object of the character being addressed, the second time it is a list of topic objects containing the words that follow the to.

So, I have implemented a kludge that seems to work (not extensively tested, but looks like I could iron out any wrinkles that might arise, with some concerted effort).

[code]+ DefaultConversationTopic
“<>”

getText()
{
    if(gCommand.action.topics != nil)
    {
        if(gCommand.action.topics[1].ofKind(Actor))
            "You look the other person in the eye as you speak. ";
        else
            "The other person does not answer you. The other person doesn\'t say much,
            ever. <.p>";
    }
}

;
[/code]

The reason for dividing it into two strings, one for each action, rather than just skipping text output for one of them is to avoid a blank response if the command entered is tell other person. It’s a nonsensical command, but that doesn’t mean it won’t ever get entered.

Jerry

I think I’ve figured out what’s going on here. This only occurs when you try to give a command to an NPC that the library doesn’t recognize as a valid action, when you’re already in conversation with that NPC. Since the parser can’t make sense of the command, it tries to run it as a SAY command. Effectively, then the command:

TELL OTHER MAN TO FLY

Is first being translated into

THE OTHER MAN, FLY

And then, since FLY isn’t a valid command, it’s then translated into two SAY commands separated by the comma:

SAY THE OTHER MAN
SAY FLY

Which is why you’re seeing the default response twice.

I’ll upload a fixed version to GitHub in due course, but in the meantime if you want to try applying the fix yourself, it’s at around line 380ff in parser.t. The test:

 if(gPlayerChar.currentInterlocutor != nil
                       && cmdLst.length == 0 
                       && Q.canTalkTo(gPlayerChar,
                                       gPlayerChar.currentInterlocutor))

Needs to be expanded to:

 if(gPlayerChar.currentInterlocutor != nil
                       && cmdLst.length == 0 
                       && Q.canTalkTo(gPlayerChar,
                                       gPlayerChar.currentInterlocutor)
                       && str.find(',') == nil)

The additional test prevents the input being interpreted as a SAY command if it contains a comma. This may turn out to be over-restrictive, but it even if it errs a bit too much on the safe side it’s probably a sensible change to make. If the player enters anything containing a comma it probably shouldn’t be interpreted as an implicit SAY command (since TELL X TO FOO is treated as equivalent to X, FOO, this will also catch this case).

Didn’t work.

I changed the code at line 379-382 to add the check for a comma and ran it, still get the double text.

The line you said to change is inside an if statement that starts at line 366…

                if (cmdLst.cmd == nil
                    && firstCmd)
                { ...

A break point at that location shows cmdList.cmd to contain a TellTo command object, so the if test fails and the code you said to change does not get hit.

Jerry

The fix I posted works fine for me. The first time through cmdList.cmd may well contain a TellTo object. It’s the TellTo action that then converts the player’s command into the OTHER PERSON, FLY form and then calls Parser.parse() again. It’s on that second pass through Parser.parse that the extra check should catch the text with the comma in.

As I said, it works fine when I test it, so somehow the change you’ve implemented must be different from mine.

As an alternative to trying to figure out how your change differs from mine, you could simply download my version from GitHub.

Okay, I downloaded the entire 1.1 library from github, did a clean rebuild, and I’m getting some unexpected results.

In my test bed game, using the code cited in the initial post to this thread, I now get this…

But in the actual game where this issue arose, I get this…

Neither of these use the default conversation topic text.

Either of them can work, I’m not really so concerned about a proper textual response to this situation, I just don’t want the double display.

But I don’t understand why I get one result in one implementation and a completely different one in the other when they both use the same library and none of my code is involved (it’s just a TADS response to a tell actor topic).

Jerry

I think I’d regard the response you’re getting in the test game as correct (it’s what I get too). Since there’s no FLY command in the adv3Lite library, a command like TELL BOB TO FLY makes no more sense to the parser than TELL BOB TO FLOOB. One could have a philosophical/artistic/aesthetic debate over whether it’s the parser or Bob who should reject such commands, but since in the Mercury library it’s the parser that does so, I’m not inclined to try to change it (it would probably require such a radical parser re-design, or else some horrid hack, that it’s probably not worth the effort). However, I appreciate that that’s not what you’re complaining about.

The response you’re getting in your game is, however, very odd. The response “Sorry; multiple objects aren’t allowed with that command.” comes from a recent fix that’s meant to block specifying multiple objects where only one is allowed, such as PUT THE SPOON IN THE BOX AND THE JAM JAR. But it’s far from immediatey obvious to me why should be seeing it here (though it suggests there may be an error in the logic of the tests the library is applying to come to the conclusion that a player has input a command with too many objects).

The most obvious reason why this would behave differently in your game than in the test case would be if your game had defined a FLY command. If it has, perhaps you could post a definition of your Fly Action and its associated VerbRule so I can use it as the basis of a further investigation.

One other thing you could try is experimenting with the response to TELL PUNK TO JUMP. I’m pretty sure this works okay in the test game, so if it does something odd in your game this would be an indication that there may be something else in your game we need to track down.

I have not defined a Fly action.

In the test bed…

In the game…

The A crowd of people… text is the default conversation topic text for the Actor that is defined as a crowd of people. The punk is an ActorState of that Actor object. The punk state has its own default conversation topic—The punk glares at you…, the text displayed as the response to the command fly without the tell. The topic text displayed (A crowd of people…) comes from the root Actor object, not the ActorState in effect at the time the command was entered.

You are correct. I’m okay with the current parser behavior. I’m not suggesting or requesting a drastic overhaul of the code. My original complaint was the double-down on the displayed text. That has gone away, and I thank you for your prompt action on my initial complaint.

The only item still on the table as far as I’m concerned is the relatively minor (for now, pending further testing) issue of differing behavior for the same command in different game implementations.

Jerry

The response to TELL PUNK TO JUMP in your game is just what I’d expect. It’s helpful to know you haven’t defined a FLY action, since that rules out one line of enquiry. My next question is where the response is coming from in this exchange:

I’m guessing that this is a DefaultTopic that’s being triggered while the player character is already in conversation with the punk, so that the parser is interpreting FLY as SAY FLY and responding accordingly. If that’s the case, then I get pretty much the equivalent behaviour in the test bed game.

So, the next question is whether the odd response you’re seeing only occurs in response to FLY, or in response to any nonsense command. I’m guessing that you’ll get the same odd response to TELL PUNK TO FOOZLE, but if you don’t, that might be a clue.

Otherwise, I’m at a bit of a loss without knowing what else is going on in your code. Clearly, there must be something in your game that’s significantly different from the test case, but I’m having a hard time working out what there could be that would produce this effect, other, perhaps, than a StringPreParser that’s mangling a command like TELL PUNK TO FLY into something rather different. Have you defined any StringPreParsers in your game?

Part of the puzzle here is that the parser ought to be throwing out a command like TELL PUNK TO FLY with “I don’t understand that command” before it ever gets to the part of the code that could generate “Sorry; multiple objects aren’t allowed with that command.” Somehow, it seems to think that FLY is a valid command but that it’s being used with too many objects, as if you’d typed TELL PUNK TO FLY THE PLANE AND THE GLIDER and had defined a FLY command that could take a single direct object (which you clearly haven’t).

One thing to try would be to set a break-point at around line 523 in parser.t at the line that reads:

/* get the winning Command */
 local cmd = cmdLst.cmd;

Obviously the breakpoint should be on the statement, not the comment, which is just shown to help you to locate it. Set the breakpoint just before you enter the command TELL PUNK TO FLY, then step through the single statement shown above and see what the cmd variable then holds. This may at least give us a clue how the parser is interpreting the input.

The text is the default conversation topic text for the punk actor state of the crowd actor object. When tell punk to… is entered on the command line, a string pre parser sets the actor state of the crowd Actor object to the punk state.

[code]
crowd: Actor ‘(a) crowd of people;elderly black woman asian punk gangsta
latina middle-aged white guy’ @busShelter
desc = “A crowd of people mills around, each person looking sullenly at
the ground, with an occasional furtive glance at others in the crowd,
seemingly at random. <.p>”
proper = true

dobjFor(TalkTo)
{
    check()
    {
        "You attempt to speak to someone, anyone in the crowd.
        Nobody pays attention to you. All eyes look elsewhere, nobody speaks.
        <.p>";
    }
}

;

  • DefaultConversationTopic
    “The crowd behaves as a crowd. No one person pays attention to you. Your
    attempt at conversation merely elicits a murmur that passes through the
    crowd like a wave. You cannot make out anything intelligible. <.p>”
    ;

  • HelloTopic
    “Please, you say, excuse me, can I just… <.p>”

    curiositySatisfied = nil
    ;

  • InitiateTopic @busShelter
    “<<busStopCrowdText.doScript()>>”
    ;

// ******** Actor States **************************************************

// initial bus stop crowd

  • initialBusStopCrowdState: ActorState
    name = ‘busStopCrowdState’
    specialDesc = "The enclosure offers a promise of shelter from the
    weather—wind and cold fog, or hot sun, depending. It is already full
    of people. There is no space on the bench to sit, hardly any space to
    stand without stepping on someone’s toes. \b
    You are cold. <<jobSeeker.desc>> "

    isInitState = true
    ;

// woman in crowd

  • womanState: ActorState
    name = ‘womanState’
    specialDesc = “The elderly woman wears a heavy woolen overcoat, has one arm
    through the handles of a large burlap handbag, and carries four
    overloaded plastic grocery bags in her hands. At her side, a toddler
    clutches the handbag. <.p>”
    ;
    ++ DefaultConversationTopic
    “You look directly at the woman as you speak. She looks directly
    back. She does not answer.<.p>”;
    ;

// punk state

  • punkState: ActorState
    name = ‘punkState’
    specialDesc = “Thickly muscled neck, shoulders, chest and arms,
    shaved head, wispy moustache and goatee, gangsta tattoos peek out from
    a sweatshirt on neck and hands—he’s a teenage Al Capone in
    training. <.p>”
    ;
    ++ DefaultConversationTopic
    “The punk glares at you, doing his best to project toughness and danger,
    but he does not speak. <.p>”
    ;[/code]

The verb fly is irrelevant, I just picked that (long story short, started out with take a flying f**k a a rolling doughnut until it became clear I would need to post on this forum, then shortened it to fly. But any action will do…

I have defined one StringPreParser, to pick the ActorState for the crowd
based on what is entered on the command line…

// change crowd state
StringPreParser
    doParsing(str, which)
    {
        if(str.find('black woman') ||
           gLocation == missionAtEugenia &&
           str.find('woman'))
            crowd.setState(womanState);
        else if(str.find('middle aged') ||
                str.find('white guy') ||
                str.find('white man') ||
                (gLocation == missionAtEugenia &&
                 str.find('man')))
            crowd.setState(middleAgedGuyState);
        else if(str.find('punk'))
            crowd.setState(punkState);
        return str;
    }
;

When I enter tell punk to floozle with a breakpoint at line 523, cmd gets set to a Command object, with cmd.action == TellTo, actor is the PC.

Jerry

Okay, I can’t see anything in the code you’ve posted that looks likely to be causing this weirdness, and putting your code in the test bed game doesn’t generate the odd message; so here’s my next suggestion.

When the game stops at the breakpoint and you see cmd.action == TellTo etc after taking one step through the code, try clicking the GO button, and you then should see the game stop at the same breakpoint. Now step over the code one line at a time, first noting what cmd.action, cmd.actor and cmd.verbProd are. Then step through the long line that starts

 if(cmd && cmd.verbProd != nil &&                        
                        (cmd.badMulti != nil 
                       || ...

And then look at the value of cmd.cmdErr. If it’s not nil at this point (and my guess is it won’t be from what you’re seeing), this is the point at which the parser is about to throw the error that’s giving you your weird message. To try to figure out why, before you step through the next statement that actually throws the error, please note the value of the following:

cmd.verbProd
cmd.badMulti
cmd.verbProd.dobjMatch
cmd.verbProd.dobjMatch.grammarTag (if cmd.verbProd.dobjMatch is not nil)
cmd.dobjs.length
cmd.verbProd.iobjMatch
cmd.verbProd.iobjMatch.grammarTag (if cmd.verbProd.iobjMatch is not nil)
cmd.iobjs.length

For what you’re seeing (assuming cmd.cmdErr is not nil at this point) some combination of the above must have values that shouldn’t be there. These may provide a clue about what’s going on here.

EDIT: Or it may be getting to the point where the best way forward is for you to send me a copy of your project so I can poke around in it myself to try to figure out what’s going on here.

cmd.action == AskAboutImplicit
cmd.actor == jobSeeker // the PC
cmd.verbProd == predicate(AskAboutImplicit)

cmd.cmdErr == BadMulitError

cmd.verbProd == predicate(AskAboutImplicit)
cmd.badMulti == IndirectObject
cmd.verbProd.dobjMatch == nil
cmd.verbProd.dobjMatch.grammarTag (if cmd.verbProd.dobjMatch is not nil)
cmd.dobjs.length == 0
cmd.verbProd.iobjMatch == topicPhrase(main)
cmd.verbProd.iobjMatch.grammarTag ‘main’
cmd.iobjs.length 2

On its way.

Jerry

Zipped and sent to your ox.ac address. If for some reason it doesn’t arrive or you prefer getting it somewhere else, email the correct contact info to me (jerry.o.ford@gmail.com) and I’ll resend.

Jerry

Okay, project received. I’ll take a look at it and get back to you in due course once I’ve figured out what’s going on. Your report of values at the breakpoints in your previous post have given me a couple of clues.

EDIT: AHA! The clues did indeed work out as I thought. Now I’ve got a pretty good idea what’s going on.

The TellTo action is meant to convert TELL X TO FOO into X, FOO, which is then reparsed to create the command given to X. To do this it uses the theName property of X.

In your game, crowd.theName = ‘a crowd of people’.

So, what’s happening is that the command TELL PUNK TO FOO is being translated into A CROWD OF PEOPLE, FOO, which then looks to the parser like an ImplicitAsk command which it can’t cope with.

The problem is with the way you’ve defined your crowd object:

crowd: Actor '(a) crowd of people;elderly black woman asian punk gangsta

It’s the brackets round ‘a’ that’s confusing the parser here (and I’m not sure why they’re there). If you define it thus, without the brackets:

crowd: Actor 'a crowd of people;elderly black woman asian punk gangsta

Or alternatively, which is what you may have meant, as:

crowd: Actor '() crowd of people;elderly black woman asian punk gangsta

You’ll find the problem goes away.

Live and learn. :slight_smile:

It does indeed seem to work better without the parens.

Thanks for your help.

Jerry

Great; I’m glad that’s sorted out your immediate problem. I’d like to probe a bit deeper, though, once I get the time to try to work out why the problem manifested itself in quite the way it did, in case it’s symptomatic of some deeper problem in the library (I understand how the TELL TO command was being misinterpreted as an implicit ASK ABOUT command, but I’m still not entirely sure why that triggered the particular response you saw). So I’ll hang on to your game code a little longer as a test case to investigate further. There’s also a small tweak I can obviously make to make this problem a bit less likely to recur (though it may not be possible to exclude it entirely).

I suspect the insoluble case will be the attempt to give a command to an NPC whose name resembles a valid command. For example, if a game author insists on having an NPC called Take Me, then an attempt to give an order to that NPC (whether via TAKE ME, TAKE BALL or TELL TAKE ME TO TAKE THE BALL will always be interpreted by the parser as the command TAKE ME followed by the command TAKE BALL. This is unlikely to occur much in practice, and is simply a limitation that game authors will have to live with. The problem you encountered was effectively a less obvious form of this, which a small tweak should make even less likely to occur (the A at the start of an NPC’s name being taken as the abbreviation for an implicit ask command). It could still occur, however, with an NPC called “A Conan Doyle” or the like, however (here using the initial ‘A’ in place of the full name ‘Arthur’). Maybe all that can be done to avoid that is to put a warning in the documentation not to give an NPC a name that the parser could take as a valid command, but even then tricky cases could occur. For example if a game about angling contains a character called Michael Fish, and the author also implements a custom FISH command, the parser could well choke over commands like FISH, FISH IN LAKE; whether such a case is curable I’m not sure; I’ll have to investigate further.