scummvm/engines/sherlock/scalpel/scalpel_talk.cpp
Torbjörn Andersson d372f11336 SHERLOCK: Extend Scalpel flower girl workaround
The same glitch that happens with the flower girl also happens with the
housekeeper: Walking nearby triggers a conversation, but doesn't abort
the current action. This can cause the UI to get corrupted.

At least for the housekeeper, this does not happen with the original
interpreter. I have no idea why.
2022-01-14 16:45:17 +01:00

1014 lines
29 KiB
C++

/* ScummVM - Graphic Adventure Engine
*
* ScummVM is the legal property of its developers, whose names
* are too numerous to list here. Please refer to the COPYRIGHT
* file distributed with this source distribution.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
#include "sherlock/scalpel/scalpel_talk.h"
#include "sherlock/scalpel/scalpel_fixed_text.h"
#include "sherlock/scalpel/scalpel_journal.h"
#include "sherlock/scalpel/scalpel_map.h"
#include "sherlock/scalpel/scalpel_people.h"
#include "sherlock/scalpel/scalpel_scene.h"
#include "sherlock/scalpel/scalpel_screen.h"
#include "sherlock/scalpel/scalpel_user_interface.h"
#include "sherlock/scalpel/scalpel.h"
#include "sherlock/screen.h"
namespace Sherlock {
namespace Scalpel {
const byte SCALPEL_OPCODES[] = {
128, // OP_SWITCH_SPEAKER
129, // OP_RUN_CANIMATION
130, // OP_ASSIGN_PORTRAIT_LOCATION
131, // OP_PAUSE
132, // OP_REMOVE_PORTRAIT
133, // OP_CLEAR_WINDOW
134, // OP_ADJUST_OBJ_SEQUENCE
135, // OP_WALK_TO_COORDS
136, // OP_PAUSE_WITHOUT_CONTROL
137, // OP_BANISH_WINDOW
138, // OP_SUMMON_WINDOW
139, // OP_SET_FLAG
140, // OP_SFX_COMMAND
141, // OP_TOGGLE_OBJECT
142, // OP_STEALTH_MODE_ACTIVE
143, // OP_IF_STATEMENT
144, // OP_ELSE_STATEMENT
145, // OP_END_IF_STATEMENT
146, // OP_STEALTH_MODE_DEACTIVATE
147, // OP_TURN_HOLMES_OFF
148, // OP_TURN_HOLMES_ON
149, // OP_GOTO_SCENE
150, // OP_PLAY_PROLOGUE
151, // OP_ADD_ITEM_TO_INVENTORY
152, // OP_SET_OBJECT
153, // OP_CALL_TALK_FILE
143, // OP_MOVE_MOUSE
155, // OP_DISPLAY_INFO_LINE
156, // OP_CLEAR_INFO_LINE
157, // OP_WALK_TO_CANIMATION
158, // OP_REMOVE_ITEM_FROM_INVENTORY
159, // OP_ENABLE_END_KEY
160, // OP_DISABLE_END_KEY
161, // OP_END_TEXT_WINDOW
0, // OP_MOUSE_ON_OFF
0, // OP_SET_WALK_CONTROL
0, // OP_SET_TALK_SEQUENCE
0, // OP_PLAY_SONG
0, // OP_WALK_HOLMES_AND_NPC_TO_CANIM
0, // OP_SET_NPC_PATH_DEST
0, // OP_NEXT_SONG
0, // OP_SET_NPC_PATH_PAUSE
0, // OP_PASSWORD
0, // OP_SET_SCENE_ENTRY_FLAG
0, // OP_WALK_NPC_TO_CANIM
0, // OP_WALK_HOLMES_AND_NPC_TO_COORDS
0, // OP_WALK_HOLMES_AND_NPC_TO_COORDS
0, // OP_SET_NPC_TALK_FILE
0, // OP_TURN_NPC_OFF
0, // OP_TURN_NPC_ON
0, // OP_NPC_DESC_ON_OFF
0, // OP_NPC_PATH_PAUSE_TAKING_NOTES
0, // OP_NPC_PATH_PAUSE_LOOKING_HOLMES
0, // OP_ENABLE_TALK_INTERRUPTS
0, // OP_DISABLE_TALK_INTERRUPTS
0, // OP_SET_NPC_INFO_LINE
0, // OP_SET_NPC_POSITION
0, // OP_NPC_PATH_LABEL
0, // OP_PATH_GOTO_LABEL
0, // OP_PATH_IF_FLAG_GOTO_LABEL
0, // OP_NPC_WALK_GRAPHICS
0, // OP_NPC_VERB
0, // OP_NPC_VERB_CANIM
0, // OP_NPC_VERB_SCRIPT
0, // OP_RESTORE_PEOPLE_SEQUENCE
0, // OP_NPC_VERB_TARGET
0, // OP_TURN_SOUNDS_OFF
0 // OP_NULL
};
/*----------------------------------------------------------------*/
ScalpelTalk::ScalpelTalk(SherlockEngine *vm) : Talk(vm) {
static OpcodeMethod OPCODE_METHODS[] = {
(OpcodeMethod)&ScalpelTalk::cmdSwitchSpeaker,
(OpcodeMethod)&ScalpelTalk::cmdRunCAnimation,
(OpcodeMethod)&ScalpelTalk::cmdAssignPortraitLocation,
(OpcodeMethod)&ScalpelTalk::cmdPause,
(OpcodeMethod)&ScalpelTalk::cmdRemovePortrait,
(OpcodeMethod)&ScalpelTalk::cmdClearWindow,
(OpcodeMethod)&ScalpelTalk::cmdAdjustObjectSequence,
(OpcodeMethod)&ScalpelTalk::cmdWalkToCoords,
(OpcodeMethod)&ScalpelTalk::cmdPauseWithoutControl,
(OpcodeMethod)&ScalpelTalk::cmdBanishWindow,
(OpcodeMethod)&ScalpelTalk::cmdSummonWindow,
(OpcodeMethod)&ScalpelTalk::cmdSetFlag,
(OpcodeMethod)&ScalpelTalk::cmdSfxCommand,
(OpcodeMethod)&ScalpelTalk::cmdToggleObject,
(OpcodeMethod)&ScalpelTalk::cmdStealthModeActivate,
(OpcodeMethod)&ScalpelTalk::cmdIf,
(OpcodeMethod)&ScalpelTalk::cmdElse,
nullptr,
(OpcodeMethod)&ScalpelTalk::cmdStealthModeDeactivate,
(OpcodeMethod)&ScalpelTalk::cmdHolmesOff,
(OpcodeMethod)&ScalpelTalk::cmdHolmesOn,
(OpcodeMethod)&ScalpelTalk::cmdGotoScene,
(OpcodeMethod)&ScalpelTalk::cmdPlayPrologue,
(OpcodeMethod)&ScalpelTalk::cmdAddItemToInventory,
(OpcodeMethod)&ScalpelTalk::cmdSetObject,
(OpcodeMethod)&ScalpelTalk::cmdCallTalkFile,
(OpcodeMethod)&ScalpelTalk::cmdMoveMouse,
(OpcodeMethod)&ScalpelTalk::cmdDisplayInfoLine,
(OpcodeMethod)&ScalpelTalk::cmdClearInfoLine,
(OpcodeMethod)&ScalpelTalk::cmdWalkToCAnimation,
(OpcodeMethod)&ScalpelTalk::cmdRemoveItemFromInventory,
(OpcodeMethod)&ScalpelTalk::cmdEnableEndKey,
(OpcodeMethod)&ScalpelTalk::cmdDisableEndKey,
(OpcodeMethod)&ScalpelTalk::cmdEndTextWindow,
nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr,
nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr,
nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr,
nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr,
nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr,
nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr,
nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr
};
_opcodeTable = OPCODE_METHODS;
_opcodes = SCALPEL_OPCODES;
if (vm->getLanguage() == Common::DE_DEU || vm->getLanguage() == Common::ES_ESP) {
// The German and Spanish versions use a different opcode range
static byte opcodes[sizeof(SCALPEL_OPCODES)];
for (uint idx = 0; idx < sizeof(SCALPEL_OPCODES); ++idx)
opcodes[idx] = SCALPEL_OPCODES[idx] ? SCALPEL_OPCODES[idx] + 47 : 0;
_opcodes = opcodes;
}
_fixedTextWindowExit = FIXED(Window_Exit);
_fixedTextWindowUp = FIXED(Window_Up);
_fixedTextWindowDown = FIXED(Window_Down);
_hotkeyWindowExit = toupper(_fixedTextWindowExit[0]);
_hotkeyWindowUp = toupper(_fixedTextWindowUp[0]);
_hotkeyWindowDown = toupper(_fixedTextWindowDown[0]);
}
void ScalpelTalk::talkTo(const Common::String filename) {
ScalpelUserInterface &ui = *(ScalpelUserInterface *)_vm->_ui;
Talk::talkTo(filename);
if (filename == "Tube59c") {
// WORKAROUND: Original game bug causes the results of testing the powdery substance
// to disappear too quickly. Introduce a delay to allow it to be properly displayed
ui._menuCounter = 30;
} else if (filename == "Lesl24z.tlk" || filename == "Beal40y.tlk") {
// WORKAROUND: Walking to the flower girl or housekeeper the
// first time triggers this automatic talk. This should abort
// any other action, such as trying to look at her, else the UI
// gets corrupted
_talkToAbort = true;
}
}
void ScalpelTalk::talkInterface(const byte *&str) {
People &people = *_vm->_people;
ScalpelScreen &screen = *(ScalpelScreen *)_vm->_screen;
UserInterface &ui = *_vm->_ui;
if (_vm->getLanguage() == Common::DE_DEU)
skipBadText(str);
// If the window isn't yet open, draw the window before printing starts
if (!ui._windowOpen && _noTextYet) {
_noTextYet = false;
drawInterface();
if (_talkTo != -1) {
screen.buttonPrint(Common::Point(119, CONTROLS_Y), COMMAND_NULL, false, _fixedTextWindowExit);
screen.buttonPrint(Common::Point(159, CONTROLS_Y), COMMAND_NULL, false, _fixedTextWindowUp);
screen.buttonPrint(Common::Point(200, CONTROLS_Y), COMMAND_NULL, false, _fixedTextWindowDown);
}
}
// If it's the first line, display the speaker
if (!_line && _speaker >= 0 && _speaker < (int)people._characters.size()) {
// If the window is open, display the name directly on-screen.
// Otherwise, simply draw it on the back buffer
if (ui._windowOpen) {
screen.print(Common::Point(16, _yp), TALK_FOREGROUND, "%s",
people._characters[_speaker & 127]._name);
} else {
screen.gPrint(Common::Point(16, _yp - 1), TALK_FOREGROUND, "%s",
people._characters[_speaker & 127]._name);
_openTalkWindow = true;
}
_yp += 9;
}
// Find amount of text that will fit on the line
int width = 0, idx = 0;
do {
width += screen.charWidth(str[idx]);
++idx;
++_charCount;
} while (width < 298 && str[idx] && str[idx] != '{' && (!isOpcode(str[idx])));
if (str[idx] || width >= 298) {
if ((!isOpcode(str[idx])) && str[idx] != '{') {
--idx;
--_charCount;
}
} else {
_endStr = true;
}
// If word wrap is needed, find the start of the current word
if (width >= 298) {
while (str[idx] != ' ') {
--idx;
--_charCount;
}
}
// Print the line
Common::String lineStr((const char *)str, (const char *)str + idx);
// If the speaker indicates a description file, print it in yellow
if (_speaker != -1) {
if (ui._windowOpen) {
screen.print(Common::Point(16, _yp), COMMAND_FOREGROUND, "%s", lineStr.c_str());
} else {
screen.gPrint(Common::Point(16, _yp - 1), COMMAND_FOREGROUND, "%s", lineStr.c_str());
_openTalkWindow = true;
}
} else {
if (ui._windowOpen) {
screen.print(Common::Point(16, _yp), COMMAND_FOREGROUND, "%s", lineStr.c_str());
} else {
screen.gPrint(Common::Point(16, _yp - 1), COMMAND_FOREGROUND, "%s", lineStr.c_str());
_openTalkWindow = true;
}
}
// Move to end of displayed line
str += idx;
// If line wrap occurred, then move to after the separating space between the words
if (str[0] && (!isOpcode(str[0])) && str[0] != '{')
++str;
_yp += 9;
++_line;
// Certain different conditions require a wait
if ((_line == 4 && str < _scriptEnd && str[0] != _opcodes[OP_SFX_COMMAND] && str[0] != _opcodes[OP_PAUSE] && _speaker != -1) ||
(_line == 5 && str < _scriptEnd && str[0] != _opcodes[OP_PAUSE] && _speaker == -1) ||
_endStr) {
_wait = 1;
}
byte v = (str >= _scriptEnd ? 0 : str[0]);
if (v == _opcodes[OP_SWITCH_SPEAKER] || v == _opcodes[OP_ASSIGN_PORTRAIT_LOCATION] ||
v == _opcodes[OP_BANISH_WINDOW] || v == _opcodes[OP_IF_STATEMENT] ||
v == _opcodes[OP_ELSE_STATEMENT] || v == _opcodes[OP_END_IF_STATEMENT] ||
v == _opcodes[OP_GOTO_SCENE] || v == _opcodes[OP_CALL_TALK_FILE]) {
_wait = 1;
}
}
OpcodeReturn ScalpelTalk::cmdSwitchSpeaker(const byte *&str) {
ScalpelPeople &people = *(ScalpelPeople *)_vm->_people;
UserInterface &ui = *_vm->_ui;
if (!(_speaker & SPEAKER_REMOVE))
people.clearTalking();
if (_talkToAbort)
return RET_EXIT;
ui.clearWindow();
_yp = CONTROLS_Y + 12;
_charCount = _line = 0;
_speaker = *++str - 1;
people.setTalking(_speaker);
pullSequence();
pushSequence(_speaker);
people.setTalkSequence(_speaker);
return RET_SUCCESS;
}
OpcodeReturn ScalpelTalk::cmdGotoScene(const byte *&str) {
ScalpelMap &map = *(ScalpelMap *)_vm->_map;
People &people = *_vm->_people;
Scene &scene = *_vm->_scene;
scene._goToScene = str[1] - 1;
if (scene._goToScene != OVERHEAD_MAP) {
// Not going to the map overview
map._oldCharPoint = scene._goToScene;
map._overPos.x = (map[scene._goToScene].x - 6) * FIXED_INT_MULTIPLIER;
map._overPos.y = (map[scene._goToScene].y + 9) * FIXED_INT_MULTIPLIER;
// Run a canimation?
if (str[2] > 100) {
people._savedPos = PositionFacing(160, 100, str[2]);
} else {
int32 posX = (str[3] - 1) * 256 + str[4] - 1;
int32 posY = str[5] - 1;
people._savedPos = PositionFacing(posX, posY, str[2] - 1);
}
}
str += 6;
_scriptMoreFlag = (scene._goToScene == 100) ? 2 : 1;
_scriptSaveIndex = str - _scriptStart;
_endStr = true;
_wait = 0;
return RET_SUCCESS;
}
OpcodeReturn ScalpelTalk::cmdAssignPortraitLocation(const byte *&str) {
People &people = *_vm->_people;
++str;
switch (str[0] & 15) {
case 1:
people._portraitSide = 20;
break;
case 2:
people._portraitSide = 220;
break;
case 3:
people._portraitSide = 120;
break;
default:
break;
}
if (str[0] > 15)
people._speakerFlip = true;
return RET_SUCCESS;
}
OpcodeReturn ScalpelTalk::cmdClearInfoLine(const byte *&str) {
UserInterface &ui = *_vm->_ui;
ui._infoFlag = true;
ui.clearInfo();
return RET_SUCCESS;
}
OpcodeReturn ScalpelTalk::cmdClearWindow(const byte *&str) {
UserInterface &ui = *_vm->_ui;
ui.clearWindow();
_yp = CONTROLS_Y + 12;
_charCount = _line = 0;
return RET_SUCCESS;
}
OpcodeReturn ScalpelTalk::cmdDisplayInfoLine(const byte *&str) {
Screen &screen = *_vm->_screen;
UserInterface &ui = *_vm->_ui;
Common::String tempString;
++str;
for (int idx = 0; idx < str[0]; ++idx)
tempString += str[idx + 1];
str += str[0];
screen.print(Common::Point(0, INFO_LINE + 1), INFO_FOREGROUND, "%s", tempString.c_str());
ui._menuCounter = 30;
return RET_SUCCESS;
}
OpcodeReturn ScalpelTalk::cmdElse(const byte *&str) {
// If this is encountered here, it means that a preceeding IF statement was found,
// and evaluated to true. Now all the statements for the true block are finished,
// so skip over the block of code that would have executed if the result was false
_wait = 0;
do {
++str;
} while (str[0] && str[0] != _opcodes[OP_END_IF_STATEMENT]);
return RET_SUCCESS;
}
OpcodeReturn ScalpelTalk::cmdIf(const byte *&str) {
++str;
int flag = (str[0] - 1) * 256 + str[1] - 1 - (str[1] == 1 ? 1 : 0);
++str;
_wait = 0;
bool result = flag < 0x8000;
if (_vm->readFlags(flag & 0x7fff) != result) {
do {
++str;
} while (str[0] && str[0] != _opcodes[OP_ELSE_STATEMENT] && str[0] != _opcodes[OP_END_IF_STATEMENT]);
if (!str[0])
_endStr = true;
}
return RET_SUCCESS;
}
OpcodeReturn ScalpelTalk::cmdMoveMouse(const byte *&str) {
Events &events = *_vm->_events;
++str;
events.warpMouse(Common::Point((str[0] - 1) * 256 + str[1] - 1, str[2]));
if (_talkToAbort)
return RET_EXIT;
str += 3;
return RET_SUCCESS;
}
OpcodeReturn ScalpelTalk::cmdPlayPrologue(const byte *&str) {
Animation &anim = *_vm->_animation;
Common::String tempString;
++str;
for (int idx = 0; idx < 8 && str[idx] != '~'; ++idx)
tempString += str[idx];
anim.play(tempString, false, 1, 3, true, 4);
return RET_SUCCESS;
}
OpcodeReturn ScalpelTalk::cmdRemovePortrait(const byte *&str) {
People &people = *_vm->_people;
if (_speaker >= 0 && _speaker < SPEAKER_REMOVE)
people.clearTalking();
pullSequence();
if (_talkToAbort)
return RET_EXIT;
_speaker |= SPEAKER_REMOVE;
return RET_SUCCESS;
}
OpcodeReturn ScalpelTalk::cmdWalkToCoords(const byte *&str) {
People &people = *_vm->_people;
++str;
people[HOLMES].walkToCoords(Point32(((str[0] - 1) * 256 + str[1] - 1) * FIXED_INT_MULTIPLIER,
str[2] * FIXED_INT_MULTIPLIER), str[3] - 1);
if (_talkToAbort)
return RET_EXIT;
str += 3;
return RET_SUCCESS;
}
OpcodeReturn ScalpelTalk::cmdSfxCommand(const byte *&str) {
Sound &sound = *_vm->_sound;
Common::String tempString;
++str;
if (sound._voices) {
for (int idx = 0; idx < 8 && str[idx] != '~'; ++idx)
tempString += str[idx];
sound.playSpeech(tempString);
// Set voices to wait for more
sound._voices = 2;
}
_wait = 1;
str += 7;
return RET_SUCCESS;
}
OpcodeReturn ScalpelTalk::cmdSummonWindow(const byte *&str) {
Events &events = *_vm->_events;
ScalpelScreen &screen = *(ScalpelScreen *)_vm->_screen;
drawInterface();
events._pressed = events._released = false;
events.clearKeyboard();
_noTextYet = false;
if (_speaker != -1) {
screen.buttonPrint(Common::Point(119, CONTROLS_Y), COMMAND_NULL, false, _fixedTextWindowExit);
screen.buttonPrint(Common::Point(159, CONTROLS_Y), COMMAND_NULL, false, _fixedTextWindowUp);
screen.buttonPrint(Common::Point(200, CONTROLS_Y), COMMAND_NULL, false, _fixedTextWindowDown);
}
return RET_SUCCESS;
}
void ScalpelTalk::loadTalkFile(const Common::String &filename) {
Talk::loadTalkFile(filename);
_3doSpeechIndex = 0;
}
void ScalpelTalk::talkWait(const byte *&str) {
UserInterface &ui = *_vm->_ui;
bool pauseFlag = _pauseFlag;
Talk::talkWait(str);
// Clear the window unless the wait was due to a PAUSE command
if (!pauseFlag && _wait != -1 && str < _scriptEnd && str[0] != _opcodes[OP_SFX_COMMAND]) {
if (!_talkStealth)
ui.clearWindow();
_yp = CONTROLS_Y + 12;
_charCount = _line = 0;
}
}
void ScalpelTalk::nothingToSay() {
error("Character had no talk options available");
}
void ScalpelTalk::switchSpeaker() {
}
int ScalpelTalk::waitForMore(int delay) {
Events &events = *_vm->_events;
if (!IS_3DO) {
return Talk::waitForMore(delay);
}
// Hide the cursor
events.hideCursor();
events.wait(1);
switchSpeaker();
// Play the video
talk3DOMovieTrigger(_3doSpeechIndex++);
// Adjust _talkStealth mode:
// mode 1 - It was by a pause without stealth being on before the pause, so reset back to 0
// mode 3 - It was set by a pause with stealth being on before the pause, to set it to active
// mode 0/2 (Inactive/active) No change
switch (_talkStealth) {
case 1:
_talkStealth = 0;
break;
case 2:
_talkStealth = 2;
break;
default:
break;
}
events.showCursor();
events._pressed = events._released = false;
return 254;
}
bool ScalpelTalk::talk3DOMovieTrigger(int subIndex) {
ScalpelEngine &vm = *(ScalpelEngine *)_vm;
Screen &screen = *_vm->_screen;
// Find out a few things that we need
int userSelector = _vm->_ui->_selector;
int scriptSelector = _scriptSelect;
int selector = 0;
int roomNr = _vm->_scene->_currentScene;
if (userSelector >= 0) {
// User-selected dialog
selector = userSelector;
} else {
if (scriptSelector >= 0) {
// Script-selected dialog
selector = scriptSelector;
} else {
warning("talk3DOMovieTrigger: unable to find selector");
return true;
}
}
// Make a quick update, so that current text is shown on screen
screen.update();
// Figure out that movie filename
Common::String movieFilename;
movieFilename = _scriptName;
movieFilename.deleteChar(1); // remove 2nd character of scriptname
// cut scriptname to 6 characters
while (movieFilename.size() > 6) {
movieFilename.deleteChar(6);
}
movieFilename.insertChar(selector + 'a', movieFilename.size());
movieFilename.insertChar(subIndex + 'a', movieFilename.size());
movieFilename = Common::String::format("movies/%02d/%s.stream", roomNr, movieFilename.c_str());
warning("3DO movie player:");
warning("room: %d", roomNr);
warning("script: %s", _scriptName.c_str());
warning("selector: %d", selector);
warning("subindex: %d", subIndex);
bool result = vm.play3doMovie(movieFilename, get3doPortraitPosition(), true);
// Restore screen HACK
_vm->_screen->makeAllDirty();
return result;
}
Common::Point ScalpelTalk::get3doPortraitPosition() const {
// TODO: This current method is only an assumption of how the original figured
// out where to place each character's portrait movie.
People &people = *_vm->_people;
Scene &scene = *_vm->_scene;
const int PORTRAIT_W = 100;
const int PORTRAIT_H = 76;
if (_speaker == -1)
return Common::Point();
// Get the position of the character
Common::Point pt;
if (_speaker == HOLMES) {
pt = Common::Point(people[HOLMES]._position.x / FIXED_INT_MULTIPLIER,
people[HOLMES]._position.y / FIXED_INT_MULTIPLIER);
} else {
int objNum = people.findSpeaker(_speaker);
if (objNum == -1)
return Common::Point();
pt = scene._bgShapes[objNum]._position;
}
// Adjust the top-left so the center of the portrait will be on the character,
// but ensure the portrait will be entirely on-screen
pt -= Common::Point(PORTRAIT_W / 2, PORTRAIT_H / 2);
pt.x = CLIP((int)pt.x, 10, SHERLOCK_SCREEN_WIDTH - 10 - PORTRAIT_W);
pt.y = CLIP((int)pt.y, 10, CONTROLS_Y - PORTRAIT_H - 10);
return pt;
}
void ScalpelTalk::drawInterface() {
ScalpelScreen &screen = *(ScalpelScreen *)_vm->_screen;
Surface &bb = *screen.getBackBuffer();
bb.fillRect(Common::Rect(0, CONTROLS_Y, SHERLOCK_SCREEN_WIDTH, CONTROLS_Y1 + 10), BORDER_COLOR);
bb.fillRect(Common::Rect(0, CONTROLS_Y + 10, 2, SHERLOCK_SCREEN_HEIGHT), BORDER_COLOR);
bb.fillRect(Common::Rect(SHERLOCK_SCREEN_WIDTH - 2, CONTROLS_Y + 10,
SHERLOCK_SCREEN_WIDTH, SHERLOCK_SCREEN_HEIGHT), BORDER_COLOR);
bb.fillRect(Common::Rect(0, SHERLOCK_SCREEN_HEIGHT - 1, SHERLOCK_SCREEN_WIDTH - 2,
SHERLOCK_SCREEN_HEIGHT), BORDER_COLOR);
bb.fillRect(Common::Rect(2, CONTROLS_Y + 10, SHERLOCK_SCREEN_WIDTH - 2,
SHERLOCK_SCREEN_HEIGHT - 1), INV_BACKGROUND);
if (_talkTo != -1) {
Common::String fixedText_Exit = FIXED(Window_Exit);
Common::String fixedText_Up = FIXED(Window_Up);
Common::String fixedText_Down = FIXED(Window_Down);
screen.makeButton(Common::Rect(99, CONTROLS_Y, 139, CONTROLS_Y + 10),
119, fixedText_Exit);
screen.makeButton(Common::Rect(140, CONTROLS_Y, 180, CONTROLS_Y + 10),
159, fixedText_Up);
screen.makeButton(Common::Rect(181, CONTROLS_Y, 221, CONTROLS_Y + 10),
200, fixedText_Down);
} else {
Common::String fixedText_PressKeyToContinue = FIXED(PressKey_ToContinue);
screen.makeButton(Common::Rect(46, CONTROLS_Y, 273, CONTROLS_Y + 10),
160, fixedText_PressKeyToContinue);
}
}
bool ScalpelTalk::displayTalk(bool slamIt) {
ScalpelScreen &screen = *(ScalpelScreen *)_vm->_screen;
int yp = CONTROLS_Y + 14;
int lineY = -1;
_moreTalkDown = _moreTalkUp = false;
for (uint idx = 0; idx < _statements.size(); ++idx) {
_statements[idx]._talkPos.top = _statements[idx]._talkPos.bottom = -1;
}
if (_talkIndex) {
for (int idx = 0; idx < _talkIndex && !_moreTalkUp; ++idx) {
if (_statements[idx]._talkMap != -1)
_moreTalkUp = true;
}
}
// Display the up arrow and enable Up button if the first option is scrolled off-screen
if (_moreTalkUp) {
if (slamIt) {
screen.print(Common::Point(5, CONTROLS_Y + 13), INV_FOREGROUND, "~");
screen.buttonPrint(Common::Point(159, CONTROLS_Y), COMMAND_FOREGROUND, true, _fixedTextWindowUp);
} else {
screen.gPrint(Common::Point(5, CONTROLS_Y + 12), INV_FOREGROUND, "~");
screen.buttonPrint(Common::Point(159, CONTROLS_Y), COMMAND_FOREGROUND, false, _fixedTextWindowUp);
}
} else {
if (slamIt) {
screen.buttonPrint(Common::Point(159, CONTROLS_Y), COMMAND_NULL, true, _fixedTextWindowUp);
screen.vgaBar(Common::Rect(5, CONTROLS_Y + 11, 15, CONTROLS_Y + 22), INV_BACKGROUND);
} else {
screen.buttonPrint(Common::Point(159, CONTROLS_Y), COMMAND_NULL, false, _fixedTextWindowUp);
screen._backBuffer1.fillRect(Common::Rect(5, CONTROLS_Y + 11,
15, CONTROLS_Y + 22), INV_BACKGROUND);
}
}
// Loop through the statements
bool done = false;
for (uint idx = _talkIndex; idx < _statements.size() && !done; ++idx) {
Statement &statement = _statements[idx];
if (statement._talkMap != -1) {
bool flag = _talkHistory[_converseNum][idx];
lineY = talkLine(idx, statement._talkMap, flag ? (byte)TALK_NULL : (byte)INV_FOREGROUND,
yp, slamIt);
if (lineY != -1) {
statement._talkPos.top = yp;
yp = lineY;
statement._talkPos.bottom = yp;
if (yp == SHERLOCK_SCREEN_HEIGHT)
done = true;
} else {
done = true;
}
}
}
// Display the down arrow and enable down button if there are more statements available down off-screen
if (lineY == -1 || lineY == SHERLOCK_SCREEN_HEIGHT) {
_moreTalkDown = true;
if (slamIt) {
screen.print(Common::Point(5, 190), INV_FOREGROUND, "|");
screen.buttonPrint(Common::Point(200, CONTROLS_Y), COMMAND_FOREGROUND, true, _fixedTextWindowDown);
} else {
screen.gPrint(Common::Point(5, 189), INV_FOREGROUND, "|");
screen.buttonPrint(Common::Point(200, CONTROLS_Y), COMMAND_FOREGROUND, false, _fixedTextWindowDown);
}
} else {
if (slamIt) {
screen.buttonPrint(Common::Point(200, CONTROLS_Y), COMMAND_NULL, true, _fixedTextWindowDown);
screen.vgaBar(Common::Rect(5, 189, 16, 199), INV_BACKGROUND);
} else {
screen.buttonPrint(Common::Point(200, CONTROLS_Y), COMMAND_NULL, false, _fixedTextWindowDown);
screen._backBuffer1.fillRect(Common::Rect(5, 189, 16, 199), INV_BACKGROUND);
}
}
return done;
}
int ScalpelTalk::talkLine(int lineNum, int stateNum, byte color, int lineY, bool slamIt) {
Screen &screen = *_vm->_screen;
int idx = lineNum;
Common::String msg, number;
bool numberFlag = false;
// Get the statement to display as well as optional number prefix
if (idx < SPEAKER_REMOVE) {
number = Common::String::format("%d.", stateNum + 1);
numberFlag = true;
} else {
idx -= SPEAKER_REMOVE;
}
msg = _statements[idx]._statement;
// Handle potentially multiple lines needed to display entire statement
const char *lineStartP = msg.c_str();
int maxWidth = 298 - (numberFlag ? 18 : 0);
for (;;) {
// Get as much of the statement as possible will fit on the
Common::String sLine;
const char *lineEndP = lineStartP;
int width = 0;
do {
width += screen.charWidth(*lineEndP);
} while (*++lineEndP && width < maxWidth);
// Check if we need to wrap the line
if (width >= maxWidth) {
// Work backwards to the prior word's end
while (*--lineEndP != ' ')
;
sLine = Common::String(lineStartP, lineEndP++);
} else {
// Can display remainder of the statement on the current line
sLine = Common::String(lineStartP);
}
if (lineY <= (SHERLOCK_SCREEN_HEIGHT - 10)) {
// Need to directly display on-screen?
if (slamIt) {
// See if a numer prefix is needed or not
if (numberFlag) {
// Are we drawing the first line?
if (lineStartP == msg.c_str()) {
// We are, so print the number and then the text
screen.print(Common::Point(16, lineY), color, "%s", number.c_str());
}
// Draw the line with an indent
screen.print(Common::Point(30, lineY), color, "%s", sLine.c_str());
} else {
screen.print(Common::Point(16, lineY), color, "%s", sLine.c_str());
}
} else {
if (numberFlag) {
if (lineStartP == msg.c_str()) {
screen.gPrint(Common::Point(16, lineY - 1), color, "%s", number.c_str());
}
screen.gPrint(Common::Point(30, lineY - 1), color, "%s", sLine.c_str());
} else {
screen.gPrint(Common::Point(16, lineY - 1), color, "%s", sLine.c_str());
}
}
// Move to next line, if any
lineY += 9;
lineStartP = lineEndP;
if (!*lineEndP)
break;
} else {
// We're close to the bottom of the screen, so stop display
lineY = -1;
break;
}
}
if (lineY == -1 && lineStartP != msg.c_str())
lineY = SHERLOCK_SCREEN_HEIGHT;
// Return the Y position of the next line to follow this one
return lineY;
}
void ScalpelTalk::showTalk() {
ScalpelScreen &screen = *(ScalpelScreen *)_vm->_screen;
ScalpelUserInterface &ui = *(ScalpelUserInterface *)_vm->_ui;
byte color = ui._endKeyActive ? COMMAND_FOREGROUND : COMMAND_NULL;
if (!ui._windowOpen) {
// Draw the talk interface on the back buffer
drawInterface();
displayTalk(false);
} else {
displayTalk(true);
}
// If the window is already open, simply draw. Otherwise, do it
// to the back buffer and then summon the window
if (ui._windowOpen) {
screen.buttonPrint(Common::Point(119, CONTROLS_Y), color, true, _fixedTextWindowExit);
} else {
screen.buttonPrint(Common::Point(119, CONTROLS_Y), color, false, _fixedTextWindowExit);
if (!ui._slideWindows) {
screen.slamRect(Common::Rect(0, CONTROLS_Y,
SHERLOCK_SCREEN_WIDTH, SHERLOCK_SCREEN_HEIGHT));
} else {
ui.summonWindow();
}
ui._windowOpen = true;
}
}
OpcodeReturn ScalpelTalk::cmdCallTalkFile(const byte *&str) {
Common::String tempString;
++str;
for (int idx = 0; idx < 8 && str[idx] != '~'; ++idx)
tempString += str[idx];
str += 8;
int scriptCurrentIndex = str - _scriptStart;
// Save the current script position and new talk file
if (_scriptStack.size() < 9) {
ScriptStackEntry rec1;
rec1._name = _scriptName;
rec1._currentIndex = scriptCurrentIndex;
rec1._select = _scriptSelect;
_scriptStack.push(rec1);
// Push the new talk file onto the stack
ScriptStackEntry rec2;
rec2._name = tempString;
rec2._currentIndex = 0;
rec2._select = 100;
_scriptStack.push(rec2);
} else {
error("Script stack overflow");
}
_scriptMoreFlag = 1;
_endStr = true;
_wait = 0;
return RET_SUCCESS;
}
void ScalpelTalk::pushSequenceEntry(Object *obj) {
Scene &scene = *_vm->_scene;
SequenceEntry seqEntry;
seqEntry._objNum = scene._bgShapes.indexOf(*obj);
if (seqEntry._objNum != -1) {
for (uint idx = 0; idx < MAX_TALK_SEQUENCES; ++idx)
seqEntry._sequences.push_back(obj->_sequences[idx]);
seqEntry._frameNumber = obj->_frameNumber;
seqEntry._seqTo = obj->_seqTo;
}
_sequenceStack.push(seqEntry);
if (_scriptStack.size() >= 5)
error("script stack overflow");
}
void ScalpelTalk::pullSequence(int slot) {
Scene &scene = *_vm->_scene;
if (_sequenceStack.empty())
return;
SequenceEntry seq = _sequenceStack.pop();
if (seq._objNum != -1) {
Object &obj = scene._bgShapes[seq._objNum];
if (obj._seqSize < MAX_TALK_SEQUENCES) {
warning("Tried to restore too few frames");
} else {
for (int idx = 0; idx < MAX_TALK_SEQUENCES; ++idx)
obj._sequences[idx] = seq._sequences[idx];
obj._frameNumber = seq._frameNumber;
obj._seqTo = seq._seqTo;
}
}
}
void ScalpelTalk::clearSequences() {
_sequenceStack.clear();
}
void ScalpelTalk::skipBadText(const byte *&msgP) {
// WORKAROUND: Skip over bad text in the original game
const char *BAD_PHRASE1 = "Change Speaker to Sherlock Holmes ";
if (!strncmp((const char *)msgP, BAD_PHRASE1, strlen(BAD_PHRASE1)))
msgP += strlen(BAD_PHRASE1);
}
} // End of namespace Scalpel
} // End of namespace Sherlock