/* 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/scummsys.h" #include "zvision/lever_control.h" #include "zvision/zvision.h" #include "zvision/script_manager.h" #include "zvision/render_manager.h" #include "zvision/cursor_manager.h" #include "zvision/rlf_animation.h" #include "zvision/zork_avi_decoder.h" #include "zvision/utility.h" #include "common/stream.h" #include "common/file.h" #include "common/tokenizer.h" #include "common/system.h" #include "graphics/surface.h" namespace ZVision { LeverControl::LeverControl(ZVision *engine, uint32 key, Common::SeekableReadStream &stream) : Control(engine, key), _frameInfo(0), _frameCount(0), _startFrame(0), _currentFrame(0), _lastRenderedFrame(0), _mouseIsCaptured(false), _isReturning(false), _accumulatedTime(0), _returnRoutesCurrentFrame(0) { // Loop until we find the closing brace Common::String line = stream.readLine(); trimCommentsAndWhiteSpace(&line); while (!stream.eos() && !line.contains('}')) { if (line.matchString("*descfile*", true)) { char levFileName[25]; sscanf(line.c_str(), "%*[^(](%25[^)])", levFileName); parseLevFile(levFileName); } else if (line.matchString("*cursor*", true)) { char cursorName[25]; sscanf(line.c_str(), "%*[^(](%25[^)])", cursorName); _cursorName = Common::String(cursorName); } line = stream.readLine(); trimCommentsAndWhiteSpace(&line); } renderFrame(_currentFrame); } LeverControl::~LeverControl() { if (_fileType == AVI) { delete _animation.avi; } else if (_fileType == RLF) { delete _animation.rlf; } delete[] _frameInfo; } void LeverControl::parseLevFile(const Common::String &fileName) { Common::File file; if (!file.open(fileName)) { warning("LEV file %s could could be opened", fileName.c_str()); return; } Common::String line = file.readLine(); while (!file.eos()) { if (line.matchString("*animation_id*", true)) { // Not used } else if (line.matchString("*filename*", true)) { char fileNameBuffer[25]; sscanf(line.c_str(), "%*[^:]:%25[^~]~", fileNameBuffer); Common::String animationFileName(fileNameBuffer); if (animationFileName.hasSuffix(".avi")) { _animation.avi = new ZorkAVIDecoder(); _animation.avi->loadFile(animationFileName); _fileType = AVI; } else if (animationFileName.hasSuffix(".rlf")) { _animation.rlf = new RlfAnimation(animationFileName, false); _fileType = RLF; } } else if (line.matchString("*skipcolor*", true)) { // Not used } else if (line.matchString("*anim_coords*", true)) { int left, top, right, bottom; sscanf(line.c_str(), "%*[^:]:%d %d %d %d~", &left, &top, &right, &bottom); _animationCoords.left = left; _animationCoords.top = top; _animationCoords.right = right; _animationCoords.bottom = bottom; } else if (line.matchString("*mirrored*", true)) { uint mirrored; sscanf(line.c_str(), "%*[^:]:%u~", &mirrored); _mirrored = mirrored == 0 ? false : true; } else if (line.matchString("*frames*", true)) { sscanf(line.c_str(), "%*[^:]:%u~", &_frameCount); _frameInfo = new FrameInfo[_frameCount]; } else if (line.matchString("*elsewhere*", true)) { // Not used } else if (line.matchString("*out_of_control*", true)) { // Not used } else if (line.matchString("*start_pos*", true)) { sscanf(line.c_str(), "%*[^:]:%u~", &_startFrame); _currentFrame = _startFrame; } else if (line.matchString("*hotspot_deltas*", true)) { uint x; uint y; sscanf(line.c_str(), "%*[^:]:%u %u~", &x, &y); _hotspotDelta.x = x; _hotspotDelta.y = y; } else { uint frameNumber; uint x, y; if (sscanf(line.c_str(), "%u:%u %u", &frameNumber, &x, &y) == 3) { _frameInfo[frameNumber].hotspot.left = x; _frameInfo[frameNumber].hotspot.top = y; _frameInfo[frameNumber].hotspot.right = x + _hotspotDelta.x; _frameInfo[frameNumber].hotspot.bottom = y + _hotspotDelta.y; } Common::StringTokenizer tokenizer(line, " ^=()"); tokenizer.nextToken(); tokenizer.nextToken(); Common::String token = tokenizer.nextToken(); while (!tokenizer.empty()) { if (token == "D") { token = tokenizer.nextToken(); uint angle; uint toFrame; sscanf(token.c_str(), "%u,%u", &toFrame, &angle); _frameInfo[frameNumber].directions.push_back(Direction(angle, toFrame)); } else if (token.hasPrefix("P")) { // Format: P( to ) tokenizer.nextToken(); tokenizer.nextToken(); token = tokenizer.nextToken(); uint to = atoi(token.c_str()); _frameInfo[frameNumber].returnRoute.push_back(to); } token = tokenizer.nextToken(); } } line = file.readLine(); } } void LeverControl::onMouseDown(const Common::Point &screenSpacePos, const Common::Point &backgroundImageSpacePos) { if (!_enabled) { return; } if (_frameInfo[_currentFrame].hotspot.contains(backgroundImageSpacePos)) { _mouseIsCaptured = true; _lastMousePos = backgroundImageSpacePos; } } void LeverControl::onMouseUp(const Common::Point &screenSpacePos, const Common::Point &backgroundImageSpacePos) { if (!_enabled) { return; } if (_mouseIsCaptured) { _mouseIsCaptured = false; _engine->getScriptManager()->setStateValue(_key, _currentFrame); _isReturning = true; _returnRoutesCurrentProgress = _frameInfo[_currentFrame].returnRoute.begin(); _returnRoutesCurrentFrame = _currentFrame; } } bool LeverControl::onMouseMove(const Common::Point &screenSpacePos, const Common::Point &backgroundImageSpacePos) { if (!_enabled) { return false; } bool cursorWasChanged = false; if (_mouseIsCaptured) { // Make sure the square distance between the last point and the current point is greater than 64 // This is a heuristic. This determines how responsive the lever is to mouse movement. // TODO: Fiddle with the heuristic to get a good lever responsiveness 'feel' if (_lastMousePos.sqrDist(backgroundImageSpacePos) >= 64) { int angle = calculateVectorAngle(_lastMousePos, backgroundImageSpacePos); _lastMousePos = backgroundImageSpacePos; for (Common::List::iterator iter = _frameInfo[_currentFrame].directions.begin(); iter != _frameInfo[_currentFrame].directions.end(); ++iter) { if (angle >= (int)iter->angle - ANGLE_DELTA && angle <= (int)iter->angle + ANGLE_DELTA) { _currentFrame = iter->toFrame; renderFrame(_currentFrame); break; } } } } else if (_frameInfo[_currentFrame].hotspot.contains(backgroundImageSpacePos)) { _engine->getCursorManager()->changeCursor(_cursorName); cursorWasChanged = true; } return cursorWasChanged; } bool LeverControl::process(uint32 deltaTimeInMillis) { if (!_enabled) { return false; } if (_isReturning) { _accumulatedTime += deltaTimeInMillis; while (_accumulatedTime >= ANIMATION_FRAME_TIME) { _accumulatedTime -= ANIMATION_FRAME_TIME; if (_returnRoutesCurrentFrame == *_returnRoutesCurrentProgress) { _returnRoutesCurrentProgress++; } if (_returnRoutesCurrentProgress == _frameInfo[_currentFrame].returnRoute.end()) { _isReturning = false; _currentFrame = _returnRoutesCurrentFrame; return false; } uint toFrame = *_returnRoutesCurrentProgress; if (_returnRoutesCurrentFrame < toFrame) { _returnRoutesCurrentFrame++; } else if (_returnRoutesCurrentFrame > toFrame) { _returnRoutesCurrentFrame--; } _engine->getScriptManager()->setStateValue(_key, _returnRoutesCurrentFrame); renderFrame(_returnRoutesCurrentFrame); } } return false; } int LeverControl::calculateVectorAngle(const Common::Point &pointOne, const Common::Point &pointTwo) { // Check for the easy angles first if (pointOne.x == pointTwo.x && pointOne.y == pointTwo.y) return -1; // This should never happen else if (pointOne.x == pointTwo.x) { if (pointTwo.y < pointOne.y) return 90; else return 270; } else if (pointOne.y == pointTwo.y) { if (pointTwo.x > pointOne.x) return 0; else return 180; } else { // Calculate the angle with trig int16 xDist = pointTwo.x - pointOne.x; int16 yDist = pointTwo.y - pointOne.y; // Calculate the angle using arctan // Then convert to degrees. (180 / 3.14159 = 57.2958) int angle = int(atan((float)yDist / (float)xDist) * 57); // Calculate what quadrant pointTwo is in uint quadrant = ((yDist > 0 ? 1 : 0) << 1) | (xDist < 0 ? 1 : 0); // Explanation of quadrants: // // yDist > 0 | xDist < 0 | Quadrant number // 0 | 0 | 0 // 0 | 1 | 1 // 1 | 0 | 2 // 1 | 1 | 3 // // Note: I know this doesn't line up with traditional mathematical quadrants // but doing it this way allows you can use a switch and is a bit cleaner IMO. // // The graph below shows the 4 quadrants pointTwo can end up in as well // as what the angle as calculated above refers to. // Note: The calculated angle in quadrants 0 and 3 is negative // due to arctan(-x) = -theta // // Origin => (pointOne.x, pointOne.y) // * => (pointTwo.x, pointTwo.y) // // 90 | // ^ | // * | * | // \ | / | // \ | / | // \ | / | // Quadrant 1 \ | / Quadrant 0 | // \ | / | // \ | / | // angle ( \|/ ) -angle | // 180 <----------------------------------------> 0 | // -angle ( /|\ ) angle | // / | \ | // / | \ | // Quadrant 3 / | \ Quadrant 2 | // / | \ | // / | \ | // / | \ | // * | * | // ^ | // 270 | // Convert the local angles to unit circle angles switch (quadrant) { case 0: angle = 180 + angle; break; case 1: // Do nothing break; case 2: angle = 180 + angle; break; case 3: angle = 360 + angle; break; } return angle; } } void LeverControl::renderFrame(uint frameNumber) { if (frameNumber == 0) { _lastRenderedFrame = frameNumber; } else if (frameNumber < _lastRenderedFrame && _mirrored) { _lastRenderedFrame = frameNumber; frameNumber = (_frameCount * 2) - frameNumber - 1; } else { _lastRenderedFrame = frameNumber; } const uint16 *frameData; int x = _animationCoords.left; int y = _animationCoords.top; int width; int height; if (_fileType == RLF) { // getFrameData() will automatically optimize to getNextFrame() / getPreviousFrame() if it can frameData = (const uint16 *)_animation.rlf->getFrameData(frameNumber)->getPixels(); width = _animation.rlf->width(); // Use the animation width instead of _animationCoords.width() height = _animation.rlf->height(); // Use the animation height instead of _animationCoords.height() } else if (_fileType == AVI) { _animation.avi->seekToFrame(frameNumber); const Graphics::Surface *surface = _animation.avi->decodeNextFrame(); frameData = (const uint16 *)surface->getPixels(); width = surface->w; height = surface->h; } _engine->getRenderManager()->copyRectToWorkingWindow(frameData, x, y, width, width, height); } } // End of namespace ZVision