Logo Pending

Pattern pen implementation differences

While looking into something unrelated in Space Quest 3, I noticed that the dirt on the right of the starting screen was drawn differently between DOS SCI and ScummVM. Today I looked into it a little closer, comparing SCI proper, ScummVM, SCI Companion, and SCI Viewer.

Damn, that’s some really tiny differences that you’re not gonna spot just like this. But here they are:

  • In most of them, the mound on the right looks like this:
  • Except in ScummVM, where it looks like this: (and now you know why I looked into this)
  • Below the column in the middle looks like this in SCI and SV:
  • But it looks more like this in SCI Companion and ScummVM alike:
  • The heap on the left is also mildly affected, looking like this in SCI and SV:
  • But it looks like this in SCI Companion and ScummVM:
  • And finally, SV, renowned for being Very Good At This, breaks the one rule — you don’t get to draw white on non-white:

So yeah, a slight difference in where a window border is drawn is the least of your problems.

Update: ScummVM had its pattern table corrected this week. Guess I’ll have to check out the latest nightly, huh? And yes, it does match SCI proper now. Good job everyone!

Bonus update: sluicebox suggested comparing against SCI Studio. Here you go, friend: the mound on the right looks like this in SCI Studio, a distinctive variation, and the bit under the pillar and to the left matched SCI Companion and ScummVM (past tense now),

[ , , ] 3 Comments on Pattern pen implementation differences

A key difference between SCI0/1 and SCI11

Oddly, this one’s entirely script based.

I was reminded of this when I watched one of Space Quest Historian’s videos, where he played the Space Quest 2 remake by Infamous Quests. Now, one thing he mentions about the version he plays in that video is that besides the narrator being muted so he can narrate it himself, well…

but recently Steve Alexander, who was one of the co-CEOs of Infamous Quests had a little peek around the old game files and decided to spruce it up a bit, fix some old bugs, add some you know, touch-ups here and there. Most importantly, at least according to him, replace the standard AGS font with something that looks a little more Sierra-ish. And that’s the version I’m going to play.

All well and good but then the main menu appeared and I noticed just how important this font thing seemed to be.

One thing immediately came to mind when I saw this. Notice how the button cuts into the caption? If I was a betting cat, I’d say the original version’s main font was just slightly shorter than this replacement. Also when I prepared the image I noticed each of these buttons has a different height, but that’s not the issue here — that’s just sloppy work.

This thing with the buttons cutting into the message text? It can’t happen accidentally in SCI0/1, but it can easily happen in SCI11. How is that?

In SCI1, the system scripts include a PrintD function. It’s pretty powerful on the face of it:

	"Would you like to skip\nthe introduction or\nwatch the whole thing?"
	#at 100 60
	#button "Skip it" 0
	#button "Watch it" 1
	#button "Restore a Game" 2

And that gives you this:

The function itself will track how large every item should be as they are defined, and adjust the final size of the window accordingly. Just to simplify the explanation a bit. And then it’ll return the value of a given button so the game knows how to react.

(Update: SCI0 had Print which worked not entirely unlike this, and SCI1 added PrintD as a more streamlined variant with #new support, because…)

In SCI11, Print is now an object. You have to call a bunch of methods on it, in sequence, then finish with an init: call, and that will trigger its display and return the value chosen. But one important distinction is that addText:addButton:, and its ilk… don’t automatically position themselves. You have to do that yourself.

That’s why these games came with a built-in dialog creation tool, among others. It’d create code like this:

; DialogEditor v1.0
; by Brian K. Hughes
	posn:		0 28,
	font:		0,
	addText:	{bluh bluh} 71 18,
	addButton:	0 {button} 71 30,
	addButton:	1 {button} 0 0,

The Sierra programmers could then integrate that into their game scripts. But importantly, those manually-positioned buttons could overlap other controls.

(Update: … PrintD also had a dialog editor made for it. This is pretty explicitly stated in the changelogs.)

AGS dialog boxes also use manual positioning, just with a nice WYSIWYG form editor. So if you take a perfectly good (yeah right) introduction menu and change the font for one with a higher X-height and don’t adjust things… you get that SQ2 window.

Update just because: this is what the SQ4 intro menu would look like with font.001 instead. That is, with SCI1’s PrintD and its automatic layout.

[ , , , ] Leave a Comment

Space Quest 4 – Roger Wilco and the Failed Attempt at Being Cute

While playing Space Quest 4 – Roger Wilco and The Time Rippers, you travel back and forth between several different time periods, each designated by a sequel number. The introduction and ending are set in SQ4, the main plot in SQ12 – Vohaul’s Revenge II, and from there you travel to SQ1 – The Sarien Encounter, SQ10 – Latex Babes of Estros, and in an easter egg, SQ3 – The Pirates of Pestulon.

While in the SQ1 era, you revisit Ulence Flats. Literally, you arrive just after the original Roger is done there and leaves. In the SCI remake of SQ1, you actually see the time pod from SQ4 arrive right behind you when you leave the area, but SQ4 came out before the SQ1 remake so you revisit the AGI version instead. Sort of. The resolution’s all wrong, but who cares?

One thing you’ll quickly notice is that instead of the usual BorderWindow frames, this part of the game uses a custom frame meant to evoke AGI’s. It is, however, immediately clear (at least to me) that they fucked it up.

That’s one unsightly black border! Also, the window is light gray instead of white but that’s not the issue here.

If you look closely and remember what I wrote about window style bits before, you might recognize that it’s drawing a white light gray window with a red border, and then a nwTRANSPARENT window on top. What does the actual code say? Here’s Sq1Window, which is used in lieu of BorderWindow in all Ulence Flats rooms:

(class Sq1Window of SysWindow
    underBits 0
    pUnderBits 0
    bordWid 3
  (method (dispose)
    (SetPort 0)
    (Graph grRESTORE_BOX underBits)
    (Graph grRESTORE_BOX pUnderBits)
    (Graph grREDRAW_BOX lsTop lsLeft lsBottom lsRight)
    (super dispose:)
  (method (open &tmp temp0 temp1)
    (SetPort 0)
    (= color gColor)
    (= back gBack)
    (= temp1 1)
    (if (!= priority -1) (= temp1 (| temp1 $0002)))
    (= lsTop (- top bordWid))
    (= lsLeft (- left bordWid))
    (= lsRight (+ right bordWid))
    (= lsBottom (+ bottom bordWid))
    (= underBits (Graph grSAVE_BOX lsTop lsLeft lsBottom lsRight 1))
    (if (!= priority -1)
      (= pUnderBits (Graph grSAVE_BOX lsTop lsLeft lsBottom lsRight 2))
    ; Draw the background
    (Graph grFILL_BOX lsTop lsLeft lsBottom lsRight temp1 back priority)
    ; Draw the border
    (Graph grDRAW_LINE (+ lsTop 1) (+ lsLeft 1) (+ lsTop 1) (- lsRight 2) global131 priority)
    (Graph grDRAW_LINE (- lsBottom 2) (+ lsLeft 1) (- lsBottom 2) (- lsRight 2) global131 priority)
    (Graph grDRAW_LINE (+ lsTop 1) (+ lsLeft 1) (- lsBottom 2) (+ lsLeft 1) global131 priority)
    (Graph grDRAW_LINE (+ lsTop 1) (- lsRight 2) (- lsBottom 2) (- lsRight 2) global131 priority)
    (Graph grUPDATE_BOX lsTop lsLeft lsBottom lsRight 1)
    ; Open a logical window for the contents to be drawn into
    (= type 129)
    (super open:)

And here’s the trick: not only does it open a logical window after drawing it, but it opens one with the wrong style.

Fix idea #1. Open the logical window before drawing it.

(method (open &tmp temp0 temp1)
  (= type 129)
  (super open:)
  (SetPort 0)
  (= color gColor)

That ain’t it. Not only does it put the contents of the window in the corner of the screen (because we’re using SetPort wrong) but when the window closes, the frame appears behind it:

No, no. The way I solved it was like this:

(method (open &tmp port temp1)
  ; temp0 was unused so we're taking it for proper SetPorting.
  (= color gColor)
  (= back gBack)
  ; Set our type to ONLY wCustom, not wCustom|wNoSave, and open.
  (= type 128)
  (super open:)
  ; Nothing will have appeared because wCustom don't draw anything, but a port has been set up!
  ; Switch to drawing on the whole screen but also *save the window's port*.
  (= port (GetPort)) ; 2022-07-29 update, hi sluicebox
  (SetPort 0)
  (= temp1 1)
  ; ...
  (Graph grUPDATE_BOX lsTop lsLeft lsBottom lsRight 1)
  ; Reset to the window's port.
  (SetPort port)

And that fixes everything!

No extra black border, no misplaced contents, and no leftovers! If you have the CD-ROM edition, you may be able to drop this file in the game’s directory and rename it to 706.scr.

Now, eagle-eyed viewers might notice that in the very broken screenshot, the text was drawn on a white background, while the window itself is light gray. This is because each graphical port in the system tracks its own color settings, among other things. A logical window is a kind of port, as is the screen as a whole. Since the broken version called (SetPort 0) after (super open:), its contents were drawn on the screen port, not the window’s. And the screen port, by default, has a white background color.

Shoutouts to the Space Quest Historian for putting me on this track with his playthrough video. And as such, see ya on the Chronostream, time jockeh!

[ , , ] 2 Comments on Space Quest 4 – Roger Wilco and the Failed Attempt at Being Cute

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...
  [logInput 200]
  [logWritten 200]
  ; Remember: these are 16-bit values, so 400 characters.
(instance sCaptainsLog of Script
  (method (changeState newState)
    (switch (= state newState)
        (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?
          ; 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...
              (== (StrAt @logInput (- cursor 1)) 32) ; a space...
              (== (StrAt @logInput (- cursor 1)) 0) ; or the end of the text.
        ; 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)
        ; 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:

  [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.
    (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.

  (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)
  (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.
    (= cycles 10) ; We've reached the 'u'. Wait and cue up state 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)
    (= typePos 153) ; Reset to where the 'P' was...
    (= cursor 29) ; ...and skip to the 'A' in "A Little"
    (= cycles 10)
  ; The rest is much the same as state 15...
  (typewriter play:)
  ; ...except for this check.
  (if (< (++ cursor) (StrLen @subtitle))
    (-- state)
    (= cycles (Random 3 5))
    (= cycles 10)
[ , , , ] Leave a Comment

Palette Cycling in Space Quest 4

It’s a followup.

Here’s Roger falling through the chronostream in the introduction:

And here’s the EGA release’s take on that shot:

I immediately thought, of course, the EGA release wouldn’t support palette cycling effects. That’s the official EGA release, with an ega320.drvnot the later ones with ega640.drv. Totally different.

But what if I were to make SQ4 VGA use ega640.drv? What would happen? Would the background remain static?

Keeping the answer under the fold for all you epileptic viewers out there.

Continue reading “Palette Cycling in Space Quest 4”

[ , , ] Leave a Comment