1 /* ScummVM - Graphic Adventure Engine
2 *
3 * ScummVM is the legal property of its developers, whose names
4 * are too numerous to list here. Please refer to the COPYRIGHT
5 * file distributed with this source distribution.
6 *
7 * This program is free software; you can redistribute it and/or
8 * modify it under the terms of the GNU General Public License
9 * as published by the Free Software Foundation; either version 2
10 * of the License, or (at your option) any later version.
11 *
12 * This program is distributed in the hope that it will be useful,
13 * but WITHOUT ANY WARRANTY; without even the implied warranty of
14 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15 * GNU General Public License for more details.
16 *
17 * You should have received a copy of the GNU General Public License
18 * along with this program; if not, write to the Free Software
19 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
20 *
21 */
22
23 #include "sherlock/talk.h"
24 #include "sherlock/sherlock.h"
25 #include "sherlock/screen.h"
26 #include "sherlock/scalpel/scalpel.h"
27 #include "sherlock/scalpel/scalpel_people.h"
28 #include "sherlock/scalpel/scalpel_talk.h"
29 #include "sherlock/scalpel/scalpel_user_interface.h"
30 #include "sherlock/tattoo/tattoo.h"
31 #include "sherlock/tattoo/tattoo_fixed_text.h"
32 #include "sherlock/tattoo/tattoo_people.h"
33 #include "sherlock/tattoo/tattoo_scene.h"
34 #include "sherlock/tattoo/tattoo_talk.h"
35 #include "sherlock/tattoo/tattoo_user_interface.h"
36
37 namespace Sherlock {
38
SequenceEntry()39 SequenceEntry::SequenceEntry() {
40 _objNum = 0;
41 _obj = nullptr;
42 _seqStack = 0;
43 _seqTo = 0;
44 _sequenceNumber = _frameNumber = 0;
45 _seqCounter = _seqCounter2 = 0;
46 }
47
48 /*----------------------------------------------------------------*/
49
load(Common::SeekableReadStream & s,bool isRoseTattoo)50 void Statement::load(Common::SeekableReadStream &s, bool isRoseTattoo) {
51 int length;
52
53 length = s.readUint16LE();
54 for (int idx = 0; idx < length - 1; ++idx)
55 _statement += (char)s.readByte();
56 s.readByte(); // Null ending
57
58 length = s.readUint16LE();
59 for (int idx = 0; idx < length - 1; ++idx)
60 _reply += (char)s.readByte();
61 s.readByte(); // Null ending
62
63 length = s.readUint16LE();
64 for (int idx = 0; idx < length - 1; ++idx)
65 _linkFile += (char)s.readByte();
66 s.readByte(); // Null ending
67
68 length = s.readUint16LE();
69 for (int idx = 0; idx < length - 1; ++idx)
70 _voiceFile += (char)s.readByte();
71 s.readByte(); // Null ending
72
73 _required.resize(s.readByte());
74 _modified.resize(s.readByte());
75
76 // Read in flag required/modified data
77 for (uint idx = 0; idx < _required.size(); ++idx)
78 _required[idx] = s.readSint16LE();
79 for (uint idx = 0; idx < _modified.size(); ++idx)
80 _modified[idx] = s.readSint16LE();
81
82 _portraitSide = s.readByte();
83 _quotient = s.readUint16LE();
84 _journal = isRoseTattoo ? s.readByte() : 0;
85 }
86
87 /*----------------------------------------------------------------*/
88
TalkHistoryEntry()89 TalkHistoryEntry::TalkHistoryEntry() {
90 Common::fill(&_data[0], &_data[16], false);
91 }
92
93 /*----------------------------------------------------------------*/
94
init(SherlockEngine * vm)95 Talk *Talk::init(SherlockEngine *vm) {
96 if (vm->getGameID() == GType_SerratedScalpel)
97 return new Scalpel::ScalpelTalk(vm);
98 else
99 return new Tattoo::TattooTalk(vm);
100 }
101
Talk(SherlockEngine * vm)102 Talk::Talk(SherlockEngine *vm) : _vm(vm) {
103 _talkCounter = 0;
104 _talkToAbort = false;
105 _openTalkWindow = false;
106 _speaker = 0;
107 _talkIndex = 0;
108 _talkTo = 0;
109 _scriptSelect = 0;
110 _converseNum = -1;
111 _talkStealth = 0;
112 _talkToFlag = -1;
113 _moreTalkDown = _moreTalkUp = false;
114 _scriptMoreFlag = 0;
115 _scriptSaveIndex = -1;
116 _opcodes = nullptr;
117 _opcodeTable = nullptr;
118 _3doSpeechIndex = -1;
119
120 _charCount = 0;
121 _line = 0;
122 _yp = 0;
123 _wait = 0;
124 _pauseFlag = false;
125 _seqCount = 0;
126 _scriptStart = _scriptEnd = nullptr;
127 _endStr = _noTextYet = false;
128
129 _talkHistory.resize(IS_ROSE_TATTOO ? 1500 : 500);
130 }
131
talkTo(const Common::String filename)132 void Talk::talkTo(const Common::String filename) {
133 Events &events = *_vm->_events;
134 Inventory &inv = *_vm->_inventory;
135 Journal &journal = *_vm->_journal;
136 People &people = *_vm->_people;
137 Scene &scene = *_vm->_scene;
138 Screen &screen = *_vm->_screen;
139 Sound &sound = *_vm->_sound;
140 UserInterface &ui = *_vm->_ui;
141 Common::Rect savedBounds = screen.getDisplayBounds();
142 bool abortFlag = false;
143
144 if (filename.empty())
145 // No filename passed, so exit
146 return;
147
148 // If there any canimations currently running, or a portrait is being cleared,
149 // save the filename for later executing when the canimation is done
150 bool ongoingAnim = scene._canimShapes.size() > 0;
151 if (IS_ROSE_TATTOO) {
152 ongoingAnim = static_cast<Tattoo::TattooScene *>(_vm->_scene)->_activeCAnim.active();
153 }
154 if (ongoingAnim || people._clearingThePortrait) {
155 // Make sure we're not in the middle of a script
156 if (!_scriptMoreFlag) {
157 _scriptName = filename;
158 _scriptSaveIndex = 0;
159
160 // Flag the selection, since we don't yet know which statement yet
161 _scriptSelect = 100;
162 _scriptMoreFlag = 3;
163 }
164
165 return;
166 }
167
168 // Save the ui mode temporarily and switch to talk mode
169 MenuMode savedMode = ui._menuMode;
170 ui._menuMode = TALK_MODE;
171
172 // Turn on the Exit option
173 ui._endKeyActive = true;
174
175 if (people[HOLMES]._walkCount || (!people[HOLMES]._walkTo.empty() &&
176 (IS_SERRATED_SCALPEL || people._allowWalkAbort))) {
177 // Only interrupt if trying to do an action, and not just if player is walking around the scene
178 if (people._allowWalkAbort)
179 abortFlag = true;
180
181 people[HOLMES].gotoStand();
182 }
183
184 if (_talkToAbort)
185 return;
186
187 freeTalkVars();
188
189 // If any sequences have changed in the prior talk file, restore them
190 if (_savedSequences.size() > 0) {
191 for (uint idx = 0; idx < _savedSequences.size(); ++idx) {
192 SequenceEntry &ss = _savedSequences[idx];
193 for (uint idx2 = 0; idx2 < ss._sequences.size(); ++idx2)
194 scene._bgShapes[ss._objNum]._sequences[idx2] = ss._sequences[idx2];
195
196 // Reset the object's frame to the beginning of the sequence
197 scene._bgShapes[ss._objNum]._frameNumber = 0;
198 }
199 }
200
201 if (IS_ROSE_TATTOO) {
202 pullSequence();
203 } else {
204 while (!isSequencesEmpty())
205 pullSequence();
206 }
207
208 if (IS_SERRATED_SCALPEL) {
209 // Restore any pressed button
210 if (!ui._windowOpen && savedMode != STD_MODE)
211 static_cast<Scalpel::ScalpelUserInterface *>(_vm->_ui)->restoreButton((int)(savedMode - 1));
212 } else {
213 static_cast<Tattoo::TattooPeople *>(_vm->_people)->pullNPCPaths();
214 }
215
216 // Clear the ui counter so that anything displayed on the info line
217 // before the window was opened isn't cleared
218 ui._menuCounter = 0;
219
220 // Close any previous window before starting the talk
221 if (ui._windowOpen) {
222 switch (savedMode) {
223 case LOOK_MODE:
224 events.setCursor(ARROW);
225
226 if (ui._invLookFlag) {
227 screen.resetDisplayBounds();
228 ui.drawInterface(2);
229 }
230
231 ui.banishWindow();
232 ui._windowBounds.top = CONTROLS_Y1;
233 ui._temp = ui._oldTemp = ui._lookHelp = 0;
234 ui._menuMode = STD_MODE;
235 events._pressed = events._released = events._oldButtons = 0;
236 ui._invLookFlag = false;
237 break;
238
239 case TALK_MODE:
240 if (_speaker < SPEAKER_REMOVE)
241 people.clearTalking();
242 if (_talkCounter)
243 return;
244
245 // If we were in inventory mode looking at an object, restore the
246 // back buffers before closing the window, so we get the ui restored
247 // rather than the inventory again
248 if (ui._invLookFlag) {
249 screen.resetDisplayBounds();
250 ui.drawInterface(2);
251 ui._invLookFlag = ui._lookScriptFlag = false;
252 }
253
254 ui.banishWindow();
255 ui._windowBounds.top = CONTROLS_Y1;
256 abortFlag = true;
257 break;
258
259 case INV_MODE:
260 case USE_MODE:
261 case GIVE_MODE:
262 inv.freeInv();
263 if (ui._invLookFlag) {
264 screen.resetDisplayBounds();
265 ui.drawInterface(2);
266 ui._invLookFlag = ui._lookScriptFlag = false;
267 }
268
269 ui._infoFlag = true;
270 ui.clearInfo();
271 ui.banishWindow(false);
272 ui._key = -1;
273 break;
274
275 case FILES_MODE:
276 ui.banishWindow(true);
277 ui._windowBounds.top = CONTROLS_Y1;
278 abortFlag = true;
279 break;
280
281 case SETUP_MODE:
282 ui.banishWindow(true);
283 ui._windowBounds.top = CONTROLS_Y1;
284 ui._temp = ui._oldTemp = ui._lookHelp = ui._invLookFlag = false;
285 ui._menuMode = STD_MODE;
286 events._pressed = events._released = events._oldButtons = 0;
287 abortFlag = true;
288 break;
289
290 default:
291 break;
292 }
293 }
294
295 screen.resetDisplayBounds();
296 events._pressed = events._released = false;
297 loadTalkFile(filename);
298 ui._selector = ui._oldSelector = ui._key = ui._oldKey = -1;
299
300 // Find the first statement that has the correct flags
301 int select = -1;
302 for (uint idx = 0; idx < _statements.size() && select == -1; ++idx) {
303 if (_statements[idx]._talkMap == 0)
304 select = _talkIndex = idx;
305 }
306
307 // If there's a pending automatic selection to be made, then use it
308 if (_scriptMoreFlag && _scriptSelect != 100)
309 select = _scriptSelect;
310
311 if (select == -1) {
312 if (IS_ROSE_TATTOO) {
313 static_cast<Tattoo::TattooUserInterface *>(&ui)->putMessage(
314 "%s", _vm->_fixedText->getText(Tattoo::kFixedText_NoEffect));
315 return;
316 }
317 error("Couldn't find statement to display");
318 }
319
320 // Add the statement into the journal and talk history
321 if (_talkTo != -1 && !_talkHistory[_converseNum][select])
322 journal.record(_converseNum, select, true);
323 _talkHistory[_converseNum][select] = true;
324
325 // Check if the talk file is meant to be a non-seen comment
326 if (filename.size() < 8 || filename[7] != '*') {
327 // Should we start in stealth mode?
328 if (_statements[select]._statement.hasPrefix("^")) {
329 _talkStealth = 2;
330 } else {
331 // Not in stealth mode, so bring up the ui window
332 _talkStealth = 0;
333 ++_talkToFlag;
334 events.setCursor(WAIT);
335
336 ui._windowBounds.top = CONTROLS_Y;
337 ui._infoFlag = true;
338 ui.clearInfo();
339 }
340
341 // Handle replies until there's no further linked file,
342 // or the link file isn't a reply first cnversation
343 while (!_vm->shouldQuit()) {
344 clearSequences();
345 _scriptSelect = select;
346 _speaker = _talkTo;
347
348 // Set up the talk file extension
349 if (IS_ROSE_TATTOO && sound._speechOn && _scriptMoreFlag != 1)
350 sound._talkSoundFile += Common::String::format("%02dB", select + 1);
351
352 // Make a copy of the statement (in case the script frees the statement list), and then execute it
353 Statement statement = _statements[select];
354 doScript(_statements[select]._reply);
355
356 if (IS_ROSE_TATTOO) {
357 for (int idx = 0; idx < MAX_CHARACTERS; ++idx)
358 people[idx]._misc = 0;
359 }
360
361 if (_talkToAbort)
362 return;
363
364 if (!_talkStealth)
365 ui.clearWindow();
366
367 if (statement._modified.size() > 0) {
368 for (uint idx = 0; idx < statement._modified.size(); ++idx)
369 _vm->setFlags(statement._modified[idx]);
370
371 setTalkMap();
372 }
373
374 // Check for a linked file
375 if (!statement._linkFile.empty() && !_scriptMoreFlag) {
376 Common::String linkFilename = statement._linkFile;
377 freeTalkVars();
378 loadTalkFile(linkFilename);
379
380 // Scan for the first valid statement in the newly loaded file
381 select = -1;
382 for (uint idx = 0; idx < _statements.size(); ++idx) {
383 if (_statements[idx]._talkMap == 0) {
384 select = idx;
385 break;
386 }
387 }
388
389 if (_talkToFlag == 1)
390 pullSequence();
391
392 // Set the stealth mode for the new talk file
393 Statement &newStatement = _statements[select];
394 _talkStealth = newStatement._statement.hasPrefix("^") ? 2 : 0;
395
396 // If the new conversion is a reply first, then we don't need
397 // to display any choices, since the reply needs to be shown
398 if (!newStatement._statement.hasPrefix("*") && !newStatement._statement.hasPrefix("^")) {
399 _talkIndex = select;
400 showTalk();
401
402 // Break out of loop now that we're waiting for player input
403 events.setCursor(ARROW);
404 break;
405 } else {
406 // Add the statement into the journal and talk history
407 if (_talkTo != -1 && !_talkHistory[_converseNum][select])
408 journal.record(_converseNum, select, true);
409 _talkHistory[_converseNum][select] = true;
410 }
411
412 ui._key = ui._oldKey = 'T'; // FIXME: I'm not sure what to do here, I need ScalpelUI->_hotkeyTalk
413 ui._temp = ui._oldTemp = 0;
414 ui._menuMode = TALK_MODE;
415 _talkToFlag = 2;
416 } else {
417 freeTalkVars();
418
419 if (IS_SERRATED_SCALPEL) {
420 if (!ui._lookScriptFlag) {
421 ui.drawInterface(2);
422 ui._menuMode = STD_MODE;
423 ui._windowBounds.top = CONTROLS_Y1;
424 }
425 } else {
426 ui._menuMode = static_cast<Tattoo::TattooScene *>(_vm->_scene)->_labTableScene ? LAB_MODE : STD_MODE;
427 }
428
429 ui.banishWindow();
430 break;
431 }
432 }
433 }
434
435 _talkStealth = 0;
436 events._pressed = events._released = events._oldButtons = 0;
437 events.clearKeyboard();
438
439 if (savedBounds.bottom == SHERLOCK_SCREEN_HEIGHT)
440 screen.resetDisplayBounds();
441 else
442 screen.setDisplayBounds(savedBounds);
443
444 _talkToAbort = abortFlag;
445
446 // If a script was added to the script stack, restore state so that the
447 // previous script can continue
448 popStack();
449
450 events.setCursor(ARROW);
451 }
452
initTalk(int objNum)453 void Talk::initTalk(int objNum) {
454 Events &events = *_vm->_events;
455 People &people = *_vm->_people;
456 Scene &scene = *_vm->_scene;
457 UserInterface &ui = *_vm->_ui;
458
459 ui._windowBounds.top = CONTROLS_Y;
460 ui._infoFlag = true;
461 _speaker = SPEAKER_REMOVE;
462
463 Common::String talkFilename = (objNum >= 1000) ? people[objNum - 1000]._npcName : scene._bgShapes[objNum]._name;
464 loadTalkFile(talkFilename);
465
466 // Find the first statement with the correct flags
467 int select = -1;
468 for (uint idx = 0; idx < _statements.size(); ++idx) {
469 if (_statements[idx]._talkMap == 0) {
470 select = idx;
471 break;
472 }
473 }
474
475 if (select == -1) {
476 freeTalkVars();
477 if (!scumm_strnicmp(talkFilename.c_str(), "PATH", 4))
478 error("No entries found to execute in path file");
479
480 nothingToSay();
481 return;
482 }
483
484 // See if the statement is a stealth mode reply
485 Statement &statement = _statements[select];
486 if (statement._statement.hasPrefix("^")) {
487 clearSequences();
488
489 // Start talk in stealth mode
490 _talkStealth = 2;
491
492 talkTo(talkFilename);
493 } else if (statement._statement.hasPrefix("*")) {
494 // Character being spoken to will speak first
495 if (objNum > 1000) {
496 (*static_cast<Tattoo::TattooPeople *>(_vm->_people))[objNum - 1000].walkHolmesToNPC();
497 } else {
498 Object &obj = scene._bgShapes[objNum];
499 clearSequences();
500 pushSequence(_talkTo);
501 people.setListenSequence(_talkTo, 129);
502
503 events.setCursor(WAIT);
504 if (obj._lookPosition.y != 0)
505 // Need to walk to character first
506 people[HOLMES].walkToCoords(obj._lookPosition, obj._lookPosition._facing);
507 events.setCursor(ARROW);
508 }
509
510 if (!_talkToAbort)
511 talkTo(talkFilename);
512 } else {
513 // Holmes will be speaking first
514 _talkToFlag = false;
515
516 if (objNum > 1000) {
517 (*static_cast<Tattoo::TattooPeople *>(_vm->_people))[objNum - 1000].walkHolmesToNPC();
518 } else {
519 Object &obj = scene._bgShapes[objNum];
520 clearSequences();
521 pushSequence(_talkTo);
522 people.setListenSequence(_talkTo, 129);
523
524 events.setCursor(WAIT);
525 if (obj._lookPosition.y != 0)
526 // Walk over to person to talk to
527 people[HOLMES].walkToCoords(obj._lookPosition, obj._lookPosition._facing);
528 events.setCursor(ARROW);
529 }
530
531 if (!_talkToAbort) {
532 // See if walking over triggered a conversation
533 if (_talkToFlag) {
534 if (_talkToFlag == 1) {
535 events.setCursor(ARROW);
536 // _sequenceStack._count = 1;
537 pullSequence();
538 }
539 } else {
540 _talkIndex = select;
541 showTalk();
542
543 // Break out of loop now that we're waiting for player input
544 events.setCursor(ARROW);
545 }
546
547 _talkToFlag = -1;
548 }
549 }
550 }
551
freeTalkVars()552 void Talk::freeTalkVars() {
553 _statements.clear();
554 }
555
loadTalkFile(const Common::String & filename)556 void Talk::loadTalkFile(const Common::String &filename) {
557 People &people = *_vm->_people;
558 Resources &res = *_vm->_res;
559 Sound &sound = *_vm->_sound;
560
561 // Save a copy of the talk filename
562 _scriptName = filename;
563
564 // Check for an existing person being talked to
565 _talkTo = -1;
566 for (int idx = 0; idx < (int)people._characters.size(); ++idx) {
567 if (!scumm_strnicmp(filename.c_str(), people._characters[idx]._portrait, 4)) {
568 _talkTo = idx;
569 break;
570 }
571 }
572
573 const char *chP = strchr(filename.c_str(), '.');
574 Common::String talkFile = chP ? Common::String(filename.c_str(), chP) + ".tlk" :
575 Common::String(filename.c_str(), filename.c_str() + 7) + ".tlk";
576
577 // Create the base of the sound filename used for talking in Rose Tattoo
578 if (IS_ROSE_TATTOO && _scriptMoreFlag != 1)
579 sound._talkSoundFile = Common::String(filename.c_str(), filename.c_str() + 7) + ".";
580
581 // Open the talk file for reading
582 Common::SeekableReadStream *talkStream = res.load(talkFile);
583 _converseNum = res.resourceIndex();
584 talkStream->skip(2); // Skip talk file version num
585
586 _statements.clear();
587 _statements.resize(talkStream->readByte());
588 for (uint idx = 0; idx < _statements.size(); ++idx)
589 _statements[idx].load(*talkStream, IS_ROSE_TATTOO);
590
591 delete talkStream;
592
593 if (!sound._voices)
594 stripVoiceCommands();
595 setTalkMap();
596 }
597
stripVoiceCommands()598 void Talk::stripVoiceCommands() {
599 for (uint sIdx = 0; sIdx < _statements.size(); ++sIdx) {
600 Statement &statement = _statements[sIdx];
601
602 // Scan for an sound effect byte, which indicates to play a sound
603 for (uint idx = 0; idx < statement._reply.size(); ++idx) {
604 if (statement._reply[idx] == (char)_opcodes[OP_SFX_COMMAND]) {
605 // Replace instruction character with a space, and delete the
606 // rest of the name following it
607 statement._reply = Common::String(statement._reply.c_str(),
608 statement._reply.c_str() + idx) + " " +
609 Common::String(statement._reply.c_str() + idx + 9);
610 }
611 }
612
613 // Ensure the last character of the reply is not a space from the prior
614 // conversion loop, to avoid any issues with the space ever causing a page
615 // wrap, and ending up displaying another empty page
616 while (statement._reply.lastChar() == ' ')
617 statement._reply.deleteLastChar();
618 }
619 }
620
setTalkMap()621 void Talk::setTalkMap() {
622 int statementNum = 0;
623
624 for (uint sIdx = 0; sIdx < _statements.size(); ++sIdx) {
625 Statement &statement = _statements[sIdx];
626
627 // Set up talk map entry for the statement
628 bool valid = true;
629 for (uint idx = 0; idx < statement._required.size(); ++idx) {
630 if (!_vm->readFlags(statement._required[idx]))
631 valid = false;
632 }
633
634 statement._talkMap = valid ? statementNum++ : -1;
635 }
636 }
637
pushSequence(int speaker)638 void Talk::pushSequence(int speaker) {
639 People &people = *_vm->_people;
640 Scene &scene = *_vm->_scene;
641
642 // Only proceed if a speaker is specified
643 if (speaker != -1) {
644 int objNum = people.findSpeaker(speaker);
645 if (objNum != -1)
646 pushSequenceEntry(&scene._bgShapes[objNum]);
647 }
648 }
649
doScript(const Common::String & script)650 void Talk::doScript(const Common::String &script) {
651 People &people = *_vm->_people;
652 Scene &scene = *_vm->_scene;
653 Screen &screen = *_vm->_screen;
654 UserInterface &ui = *_vm->_ui;
655
656 _savedSequences.clear();
657
658 _scriptStart = (const byte *)script.c_str();
659 _scriptEnd = _scriptStart + script.size();
660 const byte *str = _scriptStart;
661 _charCount = 0;
662 _line = 0;
663 _wait = 0;
664 _pauseFlag = false;
665 _seqCount = 0;
666 _noTextYet = true;
667 _endStr = false;
668 _openTalkWindow = false;
669
670 if (IS_SERRATED_SCALPEL)
671 _yp = CONTROLS_Y + 12;
672 else
673 _yp = (_talkTo == -1) ? 5 : screen.fontHeight() + 11;
674
675 if (IS_ROSE_TATTOO) {
676 for (int idx = 0; idx < MAX_CHARACTERS; ++idx) {
677 Tattoo::TattooPerson &p = (*(Tattoo::TattooPeople *)_vm->_people)[idx];
678 p._savedNpcSequence = p._sequenceNumber;
679 p._savedNpcFrame = p._frameNumber;
680 p._resetNPCPath = true;
681 }
682 }
683
684 if (_scriptMoreFlag) {
685 _scriptMoreFlag = 0;
686 str = _scriptStart + _scriptSaveIndex;
687 }
688
689 // Check if the script begins with a Stealh Mode Active command
690 if (str[0] == _opcodes[OP_STEALTH_MODE_ACTIVE] || _talkStealth) {
691 _talkStealth = 2;
692 _speaker |= SPEAKER_REMOVE;
693 } else {
694 if (IS_SERRATED_SCALPEL)
695 pushSequence(_speaker);
696 if (IS_SERRATED_SCALPEL || ui._windowOpen)
697 ui.clearWindow();
698
699 // Need to switch speakers?
700 if (str[0] == _opcodes[OP_SWITCH_SPEAKER]) {
701 _speaker = str[1] - 1;
702
703 if (IS_SERRATED_SCALPEL) {
704 str += 2;
705 pullSequence();
706 pushSequence(_speaker);
707 } else {
708 str += 3;
709 }
710
711 people.setTalkSequence(_speaker);
712 } else {
713 people.setTalkSequence(_speaker);
714 }
715
716 if (IS_SERRATED_SCALPEL) {
717 // Assign portrait location?
718 if (str[0] == _opcodes[OP_ASSIGN_PORTRAIT_LOCATION]) {
719 switch (str[1] & 15) {
720 case 1:
721 people._portraitSide = 20;
722 break;
723 case 2:
724 people._portraitSide = 220;
725 break;
726 case 3:
727 people._portraitSide = 120;
728 break;
729 default:
730 break;
731
732 }
733
734 if (str[1] > 15)
735 people._speakerFlip = true;
736 str += 2;
737 }
738
739 if (IS_SERRATED_SCALPEL) {
740 // Remove portrait?
741 if ( str[0] == _opcodes[OP_REMOVE_PORTRAIT]) {
742 _speaker = -1;
743 } else {
744 // Nope, so set the first speaker
745 ((Scalpel::ScalpelPeople *)_vm->_people)->setTalking(_speaker);
746 }
747 }
748 }
749 }
750
751 do {
752 Common::String tempString;
753 _wait = 0;
754
755 byte c = str[0];
756 if (!c) {
757 _endStr = true;
758 } else if (c == '{') {
759 // Start of comment, so skip over it
760 while (*str++ != '}')
761 ;
762 } else if (isOpcode(c)) {
763 // the original interpreter checked for c being >= 0x80
764 // and if that is the case, it tried to process it as opcode, BUT ALSO ALWAYS skipped over it
765 // This was done inside the Spanish + German interpreters of Serrated Scalpel, not the original
766 // English interpreter (reverse engineered from the binaries).
767 //
768 // This resulted in special characters not getting shown in case they occurred at the start
769 // of sentences like for example the inverted exclamation mark and the inverted question mark.
770 // For further study see fonts.cpp
771 //
772 // We create an inverted exclamation mark for the Spanish version and we show it.
773 //
774 // Us not skipping over those characters may result in an assert() happening inside fonts.cpp
775 // in case more invalid characters exist.
776 // More information see bug #6931
777 //
778
779 // Handle control code
780 switch ((this->*_opcodeTable[c - _opcodes[0]])(str)) {
781 case RET_EXIT:
782 return;
783 case RET_CONTINUE:
784 continue;
785 default:
786 break;
787 }
788
789 ++str;
790 } else {
791 // Handle drawing the talk interface with the text
792 talkInterface(str);
793 }
794
795 // Open window if it wasn't already open, and text has already been printed
796 if ((_openTalkWindow && _wait) || (_openTalkWindow && str[0] >= _opcodes[0] && str[0] != _opcodes[OP_END_TEXT_WINDOW])) {
797 if (!ui._slideWindows) {
798 screen.slamRect(Common::Rect(0, CONTROLS_Y, SHERLOCK_SCREEN_WIDTH, SHERLOCK_SCREEN_HEIGHT));
799 } else {
800 ui.summonWindow();
801 }
802
803 ui._windowOpen = true;
804 _openTalkWindow = false;
805 }
806
807 if (_wait)
808 // Handling pausing
809 talkWait(str);
810 } while (!_vm->shouldQuit() && !_endStr);
811
812 if (_wait != -1) {
813 for (int ssIndex = 0; ssIndex < (int)_savedSequences.size(); ++ssIndex) {
814 SequenceEntry &seq = _savedSequences[ssIndex];
815 Object &object = scene._bgShapes[seq._objNum];
816
817 for (uint idx = 0; idx < seq._sequences.size(); ++idx)
818 object._sequences[idx] = seq._sequences[idx];
819 object._frameNumber = seq._frameNumber;
820 object._seqTo = seq._seqTo;
821 }
822
823 pullSequence();
824
825 if (IS_SERRATED_SCALPEL) {
826 if (_speaker >= 0 && _speaker < SPEAKER_REMOVE)
827 people.clearTalking();
828 } else {
829 static_cast<Tattoo::TattooPeople *>(_vm->_people)->pullNPCPaths();
830 }
831 }
832 }
833
waitForMore(int delay)834 int Talk::waitForMore(int delay) {
835 Events &events = *_vm->_events;
836 People &people = *_vm->_people;
837 Scene &scene = *_vm->_scene;
838 Sound &sound = *_vm->_sound;
839 UserInterface &ui = *_vm->_ui;
840 CursorId oldCursor = events.getCursor();
841 int key2 = 254;
842 bool playingSpeech = false;
843
844 // Unless we're in stealth mode, show the appropriate cursor
845 if (!_talkStealth) {
846 events.setCursor(ui._lookScriptFlag ? MAGNIFY : ARROW);
847 }
848
849 // Handle playing any speech associated with the text being displayed
850 switchSpeaker();
851 if (sound._speechOn && IS_ROSE_TATTOO) {
852 sound.playSpeech(sound._talkSoundFile);
853 sound._talkSoundFile.setChar(sound._talkSoundFile.lastChar() + 1, sound._talkSoundFile.size() - 1);
854 }
855 playingSpeech = sound.isSpeechPlaying();
856
857 do {
858 if (IS_SERRATED_SCALPEL && playingSpeech && !sound.isSpeechPlaying())
859 people._portrait._frameNumber = -1;
860
861 scene.doBgAnim();
862
863 // If talkTo call was done via doBgAnim, abort out of talk quietly
864 if (_talkToAbort) {
865 key2 = -1;
866 events._released = true;
867 } else {
868 // See if there's been a button press
869 events.pollEventsAndWait();
870 events.setButtonState();
871
872 if (events.kbHit()) {
873 Common::KeyState keyState = events.getKey();
874 if (keyState.keycode == Common::KEYCODE_ESCAPE) {
875 if (IS_ROSE_TATTOO && static_cast<Tattoo::TattooEngine *>(_vm)->_runningProlog) {
876 // Skip out of the introduction
877 _vm->setFlags(-76);
878 _vm->setFlags(396);
879 scene._goToScene = 1;
880 }
881 break;
882
883 } else if (Common::isPrint(keyState.ascii))
884 key2 = keyState.keycode;
885 }
886
887 if (_talkStealth) {
888 key2 = 254;
889 events._released = false;
890 }
891 }
892
893 // Count down the delay
894 if ((delay > 0 && !ui._invLookFlag && !ui._lookScriptFlag) || _talkStealth)
895 --delay;
896
897 if (playingSpeech && !sound.isSpeechPlaying())
898 delay = 0;
899 } while (!_vm->shouldQuit() && key2 == 254 && (delay || (playingSpeech && sound.isSpeechPlaying()))
900 && !events._released && !events._rightReleased);
901
902 // If voices was set 2 to indicate a Scalpel voice file was playing, then reset it back to 1
903 if (sound._voices == 2)
904 sound._voices = 1;
905
906 if (delay > 0 && sound.isSpeechPlaying())
907 sound.stopSpeech();
908
909 // Adjust _talkStealth mode:
910 // mode 1 - It was by a pause without stealth being on before the pause, so reset back to 0
911 // mode 3 - It was set by a pause with stealth being on before the pause, to set it to active
912 // mode 0/2 (Inactive/active) No change
913 switch (_talkStealth) {
914 case 1:
915 _talkStealth = 0;
916 break;
917 case 2:
918 _talkStealth = 2;
919 break;
920 default:
921 break;
922 }
923
924
925 sound.stopSpeech();
926 events.setCursor(_talkToAbort ? ARROW : oldCursor);
927 events._pressed = events._released = false;
928
929 return key2;
930 }
931
isOpcode(byte checkCharacter)932 bool Talk::isOpcode(byte checkCharacter) {
933 if ((checkCharacter < _opcodes[0]) || (checkCharacter >= (_opcodes[0] + 99)))
934 return false; // outside of range
935 if (_opcodeTable[checkCharacter - _opcodes[0]])
936 return true;
937 return false;
938 }
939
popStack()940 void Talk::popStack() {
941 if (!_scriptStack.empty()) {
942 ScriptStackEntry scriptEntry = _scriptStack.pop();
943 _scriptName = scriptEntry._name;
944 _scriptSaveIndex = scriptEntry._currentIndex;
945 _scriptSelect = scriptEntry._select;
946 _scriptMoreFlag = 1;
947 }
948 }
949
synchronize(Serializer & s)950 void Talk::synchronize(Serializer &s) {
951 for (uint idx = 0; idx < _talkHistory.size(); ++idx) {
952 TalkHistoryEntry &he = _talkHistory[idx];
953
954 for (int flag = 0; flag < 16; ++flag)
955 s.syncAsByte(he._data[flag]);
956 }
957 }
958
cmdAddItemToInventory(const byte * & str)959 OpcodeReturn Talk::cmdAddItemToInventory(const byte *&str) {
960 Inventory &inv = *_vm->_inventory;
961 Common::String tempString;
962
963 ++str;
964 for (int idx = 0; idx < str[0]; ++idx)
965 tempString += str[idx + 1];
966 str += str[0];
967
968 inv.putNameInInventory(tempString);
969 return RET_SUCCESS;
970 }
971
cmdAdjustObjectSequence(const byte * & str)972 OpcodeReturn Talk::cmdAdjustObjectSequence(const byte *&str) {
973 Scene &scene = *_vm->_scene;
974 Common::String tempString;
975
976 // Get the name of the object to adjust
977 ++str;
978 for (int idx = 0; idx < (str[0] & 127); ++idx)
979 tempString += str[idx + 2];
980
981 // Scan for object
982 int objId = -1;
983 for (uint idx = 0; idx < scene._bgShapes.size(); ++idx) {
984 if (tempString.equalsIgnoreCase(scene._bgShapes[idx]._name))
985 objId = idx;
986 }
987 if (objId == -1)
988 error("Could not find object %s to change", tempString.c_str());
989
990 // Should the script be overwritten?
991 if (str[0] > 0x80) {
992 // Save the current sequence
993 _savedSequences.push(SequenceEntry());
994 SequenceEntry &seqEntry = _savedSequences.top();
995 seqEntry._objNum = objId;
996 seqEntry._seqTo = scene._bgShapes[objId]._seqTo;
997 for (uint idx = 0; idx < scene._bgShapes[objId]._seqSize; ++idx)
998 seqEntry._sequences.push_back(scene._bgShapes[objId]._sequences[idx]);
999 }
1000
1001 // Get number of bytes to change
1002 _seqCount = str[1];
1003 str += (str[0] & 127) + 2;
1004
1005 // WORKAROUND: Original German Scalpel crash when moving box at Tobacconists
1006 if (_vm->getLanguage() == Common::DE_DEU && _scriptName == "Alfr30Z")
1007 _seqCount = 16;
1008
1009 // Copy in the new sequence
1010 for (int idx = 0; idx < _seqCount; ++idx, ++str)
1011 scene._bgShapes[objId]._sequences[idx] = str[0] - 1;
1012
1013 // Reset object back to beginning of new sequence
1014 scene._bgShapes[objId]._frameNumber = 0;
1015
1016 return RET_CONTINUE;
1017 }
1018
cmdBanishWindow(const byte * & str)1019 OpcodeReturn Talk::cmdBanishWindow(const byte *&str) {
1020 People &people = *_vm->_people;
1021 UserInterface &ui = *_vm->_ui;
1022
1023 if (!(_speaker & SPEAKER_REMOVE))
1024 people.clearTalking();
1025 pullSequence();
1026
1027 if (_talkToAbort)
1028 return RET_EXIT;
1029
1030 _speaker |= SPEAKER_REMOVE;
1031 ui.banishWindow();
1032 ui._menuMode = TALK_MODE;
1033 _noTextYet = true;
1034
1035 return RET_SUCCESS;
1036 }
1037
cmdDisableEndKey(const byte * & str)1038 OpcodeReturn Talk::cmdDisableEndKey(const byte *&str) {
1039 _vm->_ui->_endKeyActive = false;
1040 return RET_SUCCESS;
1041 }
1042
cmdEnableEndKey(const byte * & str)1043 OpcodeReturn Talk::cmdEnableEndKey(const byte *&str) {
1044 _vm->_ui->_endKeyActive = true;
1045 return RET_SUCCESS;
1046 }
1047
cmdEndTextWindow(const byte * & str)1048 OpcodeReturn Talk::cmdEndTextWindow(const byte *&str) {
1049 return RET_SUCCESS;
1050 }
1051
cmdHolmesOff(const byte * & str)1052 OpcodeReturn Talk::cmdHolmesOff(const byte *&str) {
1053 People &people = *_vm->_people;
1054 people[HOLMES]._type = REMOVE;
1055 people._holmesOn = false;
1056
1057 return RET_SUCCESS;
1058 }
1059
cmdHolmesOn(const byte * & str)1060 OpcodeReturn Talk::cmdHolmesOn(const byte *&str) {
1061 People &people = *_vm->_people;
1062 people[HOLMES]._type = CHARACTER;
1063 people._holmesOn = true;
1064
1065 return RET_SUCCESS;
1066 }
1067
cmdPause(const byte * & str)1068 OpcodeReturn Talk::cmdPause(const byte *&str) {
1069 _charCount = *++str;
1070 _wait = _pauseFlag = true;
1071
1072 return RET_SUCCESS;
1073 }
1074
cmdPauseWithoutControl(const byte * & str)1075 OpcodeReturn Talk::cmdPauseWithoutControl(const byte *&str) {
1076 Events &events = *_vm->_events;
1077 Scene &scene = *_vm->_scene;
1078 ++str;
1079
1080 events.incWaitCounter();
1081
1082 for (int idx = 0; idx < (str[0] - 1); ++idx) {
1083 scene.doBgAnim();
1084 if (_talkToAbort)
1085 return RET_EXIT;
1086
1087 // Check for button press
1088 events.pollEvents();
1089 events.setButtonState();
1090 }
1091
1092 events.decWaitCounter();
1093
1094 _endStr = false;
1095 return RET_SUCCESS;
1096 }
1097
cmdRemoveItemFromInventory(const byte * & str)1098 OpcodeReturn Talk::cmdRemoveItemFromInventory(const byte *&str) {
1099 Inventory &inv = *_vm->_inventory;
1100 Common::String tempString;
1101
1102 ++str;
1103 for (int idx = 0; idx < str[0]; ++idx)
1104 tempString += str[idx + 1];
1105 str += str[0];
1106
1107 inv.deleteItemFromInventory(tempString);
1108
1109 return RET_SUCCESS;
1110 }
1111
cmdRunCAnimation(const byte * & str)1112 OpcodeReturn Talk::cmdRunCAnimation(const byte *&str) {
1113 Scene &scene = *_vm->_scene;
1114
1115 ++str;
1116 scene.startCAnim((str[0] - 1) & 127, (str[0] & 0x80) ? -1 : 1);
1117 if (_talkToAbort)
1118 return RET_EXIT;
1119
1120 // Check if next character is changing side or changing portrait
1121 _wait = 0;
1122 if (_charCount && (str[1] == _opcodes[OP_SWITCH_SPEAKER] ||
1123 (IS_SERRATED_SCALPEL && str[1] == _opcodes[OP_ASSIGN_PORTRAIT_LOCATION])))
1124 _wait = 1;
1125
1126 return RET_SUCCESS;
1127 }
1128
cmdSetFlag(const byte * & str)1129 OpcodeReturn Talk::cmdSetFlag(const byte *&str) {
1130 ++str;
1131 int flag1 = (str[0] - 1) * 256 + str[1] - 1 - (str[1] == 1 ? 1 : 0);
1132 int flag = (flag1 & 0x3fff) * (flag1 >= 0x4000 ? -1 : 1);
1133 _vm->setFlags(flag);
1134 ++str;
1135
1136 return RET_SUCCESS;
1137 }
1138
cmdSetObject(const byte * & str)1139 OpcodeReturn Talk::cmdSetObject(const byte *&str) {
1140 Scene &scene = *_vm->_scene;
1141 Common::String tempString;
1142
1143 ++str;
1144 for (int idx = 0; idx < (str[0] & 127); ++idx)
1145 tempString += str[idx + 1];
1146
1147 // Set comparison state according to if we want to hide or unhide
1148 bool state = (str[0] >= SPEAKER_REMOVE);
1149 str += str[0] & 127;
1150
1151 for (uint idx = 0; idx < scene._bgShapes.size(); ++idx) {
1152 Object &object = scene._bgShapes[idx];
1153 if (tempString.equalsIgnoreCase(object._name)) {
1154 // Only toggle the object if it's not in the desired state already
1155 if ((object._type == HIDDEN && state) || (object._type != HIDDEN && !state))
1156 object.toggleHidden();
1157 }
1158 }
1159
1160 return RET_SUCCESS;
1161 }
1162
cmdStealthModeActivate(const byte * & str)1163 OpcodeReturn Talk::cmdStealthModeActivate(const byte *&str) {
1164 _talkStealth = 2;
1165 return RET_SUCCESS;
1166 }
1167
cmdStealthModeDeactivate(const byte * & str)1168 OpcodeReturn Talk::cmdStealthModeDeactivate(const byte *&str) {
1169 Events &events = *_vm->_events;
1170
1171 _talkStealth = 0;
1172 events.clearKeyboard();
1173
1174 return RET_SUCCESS;
1175 }
1176
cmdToggleObject(const byte * & str)1177 OpcodeReturn Talk::cmdToggleObject(const byte *&str) {
1178 Scene &scene = *_vm->_scene;
1179 Common::String tempString;
1180
1181 ++str;
1182 for (int idx = 0; idx < str[0]; ++idx)
1183 tempString += str[idx + 1];
1184
1185 scene.toggleObject(tempString);
1186 str += str[0];
1187
1188 return RET_SUCCESS;
1189 }
1190
cmdWalkToCAnimation(const byte * & str)1191 OpcodeReturn Talk::cmdWalkToCAnimation(const byte *&str) {
1192 People &people = *_vm->_people;
1193 Scene &scene = *_vm->_scene;
1194
1195 ++str;
1196 CAnim &animation = scene._cAnim[str[0] - 1];
1197 people[HOLMES].walkToCoords(animation._goto[0], animation._goto[0]._facing);
1198
1199 return _talkToAbort ? RET_EXIT : RET_SUCCESS;
1200 }
1201
talkWait(const byte * & str)1202 void Talk::talkWait(const byte *&str) {
1203 if (!_pauseFlag && _charCount < 160)
1204 _charCount = 160;
1205
1206 _wait = waitForMore(_charCount);
1207 if (_wait == -1)
1208 _endStr = true;
1209
1210 // If a key was pressed to finish the window, see if further voice files should be skipped
1211 if (IS_SERRATED_SCALPEL && _wait >= 0 && _wait < 254) {
1212 if (str[0] == _opcodes[OP_SFX_COMMAND])
1213 str += 9;
1214 }
1215
1216 _pauseFlag = false;
1217 }
1218
1219 } // End of namespace Sherlock
1220