diff options
Diffstat (limited to 'engines/mohawk/cstime_game.cpp')
-rw-r--r-- | engines/mohawk/cstime_game.cpp | 1236 |
1 files changed, 1236 insertions, 0 deletions
diff --git a/engines/mohawk/cstime_game.cpp b/engines/mohawk/cstime_game.cpp new file mode 100644 index 0000000000..5b197599e8 --- /dev/null +++ b/engines/mohawk/cstime_game.cpp @@ -0,0 +1,1236 @@ +/* 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. + * + * $URL$ + * $Id$ + * + */ + +#include "mohawk/cstime_game.h" +#include "mohawk/cstime_ui.h" +#include "mohawk/cstime_view.h" +#include "mohawk/resource.h" +#include "mohawk/sound.h" +#include "common/events.h" + +namespace Mohawk { + +// read a null-terminated string from a stream +static Common::String readString(Common::SeekableReadStream *stream) { + Common::String ret; + while (!stream->eos()) { + byte in = stream->readByte(); + if (!in) + break; + ret += in; + } + return ret; +} + +// read a rect from a stream +Common::Rect readRect(Common::SeekableReadStream *stream) { + Common::Rect rect; + + rect.left = stream->readSint16BE(); + rect.top = stream->readSint16BE(); + rect.right = stream->readSint16BE(); + rect.bottom = stream->readSint16BE(); + + return rect; +} + +void Region::loadFrom(Common::SeekableReadStream *stream) { + uint16 rectCount = stream->readUint16BE(); + if (!rectCount) { + // TODO: why this? + stream->skip(2); + rectCount = stream->readUint16BE(); + } + for (uint i = 0; i < rectCount; i++) { + Common::Rect rect = readRect(stream); + if (!rect.isValidRect()) + continue; + _rects.push_back(rect); + } +} + +bool Region::containsPoint(Common::Point &pos) const { + for (uint i = 0; i < _rects.size(); i++) + if (_rects[i].contains(pos)) + return true; + + return false; +} + +CSTimeChar::CSTimeChar(MohawkEngine_CSTime *vm, CSTimeScene *scene, uint id) : _vm(vm), _scene(scene), _id(id) { + _resting = true; + _flappingState = 0xffff; + _surfingState = 0; + + _NIS = NULL; + _restFeature = NULL; + _talkFeature = NULL; + + _talkFeature1 = NULL; + _talkFeature2 = NULL; + _talkFeature3 = NULL; + _lastTime1 = 0; + _lastTime2 = 0; + _lastTime3 = 0; + + _playingWaveId = 0; +} + +CSTimeChar::~CSTimeChar() { +} + +void CSTimeChar::idle() { + if (!_unknown2) + return; + + if (_flappingState == 1) { + idleTalk(); + return; + } + + if (!_NIS) + idleAmbients(); +} + +void CSTimeChar::setupAmbientAnims(bool onetime) { + CSTimeConversation *conv = _vm->getCase()->getCurrConversation(); + if (_unknown1 == 0xffff || !_unknown2 || !_ambients.size() || !_resting || !_enabled || + (conv->getState() != (uint)~0 && conv->getSourceChar() == _id)) { + setupRestPos(); + _resting = true; + return; + } + + removeChr(); + for (uint i = 0; i < _ambients.size(); i++) { + // FIXME: check ambient condition + uint32 flags = kFeatureSortStatic; + if (_ambients[i].delay != 0xffff) { + flags |= kFeatureNewNoLoop; + if (onetime) + flags |= kFeatureNewDisableOnReset; + } + installAmbientAnim(i, flags); + } +} + +void CSTimeChar::idleAmbients() { + if (_flappingState != 0xffff) + return; + + for (uint i = 0; i < _ambients.size(); i++) { + if (!_ambients[i].feature) + continue; + uint16 delay = _ambients[i].delay; + if (delay == 0xffff) + continue; + uint32 now = _vm->_system->getMillis(); + if (now < _ambients[i].nextTime) + continue; + _ambients[i].feature->resetFeatureScript(1, 0); + _ambients[i].nextTime = now + delay; + } +} + +void CSTimeChar::stopAmbients(bool restpos) { + for (uint i = 0; i < _ambients.size(); i++) { + if (!_ambients[i].feature) + continue; + _vm->getView()->removeFeature(_ambients[i].feature, true); + _ambients[i].feature = NULL; + } + + if (restpos) + setupRestPos(); +} + +void CSTimeChar::setupRestPos() { + if (_unknown1 == 0xffff || !_unknown1 || !_unknown2) + return; + + if (!_restFeature) { + uint id = 0; // FIXME + uint32 flags = kFeatureSortStatic | kFeatureNewNoLoop | kFeatureNewDisableOnReset; + Feature *feature = _vm->getView()->installViewFeature(getChrBaseId() + id, flags, NULL); + // FIXME: fix priorities + _restFeature = feature; + } else { + _restFeature->resetFeatureScript(1, 0); + } + + // FIXME: fix more priorities +} + +void CSTimeChar::removeChr() { + if (_unknown1 == 0xffff || !_unknown1) + return; + + if (_talkFeature) { + _vm->getView()->removeFeature(_talkFeature, true); + _vm->getView()->removeFeature(_talkFeature3, true); + if (_talkFeature1) + _vm->getView()->removeFeature(_talkFeature1, true); + if (_unknown1 > 1) + _vm->getView()->removeFeature(_talkFeature2, true); + } + + if (_restFeature) + _vm->getView()->removeFeature(_restFeature, true); + + _talkFeature1 = NULL; + _talkFeature2 = NULL; + _talkFeature3 = NULL; + + _talkFeature = NULL; + _restFeature = NULL; +} + +uint16 CSTimeChar::getChrBaseId() { + return _scene->getSceneId() + (_id + 1) * 200; +} + +uint CSTimeChar::getScriptCount() { + static uint bases[4] = { 0, 10, 13, 21 }; + assert(_unknown1 < 4); + return bases[_unknown1] + _ambients.size() + _unknown3; +} + +void CSTimeChar::playNIS(uint16 id) { + if (_NIS) + removeNIS(); + stopAmbients(false); + removeChr(); + uint32 flags = kFeatureSortStatic | kFeatureNewNoLoop; + _NIS = _vm->getView()->installViewFeature(getChrTypeScriptBase() + id + _ambients.size(), flags, NULL); + // FIXME: fix priorities +} + +bool CSTimeChar::NISIsDone() { + return (_NIS->_data.paused || !_NIS->_data.enabled); +} + +void CSTimeChar::removeNIS() { + if (!_NIS) + return; + _vm->getView()->removeFeature(_NIS, true); + _NIS = NULL; +} + +void CSTimeChar::startFlapping(uint16 id) { + if (!_unknown2) + return; + + _scene->_activeChar = this; + if (_restFeature) { + _vm->getView()->removeFeature(_restFeature, true); + _restFeature = NULL; + } + stopAmbients(false); + setupTalk(); + _flappingState = 1; + playFlapWave(id); +} + +void CSTimeChar::interruptFlapping() { + if (_playingWaveId) + _vm->_sound->stopSound(_playingWaveId); + // TODO: kill any other (preload) sound + _waveStatus = 'q'; +} + +void CSTimeChar::installAmbientAnim(uint id, uint32 flags) { + Feature *feature = _vm->getView()->installViewFeature(getChrTypeScriptBase() + id, flags, NULL); + // FIXME: fix priorities + + _ambients[id].feature = feature; + _ambients[id].nextTime = _vm->_system->getMillis() + _ambients[id].delay; +} + +uint16 CSTimeChar::getChrTypeScriptBase() { + static uint bases[4] = { 0, 10, 13, 21 }; + assert(_unknown1 < 4); + return bases[_unknown1] + getChrBaseId(); +} + +void CSTimeChar::playFlapWave(uint16 id) { + _playingWaveId = id; + _vm->_sound->playSound(id, Audio::Mixer::kMaxChannelVolume, false, &_cueList); + _nextCue = 0; + _waveStatus = 'b'; +} + +void CSTimeChar::updateWaveStatus() { + // This is a callback in the original, handling audio events. + assert(_playingWaveId); + + // FIXME: state 's' for .. something? stopped? + if (!_vm->_sound->isPlaying(_playingWaveId)) { + _waveStatus = 'q'; + return; + } + + uint samples = _vm->_sound->getNumSamplesPlayed(_playingWaveId); + for (uint i = _nextCue; i < _cueList.pointCount; i++) { + if (_cueList.points[i].sampleFrame > samples) + return; + if (_cueList.points[i].name.empty()) + warning("cue %d reached but was empty", i); + else + _waveStatus = _cueList.points[i].name[0]; + _nextCue = i + 1; + } +} + +void CSTimeChar::setupTalk() { + if (_unknown1 == 0xffff || !_unknown1 || !_unknown2 || _talkFeature) + return; + + uint32 flags = kFeatureSortStatic | kFeatureNewNoLoop | kFeatureNewDisableOnReset; + _talkFeature = _vm->getView()->installViewFeature(getChrBaseId() + (_enabled ? 1 : 14), flags, NULL); + + _talkFeature3 = _vm->getView()->installViewFeature(getChrBaseId() + (_enabled ? 4 : 15), flags, NULL); + if (_enabled) { + _talkFeature1 = _vm->getView()->installViewFeature(getChrBaseId() + 2, flags, NULL); + if (_unknown1 > 1) { + _talkFeature2 = _vm->getView()->installViewFeature(getChrBaseId() + 10, flags, NULL); + } + } + // FIXME: fix priorities +} + +void CSTimeChar::idleTalk() { + updateWaveStatus(); + + if (_waveStatus == 'q') { + if (_surfingState) { + // FIXME + _surfingState = 0; + } else { + // FIXME + _playingWaveId = 0; + } + stopFlapping(); + return; + } + + if (_waveStatus == 's' && _surfingState) { + // FIXME + _waveStatus = 'q'; + return; + } + + CSTimeView *view = _vm->getView(); + + if (_enabled && view->getTime() > _lastTime1) { + _lastTime1 = view->getTime() + 2000 + _vm->_rnd->getRandomNumberRng(0, 1000); + if (_talkFeature1) + _talkFeature1->resetFeatureScript(1, getChrBaseId() + 2 + _vm->_rnd->getRandomNumberRng(0, 1)); + } + + if (_enabled && view->getTime() > _lastTime2) { + _lastTime2 = view->getTime() + 3000 + _vm->_rnd->getRandomNumberRng(0, 1000); + if (_talkFeature2) + _talkFeature2->resetFeatureScript(1, getChrBaseId() + 10 + _vm->_rnd->getRandomNumberRng(0, 2)); + } + + if (_waveStatus == 'c') { + if (_talkFeature3) + _talkFeature3->resetFeatureScript(1, getChrBaseId() + (_enabled ? 4 : 15)); + } else if (view->getTime() > _lastTime3) { + _lastTime3 = view->getTime() + 100; + if (_talkFeature3) + _talkFeature3->resetFeatureScript(1, getChrBaseId() + (_enabled ? 4 : 15) + _vm->_rnd->getRandomNumberRng(1, 5)); + } + + // FIXME: more animations +} + +void CSTimeChar::stopFlapping() { + _flappingState = 0; + removeChr(); + // FIXME: stupid hardcoded hack for case 5 + setupAmbientAnims(true); +} + +CSTimeConversation::CSTimeConversation(MohawkEngine_CSTime *vm, uint id) : _vm(vm), _id(id) { + clear(); + + Common::SeekableReadStream *convStream = _vm->getResource(ID_CONV, id * 10 + 500); + + _nameId = convStream->readUint16BE(); + _greeting = convStream->readUint16BE(); + _greeting2 = convStream->readUint16BE(); + + uint16 qarIds[8]; + for (uint i = 0; i < 8; i++) + qarIds[i] = convStream->readUint16BE(); + + delete convStream; + + for (uint i = 0; i < 8; i++) { + // FIXME: are they always in order? + if (qarIds[i] == 0xffff) + continue; + _qars.push_back(CSTimeQaR()); + CSTimeQaR &qar = _qars.back(); + loadQaR(qar, qarIds[i]); + } +} + +void CSTimeConversation::start() { + uint16 greeting = _greeting; + + if (_talkCount > 1) + greeting = _greeting2; + + _state = 2; + + if (greeting == 0xffff) { + finishProcessingQaR(); + return; + } + + CSTimeEvent event; + event.type = kCSTimeEventCharStartFlapping; + event.param1 = _sourceChar; + event.param2 = greeting; + _vm->addEvent(event); +} + +void CSTimeConversation::finishProcessingQaR() { + if (_state == 2) { + _vm->getInterface()->getInventoryDisplay()->hide(); + _vm->getInterface()->clearTextLine(); + selectItemsToDisplay(); + display(); + return; + } + + if (_nextToProcess == 0xffff) + return; + + uint qarIndex = _itemsToDisplay[_nextToProcess]; + CSTimeQaR &qar = _qars[qarIndex]; + + if (!qar.nextQaRsId) { + end(true); + _nextToProcess = 0xffff; + return; + } + + if (qar.responseStringId != 0xffff) { + _vm->addEventList(qar.events); + } + + if (qar.nextQaRsId == 0xffff) { + _qars.remove_at(qarIndex); + _vm->getInterface()->clearDialogLine(_nextToProcess); + _nextToProcess = 0xffff; + return; + } + + loadQaR(qar, qar.nextQaRsId); + if (qar.unknown2) + qar.finished = true; + _vm->getInterface()->displayDialogLine(qar.questionStringId, _nextToProcess, qar.finished ? 13 : 32); + + _nextToProcess = 0xffff; +} + +void CSTimeConversation::end(bool useLastClicked, bool runEvents) { + if (runEvents) { + uint entry = _currEntry; + if (!useLastClicked) + entry = _itemsToDisplay.size() - 1; + CSTimeQaR &qar = _qars[_itemsToDisplay[entry]]; + _vm->addEventList(qar.events); + if (_sourceChar != 0xffff) + _vm->getCase()->getCurrScene()->getChar(_sourceChar)->setupAmbientAnims(true); + } + + CSTimeInterface *interface = _vm->getInterface(); + CSTimeInventoryDisplay *invDisplay = interface->getInventoryDisplay(); + if (invDisplay->getState() == 4) { + invDisplay->hide(); + invDisplay->setState(0); + } + + setState(~0); + _currHover = ~0; + + interface->clearTextLine(); + interface->clearDialogArea(); + invDisplay->show(); + + // TODO: stupid case 20 stuff +} + +void CSTimeConversation::display() { + _vm->getInterface()->clearDialogArea(); + + for (uint i = 0; i < _itemsToDisplay.size(); i++) { + // FIXME: some rect stuff? + + CSTimeQaR &qar = _qars[_itemsToDisplay[i]]; + _vm->getInterface()->displayDialogLine(qar.questionStringId, i, qar.finished ? 13 : 32); + } + + _state = 1; +} + +void CSTimeConversation::selectItemsToDisplay() { + _itemsToDisplay.clear(); + + for (uint i = 0; i < _qars.size(); i++) { + if (_qars[i].unknown1 != 0xffff && !_vm->getCase()->checkConvCondition(_qars[i].unknown1)) + continue; + if (_itemsToDisplay.size() == 5) + error("Too many conversation paths"); + _itemsToDisplay.push_back(i); + } +} + +void CSTimeConversation::mouseDown(Common::Point &pos) { + if (_vm->getInterface()->getInventoryDisplay()->getState() == 4) + return; + + // TODO: case 20 rect check + + for (uint i = 0; i < _itemsToDisplay.size(); i++) { + Common::Rect thisRect = _vm->getInterface()->_dialogTextRect; + thisRect.top += 1 + i*15; + thisRect.bottom = thisRect.top + 15; + if (!thisRect.contains(pos)) + continue; + + _currEntry = i; + highlightLine(i); + _vm->getInterface()->cursorSetShape(5, true); + break; + } +} + +void CSTimeConversation::mouseMove(Common::Point &pos) { + // TODO: case 20 rect check + + bool mouseIsDown = _vm->getEventManager()->getButtonState() & 1; + + for (uint i = 0; i < _itemsToDisplay.size(); i++) { + Common::Rect thisRect = _vm->getInterface()->_dialogTextRect; + thisRect.top += 1 + i*15; + thisRect.bottom = thisRect.top + 15; + if (!thisRect.contains(pos)) + continue; + + if (mouseIsDown) { + if (i != _currEntry) + break; + highlightLine(i); + } + + _vm->getInterface()->cursorOverHotspot(); + _currHover = i; + return; + } + + if (_currHover != (uint)~0) { + if (_vm->getInterface()->cursorGetState() != 3) { + _vm->getInterface()->cursorSetShape(1, true); + if (_vm->getInterface()->getInventoryDisplay()->getState() != 4) + unhighlightLine(_currHover); + } + _currHover = ~0; + } +} + +void CSTimeConversation::mouseUp(Common::Point &pos) { + if (_vm->getInterface()->getInventoryDisplay()->getState() == 4) + return; + + if (_currEntry == (uint)~0) + return; + + // TODO: case 20 rect check + + CSTimeQaR &qar = _qars[_itemsToDisplay[_currEntry]]; + Common::Rect thisRect = _vm->getInterface()->_dialogTextRect; + thisRect.top += 1 + _currEntry*15; + thisRect.bottom = thisRect.top + 15; + if (!thisRect.contains(pos)) + return; + + if (qar.responseStringId != 0xffff) { + CSTimeEvent newEvent; + newEvent.type = kCSTimeEventCharStartFlapping; + newEvent.param1 = _sourceChar; + newEvent.param2 = qar.responseStringId; + _vm->addEvent(newEvent); + _nextToProcess = _currEntry; + return; + } + + if (!qar.nextQaRsId) { + _vm->getInterface()->cursorChangeShape(1); + end(true); + return; + } + + _vm->addEventList(qar.events); + _nextToProcess = _currEntry; +} + +void CSTimeConversation::setAsked(uint qar, uint entry) { + assert(qar < 8 && entry < 5); + _asked[qar][entry] = true; +} + +void CSTimeConversation::clear() { + _state = ~0; + _talkCount = 0; + _sourceChar = 0xffff; + _currHover = ~0; + _currEntry = ~0; + _nextToProcess = 0xffff; + for (uint i = 0; i < 8; i++) + for (uint j = 0; j < 5; j++) + _asked[i][j] = false; +} + +void CSTimeConversation::loadQaR(CSTimeQaR &qar, uint16 id) { + Common::SeekableReadStream *qarsStream = _vm->getResource(ID_QARS, id); + qar.finished = false; + qar.unknown1 = qarsStream->readUint16BE(); + qar.questionStringId = qarsStream->readUint16BE(); + qar.responseStringId = qarsStream->readUint16BE(); + qar.unknown2 = qarsStream->readUint16BE(); + qar.nextQaRsId = qarsStream->readUint16BE(); + uint16 numEvents = qarsStream->readUint16BE(); + for (uint j = 0; j < numEvents; j++) { + CSTimeEvent event; + event.type = qarsStream->readUint16BE(); + event.param1 = qarsStream->readUint16BE(); + event.param2 = qarsStream->readUint16BE(); + qar.events.push_back(event); + } +} + +void CSTimeConversation::highlightLine(uint line) { + CSTimeQaR &qar = _qars[_itemsToDisplay[line]]; + _vm->getInterface()->displayDialogLine(qar.questionStringId, line, 244); +} + +void CSTimeConversation::unhighlightLine(uint line) { + CSTimeQaR &qar = _qars[_itemsToDisplay[line]]; + _vm->getInterface()->displayDialogLine(qar.questionStringId, line, qar.finished ? 13 : 32); +} + +CSTimeCase::CSTimeCase(MohawkEngine_CSTime *vm, uint id) : _vm(vm), _id(id) { + _vm->loadResourceFile(Common::String::format("Cases/C%dText", id)); + // We load this early, so we can use the text for debugging. + loadRolloverText(); + + _vm->loadResourceFile(Common::String::format("Cases/C%dInfo", id)); + Common::SeekableReadStream *caseInfoStream = _vm->getResource(ID_CINF, 1); + uint16 numScenes = caseInfoStream->readUint16BE(); + uint16 numInvObjs = caseInfoStream->readUint16BE(); + uint16 numConversations = caseInfoStream->readUint16BE(); + for (uint i = 0; i < 3; i++) + _noteFeatureId[i] = caseInfoStream->readUint16BE(); + delete caseInfoStream; + + debug("Loading %d inventory objects...", numInvObjs); + for (uint i = 0; i < numInvObjs; i++) + _inventoryObjs.push_back(loadInventoryObject(i)); + + _vm->loadResourceFile(Common::String::format("Cases/C%dArt", id)); + _vm->loadResourceFile(Common::String::format("Cases/C%dDlog", id)); + + debug("Loading %d scenes...", numScenes); + for (uint i = 0; i < numScenes; i++) + _scenes.push_back(new CSTimeScene(_vm, this, i + 1)); + + debug("Loading %d conversations...", numConversations); + for (uint i = 0; i < numConversations; i++) + _conversations.push_back(new CSTimeConversation(_vm, i)); + + assert(!_conversations.empty()); + _currConv = _conversations[0]; + + _currScene = ~0; +} + +CSTimeCase::~CSTimeCase() { +} + +void CSTimeCase::loadRolloverText() { + Common::SeekableReadStream *stringStream = _vm->getResource(ID_STRL, 9100); + while (stringStream->pos() < stringStream->size()) + _rolloverText.push_back(readString(stringStream)); + for (uint i = 0; i < _rolloverText.size(); i++) + debug("string %d: '%s'", i, _rolloverText[i].c_str()); + delete stringStream; +} + +CSTimeInventoryObject *CSTimeCase::loadInventoryObject(uint id) { + CSTimeInventoryObject *invObj = new CSTimeInventoryObject; + invObj->feature = NULL; + invObj->id = id; + + Common::SeekableReadStream *invObjStream = _vm->getResource(ID_INVO, id + 1); + uint16 numHotspots = invObjStream->readUint16BE(); + invObj->stringId = invObjStream->readUint16BE(); + invObj->hotspotId = invObjStream->readUint16BE(); + invObj->featureId = invObjStream->readUint16BE(); + invObj->canTake = invObjStream->readUint16BE(); + debug(" invobj '%s', hotspot id %d, feature id %d, can take %d", _rolloverText[invObj->stringId].c_str(), invObj->hotspotId, invObj->featureId, invObj->canTake); + uint16 numConsumableLocations = invObjStream->readUint16BE(); + debug(" Loading %d consumable locations...", numConsumableLocations); + for (uint i = 0; i < numConsumableLocations; i++) { + CSTimeLocation location; + location.sceneId = invObjStream->readUint16BE(); + location.hotspotId = invObjStream->readUint16BE(); + invObj->locations.push_back(location); + } + uint16 numEvents = invObjStream->readUint16BE(); + debug(" Loading %d events...", numEvents); + for (uint i = 0; i < numEvents; i++) { + CSTimeEvent event; + event.type = invObjStream->readUint16BE(); + event.param1 = invObjStream->readUint16BE(); + event.param2 = invObjStream->readUint16BE(); + invObj->events.push_back(event); + } + debug(" Loading %d hotspots...", numHotspots); + for (uint i = 0; i < numHotspots; i++) { + CSTimeInventoryHotspot hotspot; + hotspot.sceneId = invObjStream->readUint16BE(); + hotspot.hotspotId = invObjStream->readUint16BE(); + hotspot.stringId = invObjStream->readUint16BE(); + uint16 numHotspotEvents = invObjStream->readUint16BE(); + debug(" scene %d, hotspot %d, string id %d, with %d hotspot events", hotspot.sceneId, hotspot.hotspotId, hotspot.stringId, numHotspotEvents); + for (uint j = 0; j < numHotspotEvents; j++) { + CSTimeEvent event; + event.type = invObjStream->readUint16BE(); + event.param1 = invObjStream->readUint16BE(); + event.param2 = invObjStream->readUint16BE(); + hotspot.events.push_back(event); + } + invObj->hotspots.push_back(hotspot); + } + delete invObjStream; + + return invObj; +} + +CSTimeScene *CSTimeCase::getCurrScene() { + return _scenes[_currScene - 1]; +} + +CSTimeScene::CSTimeScene(MohawkEngine_CSTime *vm, CSTimeCase *case_, uint id) : _vm(vm), _case(case_), _id(id) { + _activeChar = NULL; + _currHotspot = ~0; + _hoverHotspot = ~0; + load(); +} + +void CSTimeScene::load() { + Common::SeekableReadStream *sceneStream = _vm->getResource(ID_SCEN, _id + 1000); + _unknown1 = sceneStream->readUint16BE(); + _unknown2 = sceneStream->readUint16BE(); + _helperId = sceneStream->readUint16BE(); + _bubbleType = sceneStream->readUint16BE(); + uint16 numHotspots = sceneStream->readUint16BE(); + _numObjects = sceneStream->readUint16BE(); + debug("Scene: unknowns %d, %d, %d, bubble type %d, %d objects", _unknown1, _unknown2, _helperId, _bubbleType, _numObjects); + + uint16 numChars = sceneStream->readUint16BE(); + uint16 numEvents = sceneStream->readUint16BE(); + debug(" Loading %d events...", numEvents); + for (uint i = 0; i < numEvents; i++) { + CSTimeEvent event; + event.type = sceneStream->readUint16BE(); + event.param1 = sceneStream->readUint16BE(); + event.param2 = sceneStream->readUint16BE(); + _events.push_back(event); + } + uint16 numEvents2 = sceneStream->readUint16BE(); + debug(" Loading %d events2...", numEvents2); + for (uint i = 0; i < numEvents2; i++) { + CSTimeEvent event; + event.type = sceneStream->readUint16BE(); + event.param1 = sceneStream->readUint16BE(); + event.param2 = sceneStream->readUint16BE(); + _events2.push_back(event); + } + debug(" Loading %d chars...", numChars); + for (uint i = 0; i < numChars; i++) { + CSTimeChar *chr = new CSTimeChar(_vm, this, i); + if (!_activeChar) + _activeChar = chr; + chr->_enabled = true; + chr->_unknown1 = sceneStream->readUint16BE(); + chr->_unknown2 = sceneStream->readUint16BE(); + uint16 numAmbients = sceneStream->readUint16BE(); + chr->_unknown3 = sceneStream->readUint16BE(); + debug(" unknowns %d, %d, %d, with %d ambients", chr->_unknown1, chr->_unknown2, chr->_unknown3, numAmbients); + for (uint j = 0; j < numAmbients; j++) { + CSTimeAmbient ambient; + ambient.delay = sceneStream->readUint16BE(); + ambient.feature = NULL; + chr->_ambients.push_back(ambient); + } + _chars.push_back(chr); + } + debug(" Loading %d hotspots...", numHotspots); + for (uint i = 0; i < numHotspots; i++) { + CSTimeHotspot hotspot; + hotspot.stringId = sceneStream->readUint16BE(); + hotspot.invObjId = sceneStream->readUint16BE(); + hotspot.cursor = sceneStream->readUint16BE(); + hotspot.state = sceneStream->readUint16BE(); + uint16 numHotspotEvents = sceneStream->readUint16BE(); + debug(" hotspot '%s', inv obj %d, cursor %d, state %d, with %d hotspot events", _case->getRolloverText(hotspot.stringId).c_str(), hotspot.invObjId, hotspot.cursor, hotspot.state, numHotspotEvents); + for (uint j = 0; j < numHotspotEvents; j++) { + CSTimeEvent event; + event.type = sceneStream->readUint16BE(); + event.param1 = sceneStream->readUint16BE(); + event.param2 = sceneStream->readUint16BE(); + hotspot.events.push_back(event); + } + _hotspots.push_back(hotspot); + } + delete sceneStream; + + Common::SeekableReadStream *hotspotStream = _vm->getResource(ID_HOTS, _id + 1100); + for (uint i = 0; i < _hotspots.size(); i++) { + _hotspots[i].region.loadFrom(hotspotStream); + } + delete hotspotStream; +} + +void CSTimeScene::installGroup() { + uint16 resourceId = getSceneId(); + _vm->getView()->installGroup(resourceId, _numObjects, 0, true, resourceId); + + for (uint i = 0; i < _chars.size(); i++) { + uint count = _chars[i]->getScriptCount(); + if (!count) + continue; + _vm->getView()->installGroup(resourceId, count, 0, true, _chars[i]->getChrBaseId()); + } +} + +void CSTimeScene::buildScene() { + uint16 resourceId = getSceneId(); + + _vm->getView()->installBG(resourceId); + + for (uint i = 0; i < _numObjects; i++) { + if (!_case->checkObjectCondition(i)) { + _objectFeatures.push_back(NULL); + continue; + } + + // FIXME: reset object if it already exists + // FIXME: deal with NULL + uint32 flags = kFeatureSortStatic | kFeatureNewNoLoop | kFeatureNewDisableOnReset; + assert(flags == 0x4C00000); + Feature *feature = _vm->getView()->installViewFeature(resourceId + i, flags, NULL); + _objectFeatures.push_back(feature); + } +} + +void CSTimeScene::leave() { + for (uint i = 0; i < _objectFeatures.size(); i++) { + if (_objectFeatures[i] == NULL) + continue; + _vm->getView()->removeFeature(_objectFeatures[i], true); + _objectFeatures[i] = NULL; + } + + for (uint i = 0; i < _chars.size(); i++) { + _chars[i]->stopAmbients(false); + _chars[i]->removeChr(); + _chars[i]->removeNIS(); + } + + _vm->getView()->removeGroup(getSceneId()); +} + +uint16 CSTimeScene::getSceneId() { + // FIXME: there are a lot of special cases + uint16 resourceId = 10000 + 2000 * (_id - 1); + return resourceId; +} + +void CSTimeScene::mouseDown(Common::Point &pos) { + CSTimeConversation *conv = _vm->getCase()->getCurrConversation(); + bool convActive = (conv->getState() != (uint)~0); + bool helpActive = (_vm->getInterface()->getHelp()->getState() != (uint)~0); + if (convActive || helpActive) { + bool foundPoint = false; + for (uint i = 0; i < _hotspots.size(); i++) { + CSTimeHotspot &hotspot = _hotspots[i]; + if (!hotspot.region.containsPoint(pos)) + continue; + foundPoint = true; + + if (!convActive) { + // In help mode, we ignore clicks on any help hotspot. + if (!hotspotContainsEvent(i, kCSTimeEventStartHelp)) + break; + _currHotspot = ~0; + return; + } + + // In conversation mode, we ignore clicks which would restart the current conversation. + for (uint j = 0; j < hotspot.events.size(); j++) { + if (hotspot.events[j].type != kCSTimeEventStartConversation) + continue; + // FIXME: check that the conversation *is* the current one + _currHotspot = ~0; + return; + } + + break; + } + + if (foundPoint) { + // We found a valid hotspot and we didn't ignore it, stop the conversation/help. + if (convActive) { + conv->end(false); + } else { + _vm->getInterface()->getHelp()->end(); + } + } + } else { + // FIXME: check symbols + } + + // FIXME: return if sailing puzzle + + _currHotspot = ~0; + for (uint i = 0; i < _hotspots.size(); i++) { + CSTimeHotspot &hotspot = _hotspots[i]; + if (!hotspot.region.containsPoint(pos)) + continue; + if (hotspot.state != 1) + continue; + mouseDownOnHotspot(i); + break; + } + + if (_currHotspot == (uint)~0) + _vm->getInterface()->cursorSetShape(4, false); +} + +void CSTimeScene::mouseMove(Common::Point &pos) { + // TODO: if we're in sailing puzzle, return + + bool mouseIsDown = _vm->getEventManager()->getButtonState() & 1; + + if (_vm->getInterface()->getState() == kCSTimeInterfaceStateDragStart) { + Common::Point grabPoint = _vm->getInterface()->getGrabPoint(); + if (mouseIsDown && (abs(grabPoint.x - pos.x) > 2 || abs(grabPoint.y - pos.y) > 2)) { + if (_vm->getInterface()->grabbedFromInventory()) { + _vm->getInterface()->startDragging(_vm->getInterface()->getInventoryDisplay()->getLastDisplayedClicked()); + } else { + CSTimeHotspot &hotspot = _hotspots[_currHotspot]; + if (_vm->_haveInvItem[hotspot.invObjId]) { + _vm->getInterface()->setState(kCSTimeInterfaceStateNormal); + } else { + // FIXME: special-casing for cases 9, 13, 15, 18 19 + + CSTimeInventoryObject *invObj = _vm->getCase()->_inventoryObjs[hotspot.invObjId]; + if (invObj->feature) + error("Inventory object already had feature when dragging from scene"); + uint16 id = 9000 + (invObj->id - 1); + // FIXME: 0x2000 is set! help? + uint32 flags = kFeatureNewNoLoop | 0x2000; + invObj->feature = _vm->getView()->installViewFeature(id, flags, &grabPoint); + _vm->getInterface()->startDragging(hotspot.invObjId); + } + } + } + } + + for (uint i = 0; i < _hotspots.size(); i++) { + CSTimeHotspot &hotspot = _hotspots[i]; + if (hotspot.state != 1) + continue; + if (!hotspot.region.containsPoint(pos)) + continue; + + if (_vm->getInterface()->getState() == kCSTimeInterfaceStateDragging) { + // Only show a hotspot here if the dragged object can be dropped onto it. + CSTimeInventoryObject *invObj = _vm->getCase()->_inventoryObjs[_vm->getInterface()->getDraggedNum()]; + bool showText = false; + for (uint j = 0; j < invObj->hotspots.size(); j++) { + if (invObj->hotspots[j].sceneId != _id) + continue; + if (invObj->hotspots[j].hotspotId != i) + continue; + showText = true; + } + if (!showText) + continue; + } else { + // If we're not dragging but the mouse is down, ignore all hotspots + // except the one which was clicked on. + if (mouseIsDown && (i != _currHotspot)) + continue; + } + + if (i != _hoverHotspot); + _vm->getInterface()->clearTextLine(); + cursorOverHotspot(i); + _hoverHotspot = i; + return; + } + + if (_vm->getInterface()->getState() != kCSTimeInterfaceStateDragging) { + switch (_vm->getInterface()->cursorGetShape()) { + case 2: + case 13: + _vm->getInterface()->cursorSetShape(1); + break; + case 5: + case 14: + _vm->getInterface()->cursorSetShape(4); + break; + case 11: + _vm->getInterface()->cursorSetShape(10); + break; + } + } + + if (_hoverHotspot == (uint)~0) + return; + + CSTimeConversation *conv = _case->getCurrConversation(); + CSTimeHelp *help = _vm->getInterface()->getHelp(); + if (conv->getState() != (uint)~0 && conv->getState() != 0) { + Common::String text = "Talking to " + _case->getRolloverText(conv->getNameId()); + _vm->getInterface()->displayTextLine(text); + } else if (help->getState() != (uint)~0 && help->getState() != 0) { + Common::String text = "Talking to " + _case->getRolloverText(0); + _vm->getInterface()->displayTextLine(text); + } else if (_vm->getInterface()->getState() == kCSTimeInterfaceStateDragging) { + // TODO: check symbols before this + CSTimeInventoryObject *invObj = _vm->getCase()->_inventoryObjs[_vm->getInterface()->getDraggedNum()]; + Common::String text = "Holding " + _case->getRolloverText(invObj->stringId); + _vm->getInterface()->displayTextLine(text); + } else { + _vm->getInterface()->clearTextLine(); + } + + _hoverHotspot = (uint)~0; +} + +void CSTimeScene::mouseUp(Common::Point &pos) { + // TODO: if sailing puzzle is active, return + + if (_currHotspot == (uint)~0) { + if (_vm->getInterface()->cursorGetShape() == 4) + _vm->getInterface()->cursorChangeShape(1); + return; + } + + if (_vm->getInterface()->getState() == kCSTimeInterfaceStateDragStart) + _vm->getInterface()->setState(kCSTimeInterfaceStateNormal); + + CSTimeHotspot &hotspot = _hotspots[_currHotspot]; + if (hotspot.region.containsPoint(pos) && hotspot.state == 1) { + mouseUpOnHotspot(_currHotspot); + } else { + if (_vm->getInterface()->cursorGetShape() == 4 || _vm->getInterface()->cursorGetShape() == 14) + _vm->getInterface()->cursorChangeShape(1); + } +} + +void CSTimeScene::idle() { + // TODO: if sailing puzzle is active, idle it instead + + for (uint i = 0; i < _chars.size(); i++) + _chars[i]->idle(); +} + +void CSTimeScene::setupAmbientAnims() { + for (uint i = 0; i < _chars.size(); i++) + _chars[i]->setupAmbientAnims(false); +} + +void CSTimeScene::idleAmbientAnims() { + if (_vm->NISIsRunning()) + return; + + for (uint i = 0; i < _chars.size(); i++) + _chars[i]->idleAmbients(); +} + +bool CSTimeScene::eventIsActive() { + return _vm->NISIsRunning() /* TODO || _vm->soundIsPlaying()*/ || _vm->getCurrentEventType() == kCSTimeEventWaitForClick + || _activeChar->_flappingState != 0xffff || _vm->getInterface()->getState() == 4; +} + +void CSTimeScene::cursorOverHotspot(uint id) { + CSTimeHotspot &hotspot = _hotspots[id]; + + if (!_vm->getInterface()->cursorGetState()) + return; + + if (_vm->getInterface()->getState() == kCSTimeInterfaceStateDragging) { + uint16 stringId = 0xffff; + + uint16 draggedId = _vm->getInterface()->getDraggedNum(); + CSTimeInventoryObject *invObj = _vm->getCase()->_inventoryObjs[draggedId]; + for (uint j = 0; j < invObj->hotspots.size(); j++) { + if (invObj->hotspots[j].sceneId != _id) + continue; + if (invObj->hotspots[j].hotspotId != id) + continue; + stringId = invObj->hotspots[j].stringId; + break; + } + + if (hotspot.stringId != 0xFFFF) { + Common::String textLine; + if (false) { + // FIXME: special-case time cuffs + } else { + bool isChar = (hotspot.cursor == 1) && (draggedId != TIME_CUFFS_ID); + textLine = (isChar ? "Give " : "Use "); + textLine += _case->getRolloverText(invObj->stringId); + textLine += (isChar ? " to " : " on "); + textLine += _case->getRolloverText(stringId); + } + _vm->getInterface()->displayTextLine(textLine); + } + } else { + if (hotspot.stringId != 0xFFFF) + _vm->getInterface()->displayTextLine(_case->getRolloverText(hotspot.stringId)); + } + + if (_vm->getEventManager()->getButtonState() & 1) { + if (_vm->getInterface()->getState() != kCSTimeInterfaceStateDragStart && _vm->getInterface()->getState() != kCSTimeInterfaceStateDragging) { + CSTimeHotspot &currHotspot = _hotspots[_currHotspot]; + if (currHotspot.cursor == 2) + _vm->getInterface()->cursorSetShape(14); + else + _vm->getInterface()->cursorSetShape(5); + } + } else { + if (hotspot.cursor == 2) + _vm->getInterface()->cursorSetShape(13); + else if (_vm->getInterface()->cursorGetShape() != 8 && _vm->getInterface()->cursorGetShape() != 11) + _vm->getInterface()->cursorSetShape(2); + } +} + +void CSTimeScene::mouseDownOnHotspot(uint id) { + CSTimeHotspot &hotspot = _hotspots[id]; + + _currHotspot = id; + + if (hotspot.invObjId == 0xffff || _vm->_haveInvItem[hotspot.invObjId]) { + if (hotspot.cursor == 2) + _vm->getInterface()->cursorChangeShape(14); + else + _vm->getInterface()->cursorChangeShape(5); + return; + } + + _vm->getInterface()->cursorSetShape(8, true); + _vm->getInterface()->setGrabPoint(); + _vm->getInterface()->setState(kCSTimeInterfaceStateDragStart); + + CSTimeInventoryObject *invObj = _vm->getCase()->_inventoryObjs[hotspot.invObjId]; + _vm->getInterface()->displayTextLine("Pick up " + _case->getRolloverText(invObj->stringId)); +} + +void CSTimeScene::mouseUpOnHotspot(uint id) { + CSTimeHotspot &hotspot = _hotspots[id]; + + _vm->addEventList(hotspot.events); + if (_vm->getInterface()->cursorGetShape() == 8 || (!hotspot.events.empty() && _vm->getInterface()->cursorGetShape() == 11)) + return; + if (hotspot.cursor == 2) + _vm->getInterface()->cursorChangeShape(13); + else + _vm->getInterface()->cursorChangeShape(2); +} + +bool CSTimeScene::hotspotContainsEvent(uint id, uint16 eventType) { + CSTimeHotspot &hotspot = _hotspots[id]; + + for (uint i = 0; i < hotspot.events.size(); i++) + if (hotspot.events[i].type == eventType) + return true; + + return false; +} + +void CSTimeScene::setCursorForCurrentPoint() { + Common::Point mousePos = _vm->getEventManager()->getMousePos(); + + for (uint i = 0; i < _hotspots.size(); i++) { + if (!_hotspots[i].region.containsPoint(mousePos)) + continue; + if (_hotspots[i].state != 1) + continue; + if (_hotspots[i].cursor == 2) { + _vm->getInterface()->cursorSetShape(13); + } else switch (_vm->getInterface()->cursorGetShape()) { + case 8: + break; + case 12: + _vm->getInterface()->cursorSetShape(11); + break; + default: + _vm->getInterface()->cursorSetShape(2); + break; + } + return; + } + + _vm->getInterface()->cursorSetShape(1); +} + +void CSTimeScene::drawHotspots() { + for (uint i = 0; i < _hotspots.size(); i++) { + for (uint j = 0; j < _hotspots[i].region._rects.size(); j++) { + _vm->_gfx->drawRect(_hotspots[i].region._rects[j], 10 + 5*i); + } + } +} + +const Common::Array<CSTimeEvent> &CSTimeScene::getEvents(bool second) { + if (second) + return _events2; + else + return _events; +} + +} // End of namespace Mohawk |