/* ScummVM - Graphic Adventure Engine * * ScummVM is the legal property of its developers, whose names * are too numerous to list here. Please refer to the COPYRIGHT * file distributed with this source distribution. * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * You should have received a copy of the GNU General Public License * along with this program. If not, see . * */ #include "common/intrinsics.h" #include "asylum/puzzles/pipes.h" #include "asylum/resources/worldstats.h" #include "asylum/system/cursor.h" #include "asylum/system/graphics.h" #include "asylum/system/screen.h" #include "asylum/views/scene.h" #include "asylum/asylum.h" namespace Asylum { const int16 connectorPoints[21][2] = { {158, 59}, {163, 172}, {168, 272}, {202, 59}, {205, 132}, {206, 172}, {271, 60}, {272, 131}, {273, 262}, {318, 169}, {319, 206}, {318, 261}, {380, 72}, {360, 171}, {360, 206}, {428, 172}, {401, 242}, {399, 295}, {469, 119}, {466, 171}, {460, 294}, }; static const int16 peepholePoints[37][2] = { {140, 65}, {311, 44}, {387, 48}, {475, 72}, {189, 67}, {246, 66}, {169, 113}, {215, 106}, {280, 105}, {336, 95}, {434, 80}, {248, 136}, {303, 154}, {407, 125}, {470, 151}, {193, 180}, {347, 176}, {401, 177}, {245, 201}, {325, 196}, {347, 212}, {406, 213}, {431, 218}, {174, 228}, {217, 234}, {280, 227}, {325, 239}, {370, 244}, {467, 239}, {303, 267}, {405, 273}, {356, 293}, {436, 294}, {182, 317}, {277, 299}, {324, 291}, {461, 323} }; const uint32 peepholeResources[] = {15, 15, 15, 15, 32, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 32, 32, 15, 15, 32, 32, 15, 15, 15, 15, 15, 15, 15, 15, 32, 15, 15, 15, 15, 15, 15, 15}; static BinNum calcStateFromPosition(ConnectorType type, uint32 position) { assert(position); position--; uint32 shift = !!position + !!(position >> 1) + !!(position >> 2); return BinNum((type >> shift | type << (4 - shift)) & 0xF); } ////////////////////////////////////////////////////////////////////////// // Peephole ////////////////////////////////////////////////////////////////////////// bool Peephole::marks[peepholesCount]; void Peephole::startUpWater(bool flag) { if (flag) memset(marks, false, sizeof(marks)); marks[_id] = true; for (auto &connector : _connectors) { for (auto &connectedNode : connector->_connectedNodes) { if (!marks[connectedNode->getId()]) { for (uint32 i = 0; i < 4; ++i) { if (isConnected(i) && connectedNode->getId() > 3) connectedNode->_flowValues[i] += _flowValues[i]; } connectedNode->startUpWater(); } } } } ////////////////////////////////////////////////////////////////////////// // Connector ////////////////////////////////////////////////////////////////////////// Connector::Connector() : _id(0), _position(nullptr), _state(kBinNum0000), _isConnected(false), _nextConnector(nullptr), _type(kConnectorTypeI), _nextConnectorPosition(kDirectionNowhere) { memset(_nodes, 0, sizeof(_nodes)); } void Connector::init(Peephole *n, Peephole *e, Peephole *s, Peephole *w, uint32 pos, ConnectorType type, Connector *nextConnector, Direction nextConnectorPosition) { _nodes[0] = n; _nodes[1] = e; _nodes[2] = s; _nodes[3] = w; *_position = pos; _type = type; _state = calcStateFromPosition(_type, *_position); _nextConnector = nextConnector; _nextConnectorPosition = nextConnectorPosition; _isConnected = false; for (uint32 i = 0; i < 4; ++i) { if (_state & ((uint32)1 << i) && _nodes[i]) { _nodes[i]->connect(this); _connectedNodes.push_back(_nodes[i]); } } } void Connector::initGroup() { if (!_isConnected && isReadyForConnection() && _nextConnector->isReadyForConnection()) connect(_nextConnector); } void Connector::turn(bool updpos) { if (updpos) *_position = (*_position == 8) ? 1 : *_position << 1; BinNum newState = BinNum(_state >> 1 | (_state & 1) << 3); uint32 delta = _state ^ newState; uint32 newIndex[2], oldIndex[2]; if (delta == kBinNum1111) { if (newState == kBinNum0101) { newIndex[0] = 0; newIndex[1] = 2; oldIndex[0] = 1; oldIndex[1] = 3; } else { newIndex[0] = 1; newIndex[1] = 3; oldIndex[0] = 0; oldIndex[1] = 2; } } else { newIndex[0] = (uint32)Common::intLog2(newState & delta); oldIndex[0] = (uint32)Common::intLog2(_state & delta); } for (uint32 i = 0; i < (uint32)(delta == kBinNum1111 ? 2 : 1); ++i) { if (_nodes[oldIndex[i]]) { _nodes[oldIndex[i]]->disconnect(this); _connectedNodes.remove(_nodes[oldIndex[i]]); } if (_nodes[newIndex[i]]) { _nodes[newIndex[i]]->connect(this); _connectedNodes.push_back(_nodes[newIndex[i]]); } } _state = newState; if (_nextConnector) { if (_isConnected) { if (!(_nextConnectorPosition & _state)) disconnect(_nextConnector); } else if (_nextConnectorPosition & _state && _nextConnector->isReadyForConnection()) { connect(_nextConnector); } } } void Connector::connect(Connector *connector) { for (auto &connectedNode : _connectedNodes) { connectedNode->connect(connector); connector->_connectedNodes.push_back(connectedNode); } for (auto &connectedNode : connector->_connectedNodes) { connectedNode->connect(this); _connectedNodes.push_back(connectedNode); } _isConnected = connector->_isConnected = true; } void Connector::disconnect(Connector *connector) { uint32 i; Common::List::iterator> markedForDeletion; bool flag; for (i = 0; i < 4; ++i) if (_nodes[i]) { _nodes[i]->disconnect(connector); connector->_connectedNodes.remove(_nodes[i]); } for (Common::List::iterator iter = _connectedNodes.begin(); iter != _connectedNodes.end(); ++iter) { flag = true; for (i = 0; i < 4; ++i) { if (*iter == _nodes[i]) { flag = false; break; } } if (flag) markedForDeletion.push_back(iter); } for (Common::List::iterator>::iterator iter1 = markedForDeletion.begin(); iter1 != markedForDeletion.end(); ++iter1) { (*(*iter1))->disconnect(this); _connectedNodes.remove(*(*iter1)); } _isConnected = connector->_isConnected = false; } ////////////////////////////////////////////////////////////////////////// // Spider ////////////////////////////////////////////////////////////////////////// Spider::Spider(AsylumEngine *engine, const Common::Rect &rect) : _vm(engine) { _boundingBox = rect; _isAlive = true; _location.x = (int16)rnd((uint16)(_boundingBox.right - _boundingBox.left + 1)) + _boundingBox.left; _location.y = (int16)rnd((uint16)(_boundingBox.bottom - _boundingBox.top + 1)) + _boundingBox.top; _direction = Direction((uint32)1 << rnd(4)); _stepsNumber = 0; _steps = 0; randomize(); } void Spider::randomize(Direction excluded) { if (rnd(6) == 5) _delta = Common::Point(0, 0); else { while (_direction == excluded) _direction = Direction((uint32)1 << rnd(4)); _delta = Common::Point((_direction & kBinNum0010 ? 1 : 0) - (_direction & kBinNum1000 ? 1 : 0), (_direction & kBinNum0100 ? 1 : 0) - (_direction & kBinNum0001 ? 1 : 0)); } _stepsNumber = rnd(maxStepsNumber - minStepsNumber + 1) + minStepsNumber; _steps = 0; } Common::Point Spider::move() { Common::Point previousLocation(_location); if (_isAlive) { if (_steps++ > _stepsNumber) randomize(); if (!_boundingBox.contains(_location + _delta)) randomize(_direction); else _location += _delta; } return previousLocation; } ////////////////////////////////////////////////////////////////////////// // PuzzlePipes ////////////////////////////////////////////////////////////////////////// PuzzlePipes::PuzzlePipes(AsylumEngine *engine) : Puzzle(engine) { _previousMusicVolume = 0; _rectIndex = -2; _frameIndex = _frameIndexLever = 0; memset(&_levelFlags, false, sizeof(_levelFlags)); _levelFlags[4] = true; memset(&_levelValues, 0, sizeof(_levelValues)); memset(&_previousLevels, 0, sizeof(_previousLevels)); _isLeverReady = false; memset(&_sinks, 0, sizeof(_sinks)); memset(&_sources, 0, sizeof(_sources)); _frameIndexSpider = nullptr; initResources(); setup(); } PuzzlePipes::~PuzzlePipes() { for (uint32 i = 0; i < _spiders.size(); ++i) delete _spiders[i]; if (_frameIndexSpider) delete[] _frameIndexSpider; } void PuzzlePipes::saveLoadWithSerializer(Common::Serializer &s) { s.skip(16); for (uint32 i = 0; i < connectorsCount; i++) { s.syncAsUint32LE(_positions[i]); } s.skip(16); } ////////////////////////////////////////////////////////////////////////// // Event Handling ////////////////////////////////////////////////////////////////////////// bool PuzzlePipes::init(const AsylumEvent &) { _previousMusicVolume = getSound()->getMusicVolume(); if (_previousMusicVolume >= -1000) getSound()->setMusicVolume(-1000); getSound()->playSound(getWorld()->graphicResourceIds[41], true, Config.ambientVolume); getScreen()->setPalette(getWorld()->graphicResourceIds[0]); getScreen()->setGammaLevel(getWorld()->graphicResourceIds[0]); _rectIndex = -2; checkConnections(); startUpWater(); (void)checkFlags(); getCursor()->show(); return true; } void PuzzlePipes::updateScreen() { getScreen()->clearGraphicsInQueue(); getScreen()->addGraphicToQueue(getWorld()->graphicResourceIds[1], 0, Common::Point(0, 0), kDrawFlagNone, 0, 4); for (uint32 i = 0; i < ARRAYSIZE(_connectors); ++i) getScreen()->addGraphicToQueue(getWorld()->graphicResourceIds[_connectorResources[_connectors[i].getState()]], 0, &connectorPoints[i], kDrawFlagNone, 0, 1); uint32 filled = 0; for (uint32 i = 0; i < 4; ++i) { if (fabs(_levelValues[i] - _previousLevels[i]) > 0.005) _previousLevels[i] += _levelValues[i] > _previousLevels[i] ? 0.01f : -0.01f; else ++filled; } getScreen()->addGraphicToQueue(getWorld()->graphicResourceIds[18], 0, Common::Point(210, 444 - int16(_previousLevels[0] * 52)), kDrawFlagNone, 0, 3); getScreen()->addGraphicToQueue(getWorld()->graphicResourceIds[18], 0, Common::Point(276, 455 - int16(_previousLevels[1] * 52)), kDrawFlagNone, 0, 3); getScreen()->addGraphicToQueue(getWorld()->graphicResourceIds[18], 0, Common::Point(376, 448 - int16(_previousLevels[2] * 52)), kDrawFlagNone, 0, 3); getScreen()->addGraphicToQueue(getWorld()->graphicResourceIds[18], 0, Common::Point(458, 442 - int16(_previousLevels[3] * 52)), kDrawFlagNone, 0, 3); getScreen()->addGraphicToQueue(getWorld()->graphicResourceIds[33], 0, Common::Point(204, 377), kDrawFlagNone, 0, 1); _frameIndex = (_frameIndex + 1) % GraphicResource::getFrameCount(_vm, getWorld()->graphicResourceIds[15]); for (uint32 i = 0; i < ARRAYSIZE(_peepholes); ++i) if (_peepholes[i].isConnected()) getScreen()->addGraphicToQueue(getWorld()->graphicResourceIds[peepholeResources[i]], _frameIndex, &peepholePoints[i], kDrawFlagNone, 0, 1); getScreen()->addGraphicToQueue(getWorld()->graphicResourceIds[2], _frameIndexLever, Common::Point(540, 90), kDrawFlagNone, 0, 1); _isLeverReady = false; if (_frameIndexLever) { _frameIndexLever = (_frameIndexLever + 1) % GraphicResource::getFrameCount(_vm, getWorld()->graphicResourceIds[2]); if (!_frameIndexLever) { _isLeverReady = true; getCursor()->show(); } } if (filled == 4) { if (_levelFlags[0]) getScreen()->addGraphicToQueue(getWorld()->graphicResourceIds[40], 0, Common::Point(233, 416), kDrawFlagNone, 0, 1); else if (_levelFlags[1]) getScreen()->addGraphicToQueue(getWorld()->graphicResourceIds[40], 0, Common::Point(299, 431), kDrawFlagNone, 0, 1); else if (_levelFlags[2]) getScreen()->addGraphicToQueue(getWorld()->graphicResourceIds[40], 0, Common::Point(398, 421), kDrawFlagNone, 0, 1); else if (_levelFlags[3]) getScreen()->addGraphicToQueue(getWorld()->graphicResourceIds[40], 0, Common::Point(481, 417), kDrawFlagNone, 0, 1); if (!_levelFlags[4]) getScreen()->addGraphicToQueue(getWorld()->graphicResourceIds[45], 0, Common::Point(518, 108), kDrawFlagNone, 0, 2); } for (uint32 i = 0; i < _spiders.size(); ++i) { uint32 spiderResourceId = 0; switch (_spiders[i]->getDirection()) { default: error("[PuzzlePipes::update] Invalid spider direction (%d)", _spiders[i]->getDirection()); case kDirectionNh: spiderResourceId = _spiders[i]->isAlive() ? 34 : 37; break; case kDirectionEt: spiderResourceId = _spiders[i]->isAlive() ? 35 : 38; // FIXME break; case kDirectionSh: spiderResourceId = _spiders[i]->isAlive() ? 36 : 39; break; case kDirectionWt: spiderResourceId = _spiders[i]->isAlive() ? 35 : 38; break; } if (_spiders[i]->isVisible(Common::Rect(-10, -10, 650, 490))) { uint32 frameCountSpider = GraphicResource::getFrameCount(_vm, getWorld()->graphicResourceIds[spiderResourceId]); _frameIndexSpider[i] = _spiders[i]->isActive() ? (_frameIndexSpider[i] + 1) % frameCountSpider : 0; getScreen()->addGraphicToQueue(getWorld()->graphicResourceIds[spiderResourceId], _frameIndexSpider[i], _spiders[i]->move(), kDrawFlagNone, 0, 1); } } if (_isLeverReady) { _vm->clearGameFlag(kGameFlagBrokenPipeSpraying); _vm->clearGameFlag(kGameFlagSmFtnOverflows); _vm->clearGameFlag(kGameFlagFountainFilling); _vm->clearGameFlag(kGameFlagSewerExplodes); if (!_levelFlags[4]) _vm->setGameFlag((GameFlag)(96 + checkFlags())); getScreen()->clear(); getSound()->stop(getWorld()->graphicResourceIds[41]); getSound()->setMusicVolume(_previousMusicVolume); _vm->switchEventHandler(getScene()); } } bool PuzzlePipes::mouseLeftDown(const AsylumEvent &) { Common::Point mousePos = getCursor()->position(); if (Common::Rect(540, 90, 590, 250).contains(mousePos)) { if (!_frameIndexLever) ++_frameIndexLever; getCursor()->hide(); getSound()->playSound(getWorld()->graphicResourceIds[43], false, Config.sfxVolume - 10); } else { if (_rectIndex != -1) { if (_rectIndex < ARRAYSIZE(connectorPoints)) { getSound()->playSound(getWorld()->graphicResourceIds[42], false, Config.sfxVolume - 10); _connectors[_rectIndex].turn(); startUpWater(); memset(_levelFlags, false, sizeof(_levelFlags)); _levelFlags[checkFlags()] = true; } else { getSound()->playSound(getWorld()->graphicResourceIds[44], false, Config.sfxVolume - 10); _spiders[_rectIndex - ARRAYSIZE(connectorPoints)]->smash(); _frameIndexSpider[_rectIndex - ARRAYSIZE(connectorPoints)] = 0; } } } return true; } bool PuzzlePipes::exitPuzzle() { getScreen()->clear(); getSound()->stop(getWorld()->graphicResourceIds[41]); getSound()->setMusicVolume(_previousMusicVolume); _vm->switchEventHandler(getScene()); return true; } ////////////////////////////////////////////////////////////////////////// // Helpers ////////////////////////////////////////////////////////////////////////// void PuzzlePipes::initResources() { _connectorResources[kBinNum0011] = 4; _connectorResources[kBinNum0110] = 3; _connectorResources[kBinNum1100] = 6; _connectorResources[kBinNum1001] = 5; _connectorResources[kBinNum0111] = 7; _connectorResources[kBinNum1110] = 10; _connectorResources[kBinNum1101] = 9; _connectorResources[kBinNum1011] = 8; _connectorResources[kBinNum0101] = 11; _connectorResources[kBinNum1010] = 12; } void PuzzlePipes::setup() { memset(&_levelValues, 0, sizeof(_levelValues)); for (uint32 i = 0; i < peepholesCount; ++i) _peepholes[i].setId(i); for (uint32 i = 0; i < connectorsCount; ++i) { _connectors[i].setId(i); _connectors[i].setPos(&_positions[i]); } for (uint32 i = 0; i < 4; ++i) { _sinks[i] = &_peepholes[(peepholesCount - 4) + i]; _sources[i] = &_peepholes[i]; memset(&_sources[i]->_flowValues, 0, sizeof(_sources[i]->_flowValues)); _sources[i]->_flowValues[i] = 1; } _connectors[ 0].init( nullptr, _peepholes + 4, _peepholes + 6, _peepholes + 0, 1, kConnectorTypeL); _connectors[ 1].init(_peepholes + 6, _peepholes + 15, _peepholes + 23, nullptr, 1, kConnectorTypeL); _connectors[ 2].init(_peepholes + 23, _peepholes + 24, _peepholes + 33, nullptr, 2, kConnectorTypeL); _connectors[ 3].init( nullptr, _peepholes + 5, _peepholes + 7, _peepholes + 4, 1, kConnectorTypeL); _connectors[ 4].init(_peepholes + 7, _peepholes + 11, nullptr, nullptr, 2, kConnectorTypeL, _connectors + 5, kDirectionSh); _connectors[ 5].init( nullptr, _peepholes + 18, _peepholes + 24, _peepholes + 15, 1, kConnectorTypeT, _connectors + 4, kDirectionNh); _connectors[ 6].init( nullptr, _peepholes + 1, _peepholes + 8, _peepholes + 5, 1, kConnectorTypeL); _connectors[ 7].init(_peepholes + 8, _peepholes + 12, _peepholes + 25, _peepholes + 11, 1, kConnectorTypeT); _connectors[ 8].init(_peepholes + 25, _peepholes + 29, _peepholes + 34, _peepholes + 18, 2, kConnectorTypeT); _connectors[ 9].init(_peepholes + 9, _peepholes + 16, _peepholes + 19, _peepholes + 12, 8, kConnectorTypeT); _connectors[10].init(_peepholes + 19, _peepholes + 20, _peepholes + 26, nullptr, 2, kConnectorTypeL); _connectors[11].init(_peepholes + 26, _peepholes + 31, _peepholes + 35, _peepholes + 29, 2, kConnectorTypeT); _connectors[12].init(_peepholes + 2, _peepholes + 10, nullptr, _peepholes + 9, 2, kConnectorTypeL); _connectors[13].init(_peepholes + 13, _peepholes + 17, nullptr, _peepholes + 16, 1, kConnectorTypeT, _connectors + 14, kDirectionSh); _connectors[14].init( nullptr, _peepholes + 21, _peepholes + 27, _peepholes + 20, 8, kConnectorTypeT, _connectors + 13, kDirectionNh); _connectors[15].init(_peepholes + 10, nullptr, _peepholes + 22, _peepholes + 17, 1, kConnectorTypeI, _connectors + 19, kDirectionEt); _connectors[16].init(_peepholes + 21, _peepholes + 22, _peepholes + 30, _peepholes + 27, 2, kConnectorTypeT); _connectors[17].init(_peepholes + 30, _peepholes + 32, nullptr, _peepholes + 31, 2, kConnectorTypeL); _connectors[18].init(_peepholes + 3, nullptr, _peepholes + 14, _peepholes + 13, 8, kConnectorTypeL); _connectors[19].init(_peepholes + 14, nullptr, _peepholes + 28, nullptr, 4, kConnectorTypeL, _connectors + 15, kDirectionWt); _connectors[20].init(_peepholes + 28, nullptr, _peepholes + 36, _peepholes + 32, 4, kConnectorTypeL); _connectors[ 4].initGroup(); _connectors[13].initGroup(); _connectors[15].initGroup(); uint32 i = rnd(kBinNum1000); if (i & kBinNum0001) _spiders.push_back(new Spider(_vm, Common::Rect(-10, 45, 92, 315))); if (i & kBinNum0010) _spiders.push_back(new Spider(_vm, Common::Rect(-10, 389, 149, 476))); if (i & kBinNum0100) _spiders.push_back(new Spider(_vm, Common::Rect(544, 225, 650, 490))); if (i) { _frameIndexSpider = new uint32[_spiders.size()](); } } void PuzzlePipes::updateCursor() { int32 index = findRect(); if (_rectIndex == index) return; _rectIndex = index; // FIXME if (index > -1 || Common::Rect(540, 90, 590, 250).contains(getCursor()->position())) getCursor()->set(getWorld()->graphicResourceIds[16]); else getCursor()->set(getWorld()->graphicResourceIds[16], 0, kCursorAnimationNone); } int32 PuzzlePipes::findRect() { for (uint32 i = 0; i < ARRAYSIZE(connectorPoints); ++i) if (Common::Rect(connectorPoints[i][0] - 5, connectorPoints[i][1] - 5, connectorPoints[i][0] + 30, connectorPoints[i][1] + 30).contains(getCursor()->position())) return i; for (uint32 i = 0; i < _spiders.size(); ++i) if (_spiders[i]->getPolygon(Common::Rect(10, 10, 30, 30)).contains(getCursor()->position())) return ARRAYSIZE(connectorPoints) + i; return -1; } uint32 PuzzlePipes::checkFlags() { uint32 total = _sinks[0]->getLevel1() + _sinks[1]->getLevel1() +_sinks[2]->getLevel1() + _sinks[3]->getLevel1(); float temp; uint32 val = 4; if (total) for (uint32 i = 0; i < 4; ++i) { temp = _sinks[i]->getLevel1() / float(total); _levelValues[i] = temp * _sinks[i]->getLevel() / 4; if (_levelValues[i] == 1.0) val = i; } else memset(_levelValues, 0, sizeof(_levelValues)); return val; } void PuzzlePipes::checkConnections() { for (uint32 i = 0; i < connectorsCount; i++) { BinNum oldState = _connectors[i].getState(), newState = calcStateFromPosition(_connectors[i].getType(), _positions[i]); if (oldState != newState) { do { _connectors[i].turn(false); } while (_connectors[i].getState() != newState); } } } void PuzzlePipes::startUpWater() { for (uint32 i = 4; i < peepholesCount; ++i) memset(_peepholes[i]._flowValues, 0, sizeof(_peepholes[i]._flowValues)); _sources[0]->startUpWater(true); _sources[1]->startUpWater(true); _sources[2]->startUpWater(true); _sources[3]->startUpWater(true); } } // End of namespace Asylum