https://github.com/scummvm/scummvm
Tip revision: 41b49444b67e82b3a7a6b63a9f40a6dc1abb2fea authored by Eugene Sandulenko on 22 September 2019, 22:12:30 UTC
RELEASE: This is 2.2.0git
RELEASE: This is 2.2.0git
Tip revision: 41b4944
cstime_game.cpp
/* 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 "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"
#include "common/system.h"
#include "common/textconsole.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;
_unknown1 = _unknown2 = _unknown3 = 0;
_enabled = false;
_nextCue = 0;
_waveStatus = 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 = _enabled ? 0 : 13;
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 (_talkFeature2) // original checks unknown1 > 1, but this is silly when e.g. _enabled is false
_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 *iface = _vm->getInterface();
CSTimeInventoryDisplay *invDisplay = iface->getInventoryDisplay();
if (invDisplay->getState() == 4) {
invDisplay->hide();
invDisplay->setState(0);
}
setState((uint)~0);
_currHover = 0xffff;
iface->clearTextLine();
iface->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 != 0xffff) {
if (_vm->getInterface()->cursorGetShape() != 3) {
_vm->getInterface()->cursorSetShape(1, true);
if (_vm->getInterface()->getInventoryDisplay()->getState() != 4)
unhighlightLine(_currHover);
}
_currHover = 0xffff;
}
}
void CSTimeConversation::mouseUp(Common::Point &pos) {
if (_vm->getInterface()->getInventoryDisplay()->getState() == 4)
return;
if (_currEntry == 0xffff)
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 = (uint)~0;
_talkCount = 0;
_sourceChar = 0xffff;
_currHover = 0xffff;
_currEntry = 0xffff;
_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 = 0xffff;
}
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();
invObj->featureDisabled = false;
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) {
_visitCount = 0;
_activeChar = NULL;
_currHotspot = 0xffff;
_hoverHotspot = 0xffff;
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 = 0xffff;
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 = 0xffff;
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 = 0xffff;
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 == 0xffff)
_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);
invObj->featureDisabled = false;
_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 == 0xffff)
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 = 0xffff;
}
void CSTimeScene::mouseUp(Common::Point &pos) {
// TODO: if sailing puzzle is active, return
if (_currHotspot == 0xffff) {
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() == kCSTimeInterfaceDroppedInventory;
}
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