“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.
(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) ) )
; 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
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
(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) )
; 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 (
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.