Logo Pending


What do SQ5 and LSL5 have in common?

Things being typed out in their introduction.

In Space Quest 5, the introduction has a sequence where Roger Wilco dictates a captain’s log. The text appears word-for-word, though it’s pretty easy to modify the script code and make it character-by-character. But before I can explain the intricacies of that scene, I must ensure you know how a Scriptworks.

No, not a script file, a Script object. They are in essence state machines, with a state property, a changeState method, a cue method, and two timer properties, cycles and seconds. The changeState method is the beef of it all, containing the code for each state. The cue method simply takes the current state, adds one, then invokes changeState with that value. The doit method checks if either of the two timers is nonzero, decrements them accordingly, and invokes cue when time runs out. In general, you only need to implement your own changeState. Other objects may also cue a script. A good example would be waiting for an Actor to move to a particular position. They would know which Script is waiting for them and cue them at the appropriate time. Until then, the Script sits on its thumbs.

Knowing that, let’s take a look at the captain’s log:

; Earlier in the file...
(local
  [logInput 200]
  [logWritten 200]
  ; Remember: these are 16-bit values, so 400 characters.
  page
  length
  cursor
)
 
(instance sCaptainsLog of Script
  (method (changeState newState)
    (switch (= state newState)
      (0
        (Message msgGET 104 1 0 0 page @logInput) ; Grab the current page worth of text.
        (= length (StrLen @logInput)) ; Remember how long the line is.
        (= cursor 0) ; Reset our typing cursor
        (= cycles 1) ; Near-immediately continue -- we could self-cue though?
      )
      (1
        (repeat
          ; Copy one character...
          (StrAt @logWritten cursor (StrAt @logInput cursor))
          ; ...and add a terminating zero so we don't write garbage.
          (StrAt @logWritten (++ cursor) 0)
 
          ; Stop copying if we find...
          (if
            (or
              (== (StrAt @logInput (- cursor 1)) 32) ; a space...
              (== (StrAt @logInput (- cursor 1)) 0) ; or the end of the text.
            )
            (break)
          )
        )
 
        ; Put what we've copied so far on screen.
        (Display @logWritten dsCOORD 40 10 dsCOLOR 254 dsFONT 1307 dsWIDTH 250)
 
        ; If we haven't reached the end of the text yet...
        (if (> length cursor)
          (-- state) ; rewind to state 0 so the next cue is for state 1 again!
          (= ticks 15)
        )
      )
      (2
        ; The rest doesn't matter here.
      )
    )
  )
)

If one wished to write character-by-character, you’d just remove most of the repeat:

(StrAt @logWritten cursor (StrAt @logInput cursor))
(StrAt @logWritten (++ cursor) 0)
 
(Display @logWritten dsCOORD 40 10 dsCOLOR 254 dsFONT 1307 dsWIDTH 250)
 
(if (> length cursor)
  (-- state)
  (= ticks 15)
)

That was pretty easy, all things considered. I think the more interesting example to study would be the subtitle in Leisure Suit Larry 5 – Passionate Patti does Pittsburgh a Little Undercover Work.

First, we need a few local variables and, just to keep things orderly, a procedure:

(local
  cursor
  typePos
  [subtitle 50] = "Passionate Patti does PittsbuA Little Undercover Work"
  ; Notice this: "Pittsbu" is character 22 to 29.
  [backspaces 12]
)
(procedure (typeAway &tmp [oneChar 2] char underbits)
  ; Grab the current character to type.
  (= char (StrAt @subtitle cursor))
  ; Put it in a string -- just that character and a terminating null.
  (Format @oneChar "%c" char)
 
  ; Draw it, preserving what was underneath.
  (= underbits (Display @oneChar dsCOORD typePos 160 dsCOLOR global128 dsWIDTH 7 dsALIGN alLEFT dsFONT global173 dsSAVEPIXELS))
 
  (if (and (< 21 cursor) (< cursor 29)) ; Are we typing "Pittsburgh"?
    (= [backspaces (- cursor 22)] underbits) ; Then put those underbits pointers in the backspaces array.
  else
    (UnLoad 133 underbits) ; No need for these underbits, throw 'em away.
  )
 
  (= typePos (+ typePos 7)) ; Advance our drawing cursor...
  (if (== 32 char) (= typePos (- typePos 2))) ; ...but make the space a little bit thinner.
)

Further down there’s a Script that handles the entire opening cartoon. I’ll skip the boilerplate and only show the relevant parts.

(14
  (patti setMotion: MoveTo 335 140) ; This all happens while Patti walks past.
  (= cursor 0) ; Reset our type.
  (= typePos 3)
  (larry setScript: sLarryCartoon) ; Larry reacts on his own.
  (= cycles 1)
)
(15
  (typeAway) ; Type ONE character.
  (typewriter play:) ; Make some noise...
 
  (if (< (++ cursor) 29) ; Increment the cursor -- if it's not at the 'u' in "Pittsbu" yet...
    (= cycles (Random 3 5)) ; ...randomly delay the next character...
    (-- state) ; ...and rewind as shown in SQ5.
  else
    (= cycles 10) ; We've reached the 'u'. Wait and cue up state 16.
  )
)
(16
  ; Grab the right underbits from our array and restore it.
  (Display "" dsRESTORE [backspaces (- (-- cursor) 22)])
  ; Underbits include not only pixel data but also their bounds, so that's easy.
 
  (backSpace play:)
  (if (> cursor 22) ; Are we not yet back at the space before "Pittsbu"?
    (= cycles 4) ; Then rewind at four cycles a backspace.
    (-- state)
  else
    (= typePos 153) ; Reset to where the 'P' was...
    (= cursor 29) ; ...and skip to the 'A' in "A Little"
    (= cycles 10)
  )
)
(17
  ; The rest is much the same as state 15...
  (typeAway)
  (typewriter play:)
 
  ; ...except for this check.
  (if (< (++ cursor) (StrLen @subtitle))
    (-- state)
    (= cycles (Random 3 5))
  else
    (= cycles 10)
  )
)
Like
[ , , , ]

Leave a Reply

Your email address will not be published. Required fields are marked *