“Interesting. I wonder if this is related to the “BOTH” button that got cut in SQ4.” — @ATMcashpoint
I don’t know about the both button that ScummVM adds to some SCI games, but there’s quite literally no way it could work by just adding a third button state. There’s a fair bit of script logic that’d need to be overhauled. Here’s why that is, and here’s how I did it in The Dating Pool.
I could have used the SCI Companion template game to compare against and document, but to be honest it’s a bit of a mess, as you could expect from a decompilation. The leaked system scripts are much neater to work with, even though the actual script code is basically identical.
Original Messager.sc
:
(method (sayNext theMod theNoun theVerb theCase theSeq &tmp aTalker [theBuf 200] msgkey)
; If we were called with arguments, grab the text for that entry.
; If not, grab the next entry in the sequence.
(if argc
(= aTalker (Message msgGET theMod theNoun theVerb theCase theSeq @theBuf))
else
(= aTalker (Message msgNEXT @theBuf))
)
; If we have voice enabled, allocate space and grab the entry's tuple.
; This block is missing in SQ4CD.
(if (& gMessageType CD_MSG)
(= msgkey (Memory memALLOC_CRIT 12))
(Message msgLAST_MESSAGE msgkey)
)
(if (and aTalker
(or (not lastSequence)
(and lastSequence
(<= curSequence lastSequence)
)
)
)
; Look up the Talker (or Narrator) by number.
; aTalker was a number, but now it'll be an object pointer.
(= aTalker (self findTalker: aTalker))
(if (!= aTalker -1)
(talkerSet add: aTalker)
; Now let our Talker handle the rest.
(if (& gMessageType CD_MSG)
(aTalker
modNum: theMod,
say: msgkey self ;<-- pass ONLY the tuple
)
else
(aTalker
modNum: theMod,
say: @theBuf self ;<-- pass ONLY the string
)
)
; In SQ4, we just always pass only @theBuf. There's some major
; voodoo involved in getting it to work.
(++ curSequence)
)
; Cutting a bit of irrelevant fastcast voodoo
)
; Dispose of the space we allocated for the voice tuple, if needed.
(if (& gMessageType CD_MSG)
(Memory memFREE msgkey)
)
)
Catdate’s Messager.sc
:
; Exactly the same as in the template BUT...
(if (!= aTalker -1)
(talkerSet add: aTalker)
; Pass both the buffer AND the tuple, no matter our settings.
; That does mean that msgkey may be null, but say won't use it in
; that case anyway.
(aTalker
modNum: theMod
say: @theBuf msgkey
)
(++ curSequence)
)
In SQ4CD, the Narrator has extra noun
, verb
, and sequence
properties that get set to allow the text to work. It’s really quite a bit of a mess, and my hat’s off to whoever on the ScummVM team got that Both mode to work. I was going to document it but got lost trying, it’s that wild.
On to the Narrator
and by extension Talker
!
Original Talker.sc
:
(method (say theBuf whoCares)
(if theIconBar (theIconBar disable:))
(if (not initialized) (self init:))
(= caller
(if (and (> argc 1) whoCares)
whoCares
else
null
)
)
; Figure out what to do with the message.
; Note that in one case, theBuf is a string...
(if (& gMessageType TEXT_MSG)
; (method (startText theBuf &tmp strLength)
(self startText: theBuf)
)
; ...but in the other it's a tuple!
(if (& gMessageType CD_MSG)
; (method (startAudio theKeys &tmp m n v c s)
(self startAudio: theBuf)
)
; cut a bit...
; start___ will have set ticks to the length
; of the string or recording. We add one more
; second regardless.
(= ticks (+ ticks 60 gameTime))
(return true)
)
Catdate’s Talker.sc
:
; much the same, but
(method (say theText theAudio whoCares)
; ...
(if (& gMessageType TEXT_MSG)
(self startText: theText)
)
(if (& gMessageType CD_MSG)
(self startAudio: theAudio)
)
; ...
)
Now, this works fine. If I record a quick bit of gibberish, then load up the game, switch to Both, and click, I get a perfectly readable message and hear my gibber. But if I were to revert my little change and use the original code…
That’s what we in the business call mistaking a bunch of numbers for a valid string. I specifically get this result because the first value in the tuple is the module number, which is 110 (0x6E
‘n
‘) in this case, and all numbers in SCI are 16-bit so there’s a terminating null right after.
What’s funny is that after all this, I can’t see how SQ4 is supposed to support Both mode, and ScummVM only needs to add that third button state. There is no patch to adjust the script, and I can’t for the life of me figure out how this would work:
(method (say theVoodoo whoCares &tmp newEvent)
; ...
(if (& gMessageType TEXT_MSG)
; Note: noun, tVerb, and tSequence are properties. theVoodoo is now "case".
(self startText: modNum noun tVerb theVoodoo tSequence &rest)
)
(if (& gMessageType CD_MSG)
(self startAudio: theVoodoo)
)
; ..
)
The weird part is that I can’t find anywhere those properties are set.
…At least with the KQ6/LB2 patches they actually do overhaul quite a bit of the scripts’ logic, which are otherwise just the same system scripts as above. Not the way I did it for my game, but clearly in a way that works out.