TADS3: Updating the location banner.

This seems like it should be a lot simpler than it looks.

Basically, I have a number of subsequently-firing events that are supposed to shuffle the player around several rooms before he or she regains control. Simple enough to implement using moveIntoForTravel(). However, the built-in upper banner in HTML Tads (the one that shows location, turns and exists) only updates its location when the string of events stop firing instead of when the moveIntoForTravel() gets called.

Basically, this happens:

Banner displays location “Room1”
EVENT1
EVENT1’s code triggers moveIntoforTravel(Room2);
EVENT2
EVENT2’s code triggers moveIntoforTravel(Room3);
Player regains control.
Banner displays location “Room3”

The problem is that while the EVENT2 text was on display, the player had been moved to Room2, but the location display still showed Room1. It’s only after the events conclude that the display is refreshed to show Room3.

This is admittedly a minor aesthetic issue, but is there a way to forcibly update the display mid-event like I would call updateContents() on a regular banner?

I’m lazy. If I were faced with this problem, I’d just write a longish text description of the events without actually moving the player into the intermediate room(s). I’d only move the player into the final room at the end of the text dump.

I realize some players don’t like text dumps, but that’s what you’re doing in any case, so why sweat the small stuff?

The actual (code-triggered) movement is not the problem. The fact that the mid-event movement does absolutely nothing visible is sort of the issue. The scene has the character move to Room2, immediately view a brief event and then immediately being moved to Room3 before he can regain control.

The problem is that throughout the whole thing, the location being shown in the upper banner is that of Room1, which changes to Room3 upon event conclusion. The short scene that happens within Room2 is shown while the banner still displays Room1 as the location.

The solution to this sounds like it’d be refreshing the upper banner mid-event, but I have no idea what triggers that. I don’t even know where the code for that thing is stored.

There’s an article called “The Banner Window Display Model” in Part VI of the System Manual. That would be the place to start looking.

But perhaps you’re not understanding my suggestion. I would just write the entire scene as output text. Don’t move the PC to Room2 at all.

There’s few ways to do this in Tads 3. (Skip to the last suggestion here for my best solution to all this please - otherwise see some of my other ideas on handling this below).

If you just want an NPC to give the player a brief guided tour of different starting rooms, there’s always the GuidedTourState (see the Tads 3 manuals for info on this).

One way is a real time based daemon to force updates periodically another is to flush the output / window (flushOutput(), etc.). Mike Roberts suggested some other command (which eludes me at present) in case you’re finding update bugs which turns things off and on for priority. It bypasses the normal order of events but I can’t seem to locate it in my notes.

Also maybe: inputManager.processRealTimeEvents(true) [OR temporarily set to nil, then do your code, then back to true - something like that].

Also, for more insight into controlling the flow of input & display, do take a really good look at the pauseForMore(freezeRealTime) method in file: input.t

If I were you, though, I’d script it all out and after moving the player to one of the rooms, do an inputManager.pauseForMore(true)… and then move the player on to the next room (gPlayerChar.moveInto(secondRoom) for example) followed by a gPlayerChar.lookAround(true) command to force a look action.

If you want to get really fancy and object oriented about it, I once scripted up a robot to pick up the player and carry them from room to room on the NPC actor’s idleTurn method. This used a fancy “being carried” posture and also restricted gAction commands so the player could not “leave” the high nested room “platform” of the robot (i.e. being carried in the robot’s arms). The rooms would be displayed one by one, not as the player moved there, but as the robot carrying the player moved there, until finally the robot dropped the player.

beingCarriedByClinker: Posture
  postureDesc = "being carried by <<Clinker.isProperName ? 'Freddy the Robot' : 'Clinker'>>"
  tryMakingPosture(loc) { return tryImplicitAction(SitOn, loc); }
  setActorToPosuture(actr, loc) { nestedActorAction(actr, SitOn, loc); }
  msgVerbIPresent = 'carried in'
  msgVerbIPast = 'carried in'
  msgVerbTPresent = 'carried in'
  msgVerbTPast = 'carried in'
  participle = 'being carried by ' + (Clinker.isProperName ? 'Freddy the Robot' : 'Clinker')
;

// NOTE: This platform is in the robot NPC with the player put inside it if the robot catches the player (you could modify and move the platform itself from room to room if you wished to though)
carryingPlatformTwo: OutOfReach, Container, Component
     name = 'Clinker\'s arms'
     properName = 'Clinker\'s arms'
     vocabWords = '(Freddy\'s) (Clinker\'s) (robot) arms'
     desc = "You see nothing unusual about them. "
     isListed = nil
     isPlural = true
     location = Clinker
     defaultPosture = beingCarriedByClinker
     obviousPostures = [beingCarriedByClinker]
     allowedPostures = [beingCarriedByClinker]
     roomBeforeAction{
          if((gActor == gPlayerChar) && (gPlayerChar.location == carryingPlatformTwo)){
               if(gAction.ofKind(TravelAction)){
                    if(gActionIs(West) ||
                  gActionIs(East) ||
                  gActionIs(South) ||
                  gActionIs(North) ||
                  gActionIs(Out) 
                  ){
                    reportFailure('<q>You will not escape,</q> <<Clinker.robotName>> says keeping hold of you in his arms. ');
                    exit;
                    }
               }else if gActionIs(Stand){
                    reportFailure('<q>You will not be permitted to move until I put you down,<q> <<Clinker.robotName>> says
                         keeping you in his arms. ');
                    exit;
               }else if gActionIs(Sit){
                         reportFailure('<q>You can not leave until you have been delivered to the programmed destination,<q> <<Clinker.robotName>> says
                         keeping you firmly in his grasp, carrying you high in the air. ');
                    exit;
               }else if (gActionIs(Xyzzy)){
                         reportFailure('<q>You will not try and trick me with your magic words,</q> <<Clinker.robotName>> says keeping 
                                   you in his arms. ');
                         exit;
               }
          }
     }
;

You could also just move the player to a room without a string name and script out the movement via pure dialogue and inputManager.pauseForMore(true)… this would imitate what is supposed to be happening (the player moving from room to room) without it actually happening. i.e. there would be a bunch of dialogue followed by pauses… followed by one gPlayerChar.moveIntoForTravel(finalRoom).

Also, you could have code in each room “catch” the entering player and process what happens, bumping the player then on to the next room and so on until the final room is arrived at. See method: enteringRoom(traveler) in file travel.t

You could also do a mix of dialogue presentation, pauses, and this:

   local tokList = Tokenizer.tokenize('go north'); // the player moves north (by force) to the next room...
   executeCommand(gPlayerChar,gPlayerChar,tokList,nil);  // nil = start of sentence (true/nil)

… which pretends the player executed the command “go north” (this takes up a turn in game by the way - if that matters)… but… it would display the room description as usual before moving on to the next room by another “forced” command in this way.

It really depends on how complicated and/or object oriented you want to get. In some cases I myself just put the player in a nameless room and script out all the dialogue, informing them what happens next before dumping them off at the final location. In other cases I get picky and want it to play out in code as it would in real life (i.e. my robot carrying the player example).

If I knew exactly how you were bumping the player from room to room I should be able to write up an object oriented workable solution for you. For example. A wizard non-player character keeps running after the player from room to room teleporting the player off to some other room (and then catching up there only to re-cast the teleport spell, sending the player on to another room). Really depends on how much control you want the player to have during all this, of course. Can the player use the “undo” command? “Restore”? etc.? Or is the player more or less “railroaded” on to the final room?

Best Solution:
If the player can’t move or go anywhere, then simple. Put them in a high nested platform with no exits and inform them on every beforeAction gAction catch (in the platform) where they try to move in any direction that they are unable to at this time (i.e. “Your leg muscles seem frozen in place. You are rooted on the spot by magic!”) Then, just simply move the platform itself (with the player in it) from room to room. Then dump the player off in the final room and send the platform off to nowhere. In this case the platform should have no description or name and the player’s posture should be restricted to, say, standing without the ability to sit down or lie down, etc. or go anywhere at all during their time inside the platform. Really, using object oriented programming this way, the platform itself represents the magical barrier keeping the player rooted and immobile, while at the same time forcing the player from room to room by moving the platform containing the player.

And… who moves the player? Why, the hostile wizard of course or the offending chest trap or whatever that just caused this effect to just go off in game. Yes, that’s, right, make a trapped chest of the Actor class if need be and have the “forcefield” (high nested platform object) be the trap. Then, on the chest’s (or wizard’s idleTurn() method) have the platform moved from room to room, perhaps forcing the player to look into “The Room of Would Be Thieves”, and “The Room of Not So Smart Dungeon Delvers”, etc. until dumping them off in “The Room of You Get What You Deserve.” Use a counter to check for what counter number should move the player on to the next room based on counter-turns after the event began. In this way the player still has some measure of control (can “look” at things in the rooms, etc. and so on, but… the NPC idleTurn method checks that counter and continues to move the player on to the next room on the list).

I can honestly guarantee you that if you handle this problem this way that your room descriptions will fire off as is to be expected in the game without a problem at all. When you run into problems like this, always think object oriented as Tads 3 is an object oriented programming language and already has easy built-in solutions to situations like this. Though if you’re firing off room moves all in one-go “boom-boom-boom” style, then you’re better off either turning off and on Tads built in banner handling (I THINK it’s inputManager.processRealTimeEvents(nil) & true plus a flushBanner(handle_) call in between… or else just putting up dialogue with inputManger.pauseForMore(true) followed by a final gPlayerChar.moveIntoForTravel(finalRoom) and gPlayerChar.lookAround(true)).

Don’t forget to include “inherited” on the idleTurn() method to catch AgendaItem stuff, etc. also:


evilWizard: Actor

  counter = 0
  counterActive = nil

  idleTurn(){ 
          inherited;

          // your code - NOTE: This could be an AgendaItem but for the sake of laziness you could just put it here instead
          if(counterActive){
              counter++;
              switch(counter){
                   case 1:
                        rootedPlatform.moveIntoForTravel(badRoomOne):
                        gPlayerChar.lookAround(true); // NOTE: it's been awhile since I did this - this command may be unnecessary and cause duplicate room description output
                        break;
                   case 2:
                        rootedPlatform.moveIntoForTravel(badRoomTwo):
                        gPlayerChar.lookAround(true);
                        break;
                   case 3:
                        rootedPlatform.moveIntoForTravel(badRoomThree);
                        gPlayerChar.moveIntoForTravel(badRoomThree);
                        gPlayerChar.lookAround(true);
                        rootedPlatform.moveIntoForTravel(OldObjectHoldingRoom);
                        counterActive = nil; // turn off the counter now that we're done with it
                        break;
                   default:
                        // do something else perhaps
              }
         }         
  }
;

…If the “wizard” actor is unncessary in the game, just have him in some restricted room somewhere. His idleTurn() method will still fire off on each turn, making him take a turn just as the player can take a turn. Then just set the counter active, etc. and move the player to the holding platform. The NPC idleTurn() method will move the player from room to room within the holding platform as you wish, displaying rooms as the player arrives there, while also restricting movement outside of the (presumably invisible) holding platform. Also, in each room you could have the roomFirstDesc script fire off, showing the dialogue you intend as the player is getting bumped around.

If you really want precise banner control then please see extension: customBanner.t (by Eric Eve)