mirror of
https://github.com/libretro/scummvm.git
synced 2025-04-17 22:20:12 +00:00

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.
1014 lines
29 KiB
C++
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
|