mirror of
https://github.com/libretro/scummvm.git
synced 2024-12-23 18:24:59 +00:00
1223 lines
32 KiB
C++
1223 lines
32 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 2
|
|
* 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, write to the Free Software
|
|
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
|
*
|
|
*/
|
|
|
|
#include "common/str-array.h"
|
|
|
|
#include "tsage/tsage.h"
|
|
#include "tsage/globals.h"
|
|
#include "tsage/staticres.h"
|
|
#include "ringworld2/ringworld2_speakers.h"
|
|
|
|
namespace TsAGE {
|
|
|
|
#define STRIP_WORD_DELAY 30
|
|
|
|
|
|
SequenceManager::SequenceManager() : Action() {
|
|
Common::fill(&_objectList[0], &_objectList[6], (SceneObject *)NULL);
|
|
_sequenceData.clear();
|
|
_fontNum = 0;
|
|
_sequenceOffset = 0;
|
|
_resNum = 0;
|
|
_field26 = 0;
|
|
_objectIndex = 0;
|
|
_keepActive = false;
|
|
_onCallback = NULL;
|
|
for (int i = 0; i < 6; i ++)
|
|
_objectList[i] = NULL;
|
|
setup();
|
|
}
|
|
|
|
void SequenceManager::setup() {
|
|
_sequenceOffset = 0;
|
|
_objectIndex = 0;
|
|
_sceneObject = _objectList[0];
|
|
}
|
|
|
|
void SequenceManager::synchronize(Serializer &s) {
|
|
if (s.getVersion() >= 2)
|
|
Action::synchronize(s);
|
|
|
|
s.syncAsSint32LE(_resNum);
|
|
s.syncAsSint32LE(_sequenceOffset);
|
|
s.syncAsByte(_keepActive);
|
|
s.syncAsSint32LE(_fontNum);
|
|
s.syncAsSint32LE(_field26);
|
|
|
|
s.syncAsSint32LE(_objectIndex);
|
|
SYNC_POINTER(_sceneObject);
|
|
for (int i = 0; i < 6; ++i)
|
|
SYNC_POINTER(_objectList[i]);
|
|
|
|
int seqSize = _sequenceData.size();
|
|
s.syncAsUint32LE(seqSize);
|
|
if (s.isLoading())
|
|
_sequenceData.resize(seqSize);
|
|
if (seqSize > 0)
|
|
s.syncBytes(&_sequenceData[0], seqSize);
|
|
}
|
|
|
|
void SequenceManager::remove() {
|
|
if ((!_sequenceData.empty()) && !_keepActive) {
|
|
_sequenceData.clear();
|
|
}
|
|
|
|
if (g_globals->_sceneObjects->contains(&_sceneText))
|
|
_sceneText.remove();
|
|
|
|
Common::fill(&_objectList[0], &_objectList[6], (SceneObject *)NULL);
|
|
Action::remove();
|
|
}
|
|
|
|
void SequenceManager::signal() {
|
|
if (g_globals->_sceneObjects->contains(&_sceneText))
|
|
_sceneText.hide();
|
|
|
|
bool continueFlag = true;
|
|
while (continueFlag) {
|
|
if (_sequenceOffset >=_sequenceData.size()) {
|
|
// Reached the end of the sequence
|
|
if (!_keepActive)
|
|
remove();
|
|
break;
|
|
}
|
|
|
|
uint16 idx = static_cast<uint16>(getNextValue() - 32000);
|
|
|
|
int16 v1, v2, v3;
|
|
switch (idx) {
|
|
case 0:
|
|
// Stop sequence
|
|
continueFlag = false;
|
|
break;
|
|
case 1:
|
|
_sceneObject->animate(ANIM_MODE_1, NULL);
|
|
break;
|
|
case 2:
|
|
_sceneObject->animate(ANIM_MODE_2, NULL);
|
|
break;
|
|
case 3:
|
|
_sceneObject->animate(ANIM_MODE_3);
|
|
break;
|
|
case 4:
|
|
v1 = getNextValue();
|
|
v2 = getNextValue();
|
|
_sceneObject->animate(ANIM_MODE_8, v1, v2 ? this : NULL);
|
|
break;
|
|
case 5:
|
|
v1 = getNextValue();
|
|
v2 = getNextValue();
|
|
_sceneObject->animate(ANIM_MODE_7, v1, v2 ? this : NULL);
|
|
break;
|
|
case 6:
|
|
v2 = getNextValue();
|
|
_sceneObject->animate(ANIM_MODE_5, v2 ? this : NULL);
|
|
break;
|
|
case 7:
|
|
v2 = getNextValue();
|
|
_sceneObject->animate(ANIM_MODE_6, v2 ? this : NULL);
|
|
break;
|
|
case 8:
|
|
v1 = getNextValue();
|
|
v3 = getNextValue();
|
|
v2 = getNextValue();
|
|
_sceneObject->animate(ANIM_MODE_4, v1, v3, v2 ? this : NULL);
|
|
break;
|
|
case 9:
|
|
v1 = getNextValue();
|
|
v3 = getNextValue();
|
|
v2 = getNextValue();
|
|
g_globals->_sceneManager._scene->_sceneBounds.moveTo(v3, v2);
|
|
g_globals->_sceneManager._scene->loadScene(v1);
|
|
break;
|
|
case 10: {
|
|
int resNum= getNextValue();
|
|
int lineNum = getNextValue();
|
|
int color = getNextValue();
|
|
int xp = getNextValue();
|
|
int yp = getNextValue();
|
|
int width = getNextValue();
|
|
setMessage(resNum, lineNum, color, Common::Point(xp, yp), width);
|
|
break;
|
|
}
|
|
case 11:
|
|
v1 = getNextValue();
|
|
v2 = getNextValue();
|
|
setAction(globalManager(), v2 ? this : NULL, v1, _objectList[0], _objectList[1], _objectList[2], _objectList[3], NULL);
|
|
break;
|
|
case 12:
|
|
v1 = getNextValue();
|
|
setDelay(v1);
|
|
break;
|
|
case 13: {
|
|
v1 = getNextValue();
|
|
v3 = getNextValue();
|
|
v2 = getNextValue();
|
|
NpcMover *mover = new NpcMover();
|
|
Common::Point destPos(v1, v3);
|
|
_sceneObject->addMover(mover, &destPos, v2 ? this : NULL);
|
|
break;
|
|
}
|
|
case 14:
|
|
v1 = getNextValue();
|
|
_sceneObject->_numFrames = v1;
|
|
break;
|
|
case 15:
|
|
v1 = getNextValue();
|
|
_sceneObject->_moveRate = v1;
|
|
break;
|
|
case 16:
|
|
v1 = getNextValue();
|
|
v2 = getNextValue();
|
|
_sceneObject->_moveDiff = Common::Point(v1, v2);
|
|
break;
|
|
case 17:
|
|
_sceneObject->hide();
|
|
break;
|
|
case 18:
|
|
_sceneObject->show();
|
|
break;
|
|
case 19:
|
|
v1 = getNextValue();
|
|
_sceneObject->setVisage(v1);
|
|
break;
|
|
case 20:
|
|
v1 = getNextValue();
|
|
_sceneObject->setStrip(v1);
|
|
break;
|
|
case 21:
|
|
v1 = getNextValue();
|
|
_sceneObject->setFrame(v1);
|
|
break;
|
|
case 22:
|
|
v1 = getNextValue();
|
|
_sceneObject->fixPriority(v1);
|
|
break;
|
|
case 23:
|
|
v1 = getNextValue();
|
|
_sceneObject->changeZoom(v1);
|
|
break;
|
|
case 24:
|
|
v1 = getNextValue();
|
|
v2 = getNextValue();
|
|
v3 = getNextValue();
|
|
_sceneObject->setPosition(Common::Point(v1, v2), v3);
|
|
break;
|
|
case 25: {
|
|
int yStart = getNextValue();
|
|
int minPercent = getNextValue();
|
|
int yEnd = getNextValue();
|
|
int maxPercent = getNextValue();
|
|
g_globals->_sceneManager._scene->setZoomPercents(yStart, minPercent, yEnd, maxPercent);
|
|
break;
|
|
}
|
|
case 26:
|
|
v1 = getNextValue();
|
|
v2 = getNextValue();
|
|
_soundHandler.play(v1, v2 ? this : NULL, 127);
|
|
break;
|
|
case 27: {
|
|
v1 = getNextValue();
|
|
v3 = getNextValue();
|
|
v2 = getNextValue();
|
|
PlayerMover *mover = new PlayerMover();
|
|
Common::Point destPos(v1, v3);
|
|
_sceneObject->addMover(mover, &destPos, v2 ? this : NULL);
|
|
break;
|
|
}
|
|
case 28:
|
|
_objectIndex = getNextValue();
|
|
assert((_objectIndex >= 0) && (_objectIndex < 6));
|
|
_sceneObject = _objectList[_objectIndex];
|
|
assert(_sceneObject);
|
|
break;
|
|
case 29:
|
|
_sceneObject->animate(ANIM_MODE_NONE);
|
|
break;
|
|
case 30:
|
|
v1 = getNextValue();
|
|
g_globals->_scrollFollower = (v1 == -1) ? NULL : _objectList[v1];
|
|
break;
|
|
case 31:
|
|
_sceneObject->setObjectWrapper(new SceneObjectWrapper());
|
|
break;
|
|
case 32:
|
|
_sceneObject->setObjectWrapper(NULL);
|
|
break;
|
|
case 33:
|
|
v1 = getNextValue();
|
|
if (_keepActive)
|
|
setDelay(1);
|
|
else {
|
|
_sceneText.remove();
|
|
g_globals->_sceneManager._scene->_stripManager.start(v1, this);
|
|
}
|
|
break;
|
|
case 34: {
|
|
v1 = getNextValue();
|
|
v2 = getNextValue();
|
|
int objIndex1 = getNextValue() - 1;
|
|
int objIndex2 = getNextValue() - 1;
|
|
int objIndex3 = getNextValue() - 1;
|
|
int objIndex4 = getNextValue() - 1;
|
|
int objIndex5 = getNextValue() - 1;
|
|
int objIndex6 = getNextValue() - 1;
|
|
|
|
setAction(globalManager(), v2 ? this : NULL, v1, _objectList[objIndex1], _objectList[objIndex2],
|
|
_objectList[objIndex3], _objectList[objIndex4], _objectList[objIndex5], _objectList[objIndex6], NULL);
|
|
break;
|
|
}
|
|
/* Following indexes were introduced for Blue Force */
|
|
case 35:
|
|
v1 = getNextValue();
|
|
_sceneObject->updateAngle(_objectList[v1]->_position);
|
|
break;
|
|
case 36:
|
|
_sceneObject->animate(ANIM_MODE_9, NULL);
|
|
break;
|
|
case 37:
|
|
v1 = getNextValue();
|
|
v2 = getNextValue();
|
|
if (_onCallback)
|
|
_onCallback(v1, v2);
|
|
break;
|
|
case 38: {
|
|
int resNum = getNextValue();
|
|
int lineNum = getNextValue();
|
|
int fontNum = getNextValue();
|
|
int color1 = getNextValue();
|
|
int color2 = getNextValue();
|
|
int color3 = getNextValue();
|
|
int xp = getNextValue();
|
|
int yp = getNextValue();
|
|
int width = getNextValue();
|
|
setMessage(resNum, lineNum, fontNum, color1, color2, color3, Common::Point(xp, yp), width);
|
|
break;
|
|
}
|
|
default:
|
|
error("SequenceManager::signal - Unknown action %d at offset %xh", idx, _sequenceOffset - 2);
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
void SequenceManager::process(Event &event) {
|
|
if (((event.eventType == EVENT_BUTTON_DOWN) || (event.eventType == EVENT_KEYPRESS)) &&
|
|
!event.handled && g_globals->_sceneObjects->contains(&_sceneText)) {
|
|
// Remove the text item
|
|
_sceneText.remove();
|
|
setDelay(2);
|
|
event.handled = true;
|
|
} else {
|
|
Action::process(event);
|
|
}
|
|
}
|
|
|
|
|
|
void SequenceManager::attached(EventHandler *newOwner, EventHandler *endHandler, va_list va) {
|
|
// Get the sequence number to use
|
|
_resNum = va_arg(va, int);
|
|
|
|
byte *seqData = g_resourceManager->getResource(RES_SEQUENCE, _resNum, 0);
|
|
uint seqSize = g_vm->_memoryManager.getSize(seqData);
|
|
|
|
_sequenceData.resize(seqSize);
|
|
Common::copy(seqData, seqData + seqSize, &_sequenceData[0]);
|
|
|
|
DEALLOCATE(seqData);
|
|
|
|
Common::fill(&_objectList[0], &_objectList[6], (SceneObject *)NULL);
|
|
for (int idx = 0; idx < 6; ++idx) {
|
|
_objectList[idx] = va_arg(va, SceneObject *);
|
|
if (!_objectList[idx])
|
|
break;
|
|
}
|
|
|
|
setup();
|
|
Action::attached(newOwner, endHandler, va);
|
|
}
|
|
|
|
/**
|
|
* Returns the next Id in the sequence
|
|
*/
|
|
uint16 SequenceManager::getNextValue() {
|
|
uint16 result = READ_LE_UINT16(&_sequenceData[0] + _sequenceOffset);
|
|
_sequenceOffset += 2;
|
|
return result;
|
|
}
|
|
|
|
void SequenceManager::setMessage(int resNum, int lineNum, int color, const Common::Point &pt, int width) {
|
|
setMessage(resNum, lineNum, 2, color, 0, 0, pt, width);
|
|
}
|
|
|
|
void SequenceManager::setMessage(int resNum, int lineNum, int fontNum, int color1, int color2, int color3,
|
|
const Common::Point &pt, int width) {
|
|
_sceneText._color1 = color1;
|
|
_sceneText._color2 = color2;
|
|
_sceneText._color3 = color3;
|
|
_sceneText._fontNumber = fontNum;
|
|
_sceneText._width = width;
|
|
|
|
// Get the display message
|
|
Common::String msg = g_resourceManager->getMessage(resNum, lineNum);
|
|
|
|
// Set the text message
|
|
_sceneText.setup(msg);
|
|
|
|
// Move the text to the correct position
|
|
Rect textRect = _sceneText._bounds;
|
|
Rect sceneBounds = g_globals->_sceneManager._scene->_sceneBounds;
|
|
sceneBounds.collapse(4, 2);
|
|
textRect.moveTo(pt);
|
|
textRect.contain(sceneBounds);
|
|
|
|
_sceneText.setPosition(Common::Point(textRect.left, textRect.top));
|
|
|
|
// Draw the text
|
|
_sceneText.fixPriority(255);
|
|
_sceneText.show();
|
|
|
|
// Set the delay based on the number of words
|
|
int numWords = 0;
|
|
const char *msgP = msg.c_str();
|
|
while (*msgP) {
|
|
if (*msgP++ == ' ')
|
|
++numWords;
|
|
}
|
|
|
|
setDelay(numWords * 18 + 120);
|
|
}
|
|
|
|
SequenceManager *SequenceManager::globalManager() {
|
|
return &g_globals->_sequenceManager;
|
|
}
|
|
|
|
/*--------------------------------------------------------------------------*/
|
|
|
|
ConversationChoiceDialog::ConversationChoiceDialog() {
|
|
_stdColor = 23;
|
|
_highlightColor = g_globals->_scenePalette._colors.background;
|
|
_fontNumber = (g_vm->getGameID() == GType_Ringworld2) ? 3 : 1;
|
|
_savedFgColor = _savedFontNumber = 0;
|
|
_selectedIndex = 0;
|
|
}
|
|
|
|
int ConversationChoiceDialog::execute(const Common::StringArray &choiceList) {
|
|
_gfxManager._font.setFontNumber(_fontNumber);
|
|
|
|
_bounds = Rect(40, 0, 40, 0);
|
|
_choiceList.clear();
|
|
|
|
// Set up the list of choices
|
|
int yp = 0;
|
|
for (uint idx = 0; idx < choiceList.size(); ++idx) {
|
|
Rect tempRect;
|
|
_gfxManager._font.getStringBounds(choiceList[idx].c_str(), tempRect, textMaxWidth());
|
|
tempRect.moveTo(textLeft(), yp + 10);
|
|
|
|
_choiceList.push_back(ChoiceEntry(choiceList[idx], tempRect));
|
|
yp += tempRect.height() + 5;
|
|
_bounds.extend(tempRect);
|
|
}
|
|
_selectedIndex = _choiceList.size();
|
|
|
|
// Set the position for the dialog
|
|
_bounds.bottom -= 10;
|
|
yp = 180 - _bounds.height();
|
|
_bounds.translate(0, yp);
|
|
_bounds.setWidth(textMaxWidth() + 15);
|
|
_bounds.moveTo(160 - (_bounds.width() / 2), _bounds.top);
|
|
|
|
// Draw the dialog
|
|
draw();
|
|
g_globals->_events.showCursor();
|
|
|
|
// WORKAROUND: On-screen dialogs are really meant to use a GfxManager instance
|
|
// for their lifetime, which prevents saving or loading. Since I don't want to spend a lot
|
|
// of time refactoring this already working dialog, fake it by putting a dummy gfxmanager at
|
|
// the end of the gfx manager list so as to prevent saving or loading
|
|
GfxManager gfxManager;
|
|
GLOBALS._gfxManagers.push_back(&gfxManager);
|
|
|
|
// Event handling loop
|
|
Event event;
|
|
while (!g_vm->shouldQuit()) {
|
|
while (!g_globals->_events.getEvent(event, EVENT_KEYPRESS | EVENT_BUTTON_DOWN | EVENT_MOUSE_MOVE) &&
|
|
!g_vm->shouldQuit()) {
|
|
g_system->delayMillis(10);
|
|
GLOBALS._screenSurface.updateScreen();
|
|
}
|
|
if (g_vm->shouldQuit())
|
|
break;
|
|
|
|
if ((event.eventType == EVENT_KEYPRESS) && (event.kbd.keycode >= Common::KEYCODE_1) &&
|
|
(event.kbd.keycode <= (Common::KEYCODE_0 + (int)_choiceList.size()))) {
|
|
// Selected an option by number
|
|
_selectedIndex = event.kbd.keycode - Common::KEYCODE_1;
|
|
break;
|
|
} else if ((_selectedIndex != _choiceList.size()) && ((event.eventType == EVENT_BUTTON_DOWN) ||
|
|
(event.eventType == EVENT_BUTTON_UP))) {
|
|
// Item selected
|
|
break;
|
|
} else {
|
|
// Check if any item is highlighted
|
|
event.mousePos.x -= _gfxManager._bounds.left;
|
|
event.mousePos.y -= _gfxManager._bounds.top;
|
|
|
|
uint idx = 0;
|
|
while ((idx < _choiceList.size()) && !_choiceList[idx]._bounds.contains(event.mousePos.x, event.mousePos.y))
|
|
++idx;
|
|
|
|
if (idx != _selectedIndex) {
|
|
if (_selectedIndex != _choiceList.size()) {
|
|
// De-highlight previously selected item
|
|
_gfxManager._font._colors.foreground = _stdColor;
|
|
_gfxManager._font.writeLines(_choiceList[_selectedIndex]._msg.c_str(),
|
|
_choiceList[_selectedIndex]._bounds, ALIGN_LEFT);
|
|
}
|
|
|
|
_selectedIndex = idx;
|
|
|
|
if (_selectedIndex != _choiceList.size()) {
|
|
// Highlight the new item
|
|
_gfxManager._font._colors.foreground = _highlightColor;
|
|
_gfxManager._font.writeLines(_choiceList[idx]._msg.c_str(), _choiceList[idx]._bounds, ALIGN_LEFT);
|
|
}
|
|
|
|
}
|
|
}
|
|
}
|
|
|
|
// Remove the dialog
|
|
remove();
|
|
GLOBALS._gfxManagers.remove(&gfxManager);
|
|
|
|
return _selectedIndex;
|
|
}
|
|
|
|
void ConversationChoiceDialog::draw() {
|
|
// Make a backup copy of the area the dialog will occupy
|
|
Rect tempRect = _bounds;
|
|
tempRect.collapse(-10, -10);
|
|
_savedArea = surfaceGetArea(g_globals->_gfxManagerInstance.getSurface(), tempRect);
|
|
|
|
// Fill in the contents of the entire dialog
|
|
_gfxManager._bounds = Rect(0, 0, SCREEN_WIDTH, SCREEN_HEIGHT);
|
|
|
|
drawFrame();
|
|
|
|
_gfxManager._bounds = tempRect;
|
|
_gfxManager._font._colors.foreground = _stdColor;
|
|
_gfxManager.activate();
|
|
|
|
// Loop through writing the conversation choices
|
|
for (uint idx = 0; idx < _choiceList.size(); ++idx) {
|
|
Common::String strNum = Common::String::format("%d", idx + 1);
|
|
|
|
// Write the choice number
|
|
_gfxManager._font.setPosition(numberLeft(), _choiceList[idx]._bounds.top);
|
|
_gfxManager._font.writeString(strNum.c_str());
|
|
|
|
_gfxManager._font.writeLines(_choiceList[idx]._msg.c_str(), _choiceList[idx]._bounds, ALIGN_LEFT);
|
|
}
|
|
|
|
_gfxManager.deactivate();
|
|
}
|
|
|
|
int ConversationChoiceDialog::textLeft() const {
|
|
return (g_vm->getGameID() == GType_Ringworld2) ? 20 : 25;
|
|
}
|
|
|
|
int ConversationChoiceDialog::textMaxWidth() const {
|
|
return (g_vm->getGameID() == GType_Ringworld2) ? 250 : 265;
|
|
}
|
|
|
|
int ConversationChoiceDialog::numberLeft() const {
|
|
return (g_vm->getGameID() == GType_Ringworld2) ? 8 : 13;
|
|
}
|
|
|
|
/*--------------------------------------------------------------------------*/
|
|
|
|
void Obj44::load(const byte *dataP) {
|
|
Common::MemoryReadStream s(dataP, (g_vm->getGameID() == GType_Ringworld2) ? 126 : 68);
|
|
|
|
if (g_vm->getGameID() == GType_Ringworld2) {
|
|
_mode = s.readSint16LE();
|
|
_lookupValue = s.readSint16LE();
|
|
_lookupIndex = s.readSint16LE();
|
|
_exitMode = s.readSint16LE();
|
|
_speakerMode = s.readSint16LE();
|
|
}
|
|
|
|
_id = s.readSint16LE();
|
|
for (int idx = 0; idx < OBJ44_LIST_SIZE; ++idx)
|
|
_callbackId[idx] = s.readSint16LE();
|
|
|
|
if (g_vm->getGameID() == GType_Ringworld2) {
|
|
for (int i = 0; i < 11; ++i)
|
|
_field16[i] = s.readSint16LE();
|
|
} else {
|
|
s.skip(4);
|
|
}
|
|
|
|
for (int idx = 0; idx < OBJ0A_LIST_SIZE; ++idx) {
|
|
_list[idx]._id = s.readSint16LE();
|
|
_list[idx]._scriptOffset = s.readSint16LE();
|
|
s.skip(6);
|
|
}
|
|
|
|
_speakerOffset = s.readSint16LE();
|
|
}
|
|
|
|
void Obj44::synchronize(Serializer &s) {
|
|
s.syncAsSint32LE(_id);
|
|
for (int idx = 0; idx < OBJ44_LIST_SIZE; ++idx)
|
|
s.syncAsSint32LE(_callbackId[idx]);
|
|
for (int idx = 0; idx < OBJ0A_LIST_SIZE; ++idx)
|
|
_list[idx].synchronize(s);
|
|
s.syncAsUint32LE(_speakerOffset);
|
|
|
|
if (g_vm->getGameID() == GType_Ringworld2) {
|
|
s.syncAsSint16LE(_mode);
|
|
s.syncAsSint16LE(_lookupValue);
|
|
s.syncAsSint16LE(_lookupIndex);
|
|
s.syncAsSint16LE(_exitMode);
|
|
s.syncAsSint16LE(_speakerMode);
|
|
|
|
for (int i = 0; i < 11; ++i)
|
|
s.syncAsSint16LE(_field16[i]);
|
|
}
|
|
}
|
|
|
|
/*--------------------------------------------------------------------------*/
|
|
|
|
StripManager::StripManager() {
|
|
_callbackObject = NULL;
|
|
_activeSpeaker = NULL;
|
|
_onBegin = NULL;
|
|
_onEnd = NULL;
|
|
_sceneNumber = 0;
|
|
_lookupList = NULL;
|
|
reset();
|
|
}
|
|
|
|
StripManager::~StripManager() {
|
|
}
|
|
|
|
void StripManager::start(int stripNum, EventHandler *owner, StripCallback *callback) {
|
|
if (_onBegin)
|
|
_onBegin();
|
|
reset();
|
|
|
|
_stripNum = stripNum;
|
|
_callbackObject = callback;
|
|
_sceneNumber = g_globals->_sceneManager._scene->_screenNumber;
|
|
_sceneBounds = g_globals->_sceneManager._scene->_sceneBounds;
|
|
_script.clear();
|
|
|
|
assert(owner);
|
|
owner->setAction(this, owner);
|
|
}
|
|
|
|
void StripManager::start3(int stripNum, EventHandler *owner, byte *lookupList) {
|
|
_lookupList = lookupList;
|
|
start(stripNum, owner, NULL);
|
|
}
|
|
|
|
void StripManager::reset() {
|
|
_actionIndex = 0;
|
|
_delayFrames = 0;
|
|
_owner = NULL;
|
|
_endHandler = NULL;
|
|
_uselessFl = false;
|
|
_stripNum = -1;
|
|
_obj44ListIndex = 0;
|
|
_currObj44Id = 0;
|
|
_useless = 0;
|
|
_activeSpeaker = NULL;
|
|
_textShown = false;
|
|
_callbackObject = NULL;
|
|
_exitMode = 0;
|
|
|
|
_obj44List.clear();
|
|
if (!_script.empty()) {
|
|
_script.clear();
|
|
}
|
|
}
|
|
|
|
void StripManager::load() {
|
|
// Get the script
|
|
byte *script = g_resourceManager->getResource(RES_STRIP, _stripNum, 2);
|
|
uint scriptSize = g_vm->_memoryManager.getSize(script);
|
|
|
|
_script.resize(scriptSize);
|
|
Common::copy(script, script + scriptSize, &_script[0]);
|
|
|
|
DEALLOCATE(script);
|
|
|
|
// Get the object list
|
|
byte *obj44List = g_resourceManager->getResource(RES_STRIP, _stripNum, 1);
|
|
int dataSize = g_vm->_memoryManager.getSize(obj44List);
|
|
|
|
int obj44Size = (g_vm->getGameID() == GType_Ringworld2) ? 126 : 68;
|
|
assert((dataSize % obj44Size) == 0);
|
|
|
|
byte *dataP = obj44List;
|
|
for (int idx = 0; idx < (dataSize / obj44Size); ++idx, dataP += obj44Size) {
|
|
Obj44 obj;
|
|
obj.load(dataP);
|
|
_obj44List.push_back(obj);
|
|
}
|
|
|
|
DEALLOCATE(obj44List);
|
|
}
|
|
|
|
void StripManager::synchronize(Serializer &s) {
|
|
if (s.getVersion() >= 2)
|
|
Action::synchronize(s);
|
|
|
|
s.syncAsSint32LE(_stripNum);
|
|
s.syncAsSint32LE(_obj44ListIndex);
|
|
s.syncAsSint32LE(_useless);
|
|
s.syncAsSint32LE(_sceneNumber);
|
|
_sceneBounds.synchronize(s);
|
|
SYNC_POINTER(_activeSpeaker);
|
|
s.syncAsByte(_textShown);
|
|
s.syncAsByte(_uselessFl);
|
|
s.syncAsSint32LE(_currObj44Id);
|
|
if (g_vm->getGameID() == GType_Ringworld2)
|
|
s.syncAsSint16LE(_exitMode);
|
|
|
|
// Synchronize the item list
|
|
int arrSize = _obj44List.size();
|
|
s.syncAsUint16LE(arrSize);
|
|
if (s.isLoading())
|
|
_obj44List.resize(arrSize);
|
|
for (int i = 0; i < arrSize; ++i)
|
|
_obj44List[i].synchronize(s);
|
|
|
|
// Synchronize script data
|
|
int scriptSize = _script.size();
|
|
s.syncAsUint16LE(scriptSize);
|
|
if (s.isLoading())
|
|
_script.resize(scriptSize);
|
|
if (scriptSize > 0)
|
|
s.syncBytes(&_script[0], scriptSize);
|
|
|
|
// Add speaker list
|
|
arrSize = _speakerList.size();
|
|
s.syncAsUint16LE(arrSize);
|
|
if (s.isLoading())
|
|
_speakerList.resize(arrSize);
|
|
for (int i = 0; i < arrSize; ++i)
|
|
SYNC_POINTER(_speakerList[i]);
|
|
|
|
SYNC_POINTER(_callbackObject);
|
|
}
|
|
|
|
void StripManager::remove() {
|
|
if (g_vm->getGameID() == GType_Ringworld2) {
|
|
for (uint i = 0; i < _speakerList.size(); ++i) {
|
|
if (_activeSpeaker != _speakerList[i])
|
|
_speakerList[i]->stopSpeaking();
|
|
}
|
|
}
|
|
|
|
if (_textShown) {
|
|
if (_activeSpeaker)
|
|
_activeSpeaker->removeText();
|
|
_textShown = false;
|
|
}
|
|
|
|
if (_activeSpeaker) {
|
|
if (g_vm->getGameID() == GType_Ringworld2)
|
|
static_cast<Ringworld2::VisualSpeaker *>(_activeSpeaker)->_speakerMode = 0xff;
|
|
_activeSpeaker->remove();
|
|
}
|
|
|
|
if (_sceneNumber != g_globals->_sceneManager._scene->_screenNumber) {
|
|
g_globals->_sceneManager._scene->_sceneBounds = _sceneBounds;
|
|
g_globals->_sceneManager._scene->loadScene(_sceneNumber);
|
|
}
|
|
|
|
if (_onEnd)
|
|
_onEnd();
|
|
|
|
if (g_vm->getGameID() == GType_Ringworld2)
|
|
_endHandler = NULL;
|
|
|
|
Action::remove();
|
|
}
|
|
|
|
void StripManager::dispatch() {
|
|
if (g_vm->getGameID() == GType_Ringworld2) {
|
|
if (_activeSpeaker)
|
|
_activeSpeaker->dispatch();
|
|
}
|
|
|
|
Action::dispatch();
|
|
}
|
|
|
|
void StripManager::signal() {
|
|
int strIndex = 0;
|
|
|
|
if (_textShown) {
|
|
_activeSpeaker->removeText();
|
|
_textShown = false;
|
|
}
|
|
|
|
if (_obj44ListIndex < 0) {
|
|
EventHandler *owner = _endHandler;
|
|
int stripNum = ABS(_obj44ListIndex);
|
|
remove();
|
|
|
|
start(stripNum, owner);
|
|
return;
|
|
} else if (_obj44ListIndex == 10000) {
|
|
// Reached end of strip
|
|
EventHandler *endHandler = _endHandler;
|
|
remove();
|
|
|
|
if ((g_vm->getGameID() == GType_Ringworld2) && endHandler)
|
|
endHandler->signal();
|
|
|
|
return;
|
|
}
|
|
|
|
// Run strip
|
|
|
|
if (_obj44List.size() == 0)
|
|
// Load the data for the strip
|
|
load();
|
|
|
|
Obj44 &obj44 = _obj44List[_obj44ListIndex];
|
|
|
|
if (g_vm->getGameID() == GType_Ringworld2) {
|
|
// Return to Ringworld specific handling
|
|
if (obj44._exitMode)
|
|
_exitMode = obj44._exitMode;
|
|
|
|
switch (obj44._mode) {
|
|
case 1:
|
|
++_lookupList[obj44._lookupIndex - 1];
|
|
break;
|
|
case 2:
|
|
--_lookupList[obj44._lookupIndex - 1];
|
|
break;
|
|
case 3:
|
|
_lookupList[obj44._lookupIndex - 1] = obj44._lookupValue;
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
|
|
_currObj44Id = obj44._id;
|
|
Common::StringArray choiceList;
|
|
|
|
// Build up a list of script entries
|
|
int idx;
|
|
bool delayFlag = false;
|
|
|
|
if ((g_vm->getGameID() == GType_Ringworld2) && obj44._field16[0]) {
|
|
// Special loading mode used in Return to Ringworld
|
|
for (idx = 0; idx < OBJ44_LIST_SIZE; ++idx) {
|
|
int f16Index = _lookupList[obj44._field16[0] - 1];
|
|
int entryId = obj44._field16[f16Index];
|
|
|
|
Obj0A &entry = obj44._list[idx];
|
|
if (entry._id == entryId) {
|
|
// Get the next one
|
|
choiceList.push_back((const char *)&_script[0] + entry._scriptOffset);
|
|
strIndex = idx;
|
|
delayFlag = true;
|
|
break;
|
|
}
|
|
}
|
|
|
|
// If no entry found, get the default response
|
|
if (!delayFlag) {
|
|
idx = 0;
|
|
while (obj44._list[idx + 1]._id)
|
|
++idx;
|
|
|
|
choiceList.push_back((const char *)&_script[0] + obj44._list[idx]._scriptOffset);
|
|
strIndex = idx;
|
|
delayFlag = true;
|
|
}
|
|
} else {
|
|
// Standard choices loading
|
|
for (idx = 0; idx < OBJ0A_LIST_SIZE; ++idx) {
|
|
if (!obj44._list[idx]._id)
|
|
break;
|
|
|
|
// Get the next one
|
|
const char *choiceStr = (const char *)&_script[0] + obj44._list[idx]._scriptOffset;
|
|
|
|
if (!*choiceStr) {
|
|
// Choice is empty
|
|
assert(g_vm->getGameID() == GType_Ringworld2);
|
|
|
|
if (obj44._list[1]._id) {
|
|
// it's a reference to another list slot
|
|
int listId = obj44._list[idx]._id;
|
|
|
|
int obj44Idx = 0;
|
|
while (_obj44List[obj44Idx]._id != listId)
|
|
++obj44Idx;
|
|
|
|
if (_obj44List[obj44Idx]._field16[0]) {
|
|
// WORKAROUND: The _lookupList isn't always correctly initialized. But it always
|
|
// seems to be set to the R2_GLOBALS._stripManager_lookupList, so manually set it
|
|
if (!_lookupList)
|
|
_lookupList = R2_GLOBALS._stripManager_lookupList;
|
|
|
|
int f16Index = _lookupList[_obj44List[obj44Idx]._field16[0] - 1];
|
|
listId = _obj44List[obj44Idx]._field16[f16Index];
|
|
|
|
if (_lookupList[_obj44List[obj44Idx]._field16[0] - 1]) {
|
|
int listIdx = 0;
|
|
while (_obj44List[obj44Idx]._list[listIdx]._id != listId)
|
|
++listIdx;
|
|
|
|
choiceStr = (const char *)&_script[0] + _obj44List[obj44Idx]._list[listIdx]._scriptOffset;
|
|
} else {
|
|
for (int listIdx = idx; listIdx < (OBJ0A_LIST_SIZE - 1); ++listIdx) {
|
|
obj44._list[listIdx]._id = obj44._list[listIdx + 1]._id;
|
|
obj44._list[listIdx]._scriptOffset = obj44._list[listIdx + 1]._scriptOffset;
|
|
|
|
if (!obj44._list[listIdx + 1]._id)
|
|
obj44._list[listIdx]._id = 0;
|
|
}
|
|
|
|
--idx;
|
|
continue;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// Add entry to the list
|
|
choiceList.push_back(choiceStr);
|
|
}
|
|
}
|
|
|
|
if (choiceList.size() > 1)
|
|
// Get the user to select a conversation option
|
|
strIndex = _choiceDialog.execute(choiceList);
|
|
|
|
if ((delayFlag || choiceList.size() != 1) && !_uselessFl)
|
|
_delayFrames = 1;
|
|
else {
|
|
Speaker *speakerP = getSpeaker((const char *)&_script[0] + obj44._speakerOffset);
|
|
if (!speakerP)
|
|
error("Speaker not found. Screenplay: %s %d", (const char *)&_script[0] + obj44._speakerOffset, _stripNum);
|
|
|
|
if (speakerP != _activeSpeaker) {
|
|
if (_activeSpeaker)
|
|
_activeSpeaker->remove();
|
|
_activeSpeaker = speakerP;
|
|
|
|
if ((_activeSpeaker->_newSceneNumber == -1) && (g_globals->_sceneManager._scene->_screenNumber != _sceneNumber)) {
|
|
g_globals->_sceneManager._scene->_sceneBounds = _sceneBounds;
|
|
g_globals->_sceneManager._scene->loadScene(_sceneNumber);
|
|
}
|
|
|
|
_activeSpeaker->startSpeaking(this);
|
|
}
|
|
|
|
if (_callbackObject) {
|
|
for (idx = 0; idx < OBJ44_LIST_SIZE; ++idx) {
|
|
if (!obj44._callbackId[idx])
|
|
break;
|
|
|
|
_callbackObject->stripCallback(obj44._callbackId[idx]);
|
|
}
|
|
}
|
|
|
|
if (g_vm->getGameID() == GType_Ringworld2) {
|
|
Ringworld2::VisualSpeaker *speaker = static_cast<Ringworld2::VisualSpeaker *>(_activeSpeaker);
|
|
|
|
if (speaker) {
|
|
speaker->_speakerMode = obj44._speakerMode;
|
|
if (!choiceList[strIndex].empty())
|
|
speaker->animateSpeaker();
|
|
}
|
|
|
|
if (!choiceList[strIndex].empty()) {
|
|
_textShown = true;
|
|
_activeSpeaker->setText(choiceList[strIndex]);
|
|
} else if (!obj44._speakerMode) {
|
|
_delayFrames = 1;
|
|
} else {
|
|
_delayFrames = 0;
|
|
speaker->animateSpeaker();
|
|
}
|
|
} else {
|
|
_textShown = true;
|
|
_activeSpeaker->setText(choiceList[strIndex]);
|
|
}
|
|
}
|
|
|
|
_obj44ListIndex = getNewIndex(obj44._list[strIndex]._id);
|
|
if (_obj44ListIndex == 10001) {
|
|
MessageDialog::show("Strip Failure: Node not found", OK_BTN_STRING);
|
|
_obj44ListIndex = 0;
|
|
}
|
|
}
|
|
|
|
void StripManager::process(Event &event) {
|
|
Action::process(event);
|
|
if (event.handled)
|
|
return;
|
|
|
|
if ((event.eventType == EVENT_KEYPRESS) && (event.kbd.keycode == Common::KEYCODE_ESCAPE)) {
|
|
if (_obj44ListIndex != 10000) {
|
|
int currIndex = _obj44ListIndex;
|
|
while (!_obj44List[_obj44ListIndex]._list[1]._id) {
|
|
_obj44ListIndex = getNewIndex(_obj44List[_obj44ListIndex]._list[0]._id);
|
|
if ((_obj44ListIndex < 0) || (_obj44ListIndex == 10000))
|
|
break;
|
|
currIndex = _obj44ListIndex;
|
|
}
|
|
|
|
_currObj44Id = _obj44List[currIndex]._id;
|
|
}
|
|
|
|
// Signal the end of the strip
|
|
_delayFrames = 0;
|
|
event.handled = true;
|
|
signal();
|
|
} else if (event.eventType & (EVENT_BUTTON_DOWN | EVENT_KEYPRESS)) {
|
|
// Move to next sequence in the strip
|
|
_delayFrames = 0;
|
|
event.handled = true;
|
|
signal();
|
|
}
|
|
}
|
|
|
|
void StripManager::addSpeaker(Speaker *speaker) {
|
|
assert(_speakerList.size() < 100);
|
|
_speakerList.push_back(speaker);
|
|
}
|
|
|
|
Speaker *StripManager::getSpeaker(const char *speakerName) {
|
|
for (uint idx = 0; idx < _speakerList.size(); ++idx) {
|
|
if (!strcmp(_speakerList[idx]->_speakerName.c_str(), speakerName))
|
|
return _speakerList[idx];
|
|
}
|
|
|
|
// TODO: Check if it necessary to make a strcmp first.
|
|
//
|
|
// If nothing is found, recheck and ignore the case as
|
|
// in R2R, some character names aren't in uppercase.
|
|
if (g_vm->getGameID() == GType_Ringworld2) {
|
|
for (uint idx = 0; idx < _speakerList.size(); ++idx) {
|
|
if (!scumm_stricmp(_speakerList[idx]->_speakerName.c_str(), speakerName))
|
|
return _speakerList[idx];
|
|
}
|
|
}
|
|
|
|
return NULL;
|
|
}
|
|
|
|
int StripManager::getNewIndex(int id) {
|
|
if (id == 10000)
|
|
return id;
|
|
if ((g_vm->getGameID() == GType_Ringworld2) && (id < 0))
|
|
return id;
|
|
|
|
for (uint idx = 0; idx < _obj44List.size(); ++idx) {
|
|
if (_obj44List[idx]._id == id) {
|
|
return (id == 0) ? 10001 : idx;
|
|
}
|
|
}
|
|
|
|
return 10001;
|
|
}
|
|
|
|
/*--------------------------------------------------------------------------*/
|
|
|
|
Speaker::Speaker() : EventHandler() {
|
|
_newSceneNumber = -1;
|
|
_hideObjects = true;
|
|
_field18 = 0;
|
|
_textWidth = 140;
|
|
_textPos = Common::Point(10, 20);
|
|
_fontNumber = 2;
|
|
_textMode = ALIGN_LEFT;
|
|
_color1 = _color2 = _color3 = g_globals->_scenePalette._colors.foreground;
|
|
_action = NULL;
|
|
_speakerName = "SPEAKER";
|
|
_oldSceneNumber = -1;
|
|
}
|
|
|
|
void Speaker::synchronize(Serializer &s) {
|
|
if (s.getVersion() >= 2)
|
|
EventHandler::synchronize(s);
|
|
|
|
_fieldA.synchronize(s);
|
|
SYNC_POINTER(_field18);
|
|
s.syncString(_speakerName);
|
|
s.syncAsSint32LE(_newSceneNumber);
|
|
s.syncAsSint32LE(_oldSceneNumber);
|
|
_sceneBounds.synchronize(s);
|
|
s.syncAsSint32LE(_textWidth);
|
|
s.syncAsSint16LE(_textPos.x); s.syncAsSint16LE(_textPos.y);
|
|
s.syncAsSint32LE(_fontNumber);
|
|
SYNC_ENUM(_textMode, TextAlign);
|
|
s.syncAsSint16LE(_color1);
|
|
s.syncAsSint16LE(_color2);
|
|
s.syncAsSint16LE(_color3);
|
|
s.syncAsByte(_hideObjects);
|
|
}
|
|
|
|
void Speaker::remove() {
|
|
if (_hideObjects)
|
|
SceneObjectList::deactivate();
|
|
}
|
|
|
|
void Speaker::startSpeaking(Action *action) {
|
|
_action = action;
|
|
if (_newSceneNumber != -1) {
|
|
_oldSceneNumber = g_globals->_sceneManager._sceneNumber;
|
|
_sceneBounds = g_globals->_sceneManager._scene->_sceneBounds;
|
|
g_globals->_sceneManager._scene->loadScene(_newSceneNumber);
|
|
}
|
|
|
|
if (_hideObjects)
|
|
// Activate the object list for display
|
|
_objectList.activate();
|
|
|
|
// Draw the speaker objects without any fading
|
|
FadeMode fadeMode = g_globals->_sceneManager._fadeMode;
|
|
g_globals->_sceneManager._fadeMode = FADEMODE_IMMEDIATE;
|
|
g_globals->_sceneObjects->draw();
|
|
g_globals->_sceneManager._fadeMode = fadeMode;
|
|
}
|
|
|
|
void Speaker::setText(const Common::String &msg) {
|
|
g_globals->_sceneObjects->draw();
|
|
|
|
_sceneText._color1 = _color1;
|
|
_sceneText._color2 = _color2;
|
|
_sceneText._color3 = _color3;
|
|
_sceneText._width = _textWidth;
|
|
_sceneText._fontNumber = _fontNumber;
|
|
_sceneText._textMode = _textMode;
|
|
_sceneText.setup(msg);
|
|
_sceneText.setPosition(_textPos);
|
|
_sceneText.fixPriority(256);
|
|
|
|
// Count the number of words (by spaces) in the string
|
|
const char *msgP = msg.c_str();
|
|
int spaceCount = 0;
|
|
while (*msgP) {
|
|
if (*msgP++ == ' ')
|
|
++spaceCount;
|
|
}
|
|
|
|
int numFrames = spaceCount * STRIP_WORD_DELAY + 120;
|
|
if (_action)
|
|
_action->setDelay(numFrames);
|
|
}
|
|
|
|
void Speaker::removeText() {
|
|
_sceneText.remove();
|
|
}
|
|
|
|
/*--------------------------------------------------------------------------*/
|
|
|
|
SpeakerGameText::SpeakerGameText() : Speaker() {
|
|
_speakerName = "GAMETEXT";
|
|
_textPos = Common::Point(40, 40);
|
|
_textMode = ALIGN_CENTER;
|
|
_color1 = 7;
|
|
_textWidth = 230;
|
|
_hideObjects = false;
|
|
}
|
|
|
|
/*--------------------------------------------------------------------------*/
|
|
|
|
ScreenSpeaker::ScreenSpeaker() : Speaker() {
|
|
_npc = NULL;
|
|
_textMode = ALIGN_CENTER;
|
|
}
|
|
|
|
void ScreenSpeaker::setText(const Common::String &msg) {
|
|
GfxManager gfxMan;
|
|
gfxMan.activate();
|
|
gfxMan._font.setFontNumber(_fontNumber);
|
|
Rect textRect;
|
|
|
|
g_globals->gfxManager().getStringBounds(msg.c_str(), textRect, _textWidth);
|
|
if (_npc) {
|
|
textRect.center(_npc->_position.x, _npc->_bounds.top - (textRect.height() / 2 + 10));
|
|
} else {
|
|
textRect.center(g_globals->_sceneManager._scene->_sceneBounds.left +
|
|
(g_globals->_sceneManager._scene->_sceneBounds.width() / 2),
|
|
g_globals->_sceneManager._scene->_sceneBounds.top);
|
|
}
|
|
|
|
Rect rect2 = g_globals->_sceneManager._scene->_sceneBounds;
|
|
rect2.collapse(10, 6);
|
|
textRect.contain(rect2);
|
|
|
|
_textPos.x = textRect.left;
|
|
_textPos.y = textRect.top;
|
|
Speaker::setText(msg);
|
|
|
|
gfxMan.deactivate();
|
|
}
|
|
|
|
/*--------------------------------------------------------------------------*/
|
|
|
|
void SpeakerAction::signal() {
|
|
switch (_actionIndex++) {
|
|
case 0:
|
|
setDelay(g_globals->_randomSource.getRandomNumber(60) + 60);
|
|
break;
|
|
case 1:
|
|
static_cast<SceneObject *>(_owner)->setFrame(1);
|
|
static_cast<SceneObject *>(_owner)->animate(ANIM_MODE_5, this, NULL);
|
|
break;
|
|
case 2:
|
|
setDelay(g_globals->_randomSource.getRandomNumber(10));
|
|
_actionIndex = 0;
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
|
|
/*--------------------------------------------------------------------------*/
|
|
|
|
void AnimatedSpeaker::removeText() {
|
|
Speaker::removeText();
|
|
_object1.remove();
|
|
_object2.remove();
|
|
|
|
_objectList.draw();
|
|
}
|
|
|
|
} // end of namespace TsAGE
|