aboutsummaryrefslogtreecommitdiff
path: root/engines
diff options
context:
space:
mode:
Diffstat (limited to 'engines')
-rw-r--r--engines/zvision/control.cpp323
-rw-r--r--engines/zvision/control.h88
2 files changed, 410 insertions, 1 deletions
diff --git a/engines/zvision/control.cpp b/engines/zvision/control.cpp
index ff767863f4..9a1d52bd4f 100644
--- a/engines/zvision/control.cpp
+++ b/engines/zvision/control.cpp
@@ -23,16 +23,25 @@
#include "common/scummsys.h"
#include "common/stream.h"
+#include "common/system.h"
+#include "common/tokenizer.h"
#include "zvision/zvision.h"
#include "zvision/render_manager.h"
#include "zvision/render_table.h"
#include "zvision/script_manager.h"
+#include "zvision/cursor_manager.h"
#include "zvision/control.h"
#include "zvision/utility.h"
+#include "zvision/rlf_animation.h"
+#include "zvision/zork_avi_decoder.h"
namespace ZVision {
+//////////////////////////////////////////////////////////////////////////////
+// Static member functions
+//////////////////////////////////////////////////////////////////////////////
+
void Control::parseFlatControl(ZVision *engine) {
engine->getRenderManager()->getRenderTable()->setRenderState(RenderTable::FLAT);
}
@@ -176,4 +185,318 @@ bool PushToggleControl::onMouseMove(const Common::Point &screenSpacePos, const C
return false;
}
+
+//////////////////////////////////////////////////////////////////////////////
+// LeverControl
+//////////////////////////////////////////////////////////////////////////////
+
+LeverControl::LeverControl(ZVision *engine, uint32 key, Common::SeekableReadStream &stream)
+ : MouseEvent(key),
+ _engine(engine),
+ _frameInfo(0),
+ _frameCount(0),
+ _startFrame(0),
+ _currentFrame(0),
+ _mouseIsCaptured(false),
+ _isReturning(false) {
+
+ // 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);
+
+ _engine->getScriptManager()->addActionNode(this);
+}
+
+LeverControl::~LeverControl() {
+ if (_fileType == AVI) {
+ delete _animation.avi;
+ } else if (_fileType == RLF) {
+ delete _animation.rlf;
+ }
+ if (_frameInfo != 0) {
+ 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 fileName(fileNameBuffer);
+
+ if (fileName.hasSuffix(".avi")) {
+ _animation.avi = new ZorkAVIDecoder();
+ _animation.avi->loadFile(fileName);
+ } else if (fileName.hasSuffix(".rlf")) {
+ _animation.rlf = new RlfAnimation(fileNameBuffer, false);
+ }
+ } else if (line.matchString("*skipcolor*", true)) {
+ // Not used
+ } else if (line.matchString("*anim_coords*", true)) {
+ sscanf(line.c_str(), "%*[^:]:%u %u %u %u~", &_animationCoords.left, &_animationCoords.top, &_animationCoords.right, &_animationCoords.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")) {
+ uint to;
+ sscanf(token.c_str(), "P(%*u to %u)", &to);
+
+ _frameInfo[frameNumber].returnRoute.push_back(to);
+ }
+
+ token = tokenizer.nextToken();
+ }
+ }
+
+ line = file.readLine();
+ }
+}
+
+void LeverControl::onMouseDown(const Common::Point &screenSpacePos, const Common::Point backgroundImageSpacePos) {
+ if (_frameInfo[_currentFrame].hotspot.contains(backgroundImageSpacePos)) {
+ _mouseIsCaptured = true;
+ _lastMousePos = backgroundImageSpacePos;
+ }
+}
+
+void LeverControl::onMouseUp(const Common::Point &screenSpacePos, const Common::Point backgroundImageSpacePos) {
+ _mouseIsCaptured = false;
+ _engine->getScriptManager()->setStateValue(_key, _currentFrame);
+
+ if (_frameInfo[_currentFrame].hotspot.contains(backgroundImageSpacePos)) {
+
+ }
+
+ // TODO: Animation reversal back to origin
+}
+
+bool LeverControl::onMouseMove(const Common::Point &screenSpacePos, const Common::Point backgroundImageSpacePos) {
+ 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<Direction>::iterator iter = _frameInfo[_currentFrame].directions.begin(); iter != _frameInfo[_currentFrame].directions.end(); iter++) {
+ if (angle >= (int)(*iter).angle - ANGLE_DELTA && angle <= (int)(*iter).angle) {
+ _currentFrame = (*iter).toFrame;
+ renderFrame(_currentFrame);
+ break;
+ }
+ }
+ }
+ } else if (_frameInfo[_currentFrame].hotspot.contains(backgroundImageSpacePos)) {
+ _engine->getCursorManager()->changeCursor(_cursorName);
+ }
+
+ return cursorWasChanged;
+}
+
+bool LeverControl::process(uint32 deltaTimeInMillis) {
+ // TODO: Implement reversal over time
+
+ 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;
+
+ int angle = int(atan((float)yDist / (float)xDist));
+
+ // Convert to degrees. (180 / 3.14159 = 57.2958)
+ angle *= 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 3 \ | / Quadrant 2
+ // \ | /
+ // \ | /
+ // -angle ( \|/ ) angle
+ // 180 <----------------------------------------> 0
+ // angle ( /|\ ) -angle
+ // / | \
+ // / | \
+ // Quadrant 1 / | \ Quadrant 0
+ // / | \
+ // / | \
+ // / | \
+ // * | *
+ // ^
+ // 270
+
+ // Convert the local angles to unit circle angles
+ switch (quadrant) {
+ case 0:
+ angle = 360 + angle;
+ break;
+ case 1:
+ angle = 180 + angle;
+ break;
+ case 2:
+ angle = 180 + angle;
+ break;
+ case 3:
+ // Do nothing
+ break;
+ }
+
+ return angle;
+ }
+}
+
+void LeverControl::renderFrame(uint frameNumber) {
+ const uint16 *frameData;
+ int pitch;
+ int x;
+ int y;
+ int width;
+ int height;
+
+ if (_fileType == RLF) {
+ // getFrameData() will automatically optimize to getNextFrame() / getPreviousFrame() if it can
+ frameData = _animation.rlf->getFrameData(_currentFrame);
+ pitch = _animation.rlf->width() * sizeof(uint16);
+ x = _animationCoords.left;
+ y = _animationCoords.right;
+ 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) {
+ // Cry because AVI seeking isn't implemented (yet)
+ }
+
+ _engine->_system->copyRectToScreen(frameData, pitch, x + _engine->_workingWindow.left, y + _engine->_workingWindow.top, width, height);
+}
+
+bool LeverControl::enable() {
+ // TODO: Implement
+ return true;
+}
+
+bool LeverControl::disable() {
+ // TODO: Implement
+ return true;
+}
+
} // End of namespace ZVision
diff --git a/engines/zvision/control.h b/engines/zvision/control.h
index 28079d52e7..d764f914bb 100644
--- a/engines/zvision/control.h
+++ b/engines/zvision/control.h
@@ -26,6 +26,7 @@
#include "common/types.h"
#include "zvision/mouse_event.h"
+#include "zvision/action_node.h"
namespace Common {
class SeekableReadStream;
@@ -34,6 +35,8 @@ class SeekableReadStream;
namespace ZVision {
class ZVision;
+class RlfAnimation;
+class ZorkAVIDecoder;
class Control {
public:
@@ -94,15 +97,98 @@ private:
Common::String _hoverCursor;
};
+
+class LeverControl : public Control, public MouseEvent, public ActionNode {
public:
+ LeverControl(ZVision *engine, uint32 key, Common::SeekableReadStream &stream);
+ ~LeverControl();
+
+private:
+ enum FileType {
+ RLF = 1,
+ AVI = 2
+ };
+
+ struct Direction {
+ Direction(uint angle, uint toFrame) : angle(angle), toFrame(toFrame) {}
+
+ uint angle;
+ uint toFrame;
+ };
+
+ struct FrameInfo {
+ Common::Rect hotspot;
+ Common::List<Direction> directions;
+ Common::List<uint> returnRoute;
+ };
+
+ enum {
+ ANGLE_DELTA = 30 // How far off a mouse angle can be and still be considered valid. This is in both directions, so the total buffer zone is (2 * ANGLE_DELTA)
+ };
private:
+ ZVision *_engine;
+
+ union {
+ RlfAnimation *rlf;
+ ZorkAVIDecoder *avi;
+ } _animation;
+ FileType _fileType;
+
+ Common::String _cursorName;
+ Common::Rect _animationCoords;
+ bool _mirrored;
+ uint _frameCount;
+ uint _startFrame;
+ Common::Point _hotspotDelta;
+ FrameInfo *_frameInfo;
+
+ uint _currentFrame;
+ bool _mouseIsCaptured;
+ bool _isReturning;
+ Common::Point _lastMousePos;
+ Common::List<uint>::iterator _returnRoutesCurrentProgress;
+
+public:
+ bool enable();
+ bool disable();
+ void onMouseDown(const Common::Point &screenSpacePos, const Common::Point backgroundImageSpacePos);
+ void onMouseUp(const Common::Point &screenSpacePos, const Common::Point backgroundImageSpacePos);
+ bool onMouseMove(const Common::Point &screenSpacePos, const Common::Point backgroundImageSpacePos);
+ bool process(uint32 deltaTimeInMillis);
+
+private:
+ void parseLevFile(const Common::String &fileName);
+ /**
+ * Calculates the angle a vector makes with the negative y-axis
+ *
+ * 90
+ * pointTwo * ^
+ * \ |
+ * \ |
+ * \ |
+ * \ |
+ * angle ( \|pointOne
+ * 180 <-----------*-----------> 0
+ * |
+ * |
+ * |
+ * |
+ * |
+ * ^
+ * 270
+ *
+ * @param pointOne The origin of the vector
+ * @param pointTwo The end of the vector
+ * @return The angle the vector makes with the negative y-axis
+ */
+ static int calculateVectorAngle(const Common::Point &pointOne, const Common::Point &pointTwo);
+ void renderFrame(uint frameNumber);
};
// TODO: Implement InputControl
// TODO: Implement SaveControl
// TODO: Implement SlotControl
-// TODO: Implement LeverControl
// TODO: Implement SafeControl
// TODO: Implement FistControl
// TODO: Implement HotMovieControl