/* 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(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(); // Force the display of an arrow cursor during discussions in R2R if (g_vm->getGameID() == GType_Ringworld2) R2_GLOBALS._events.setCursor(CURSOR_ARROW); // 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._screen.update(); } 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(_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(_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(_owner)->setFrame(1); static_cast(_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