[Scummvm-git-logs] scummvm master -> cf188ca8fb678080ef5b311b094ca787e0a2ff60

fracturehill noreply at scummvm.org
Tue Oct 17 15:32:04 UTC 2023


This automated email contains information about 3 new commits which have been
pushed to the 'scummvm' repo located at https://github.com/scummvm/scummvm .

Summary:
3551db675d NANCY: Implement AssemblyPuzzle
489b9c696d NANCY: Fix Coverity issues
cf188ca8fb NANCY: Implement CubePuzzle


Commit: 3551db675d56da5c8c08b0597bd79d6892555a1f
    https://github.com/scummvm/scummvm/commit/3551db675d56da5c8c08b0597bd79d6892555a1f
Author: Kaloyan Chehlarski (strahy at outlook.com)
Date: 2023-10-17T18:31:08+03:00

Commit Message:
NANCY: Implement AssemblyPuzzle

Implemented the record type responsible for nancy6's
broken pottery assembly minigame.

Changed paths:
  A engines/nancy/action/puzzle/assemblypuzzle.cpp
  A engines/nancy/action/puzzle/assemblypuzzle.h
    engines/nancy/action/arfactory.cpp
    engines/nancy/module.mk
    engines/nancy/renderobject.h


diff --git a/engines/nancy/action/arfactory.cpp b/engines/nancy/action/arfactory.cpp
index a56b3d2358c..a090c786cb2 100644
--- a/engines/nancy/action/arfactory.cpp
+++ b/engines/nancy/action/arfactory.cpp
@@ -30,6 +30,7 @@
 #include "engines/nancy/action/secondaryvideo.h"
 #include "engines/nancy/action/secondarymovie.h"
 
+#include "engines/nancy/action/puzzle/assemblypuzzle.h"
 #include "engines/nancy/action/puzzle/bombpuzzle.h"
 #include "engines/nancy/action/puzzle/collisionpuzzle.h"
 #include "engines/nancy/action/puzzle/leverpuzzle.h"
@@ -311,6 +312,8 @@ ActionRecord *ActionManager::createActionRecord(uint16 type, Common::SeekableRea
 		return new MouseLightPuzzle();
 	case 220:
 		return new TwoDialPuzzle();
+	case 222:
+		return new AssemblyPuzzle();
 	case 224:
 		return new OrderingPuzzle(OrderingPuzzle::kKeypadTerse);
 	default:
diff --git a/engines/nancy/action/puzzle/assemblypuzzle.cpp b/engines/nancy/action/puzzle/assemblypuzzle.cpp
new file mode 100644
index 00000000000..94ed2b009ec
--- /dev/null
+++ b/engines/nancy/action/puzzle/assemblypuzzle.cpp
@@ -0,0 +1,320 @@
+/* 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 <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#include "engines/nancy/nancy.h"
+#include "engines/nancy/graphics.h"
+#include "engines/nancy/resource.h"
+#include "engines/nancy/sound.h"
+#include "engines/nancy/input.h"
+#include "engines/nancy/util.h"
+
+#include "engines/nancy/state/scene.h"
+
+#include "engines/nancy/action/puzzle/assemblypuzzle.h"
+
+namespace Nancy {
+namespace Action {
+
+void AssemblyPuzzle::init() {
+	g_nancy->_resource->loadImage(_imageName, _image);
+	_image.setTransparentColor(_drawSurface.getTransparentColor());
+
+	for (uint i = 0; i < _pieces.size(); ++i) {
+		Piece &piece = _pieces[i];
+		piece.curRotation = 0;
+		piece._drawSurface.create(_image, piece.srcRects[piece.curRotation]);
+		piece.setVisible(true);
+		piece.setTransparent(true);
+		piece.moveTo(piece.placed ? piece.destRects[0] : piece.startRect);
+		piece.setZ(_z + i + _pieces.size());
+	}
+}
+
+void AssemblyPuzzle::registerGraphics() {
+	for (uint i = 0; i < _pieces.size(); ++i) {
+		_pieces[i].registerGraphics();
+	}
+}
+
+void AssemblyPuzzle::readData(Common::SeekableReadStream &stream) {
+	readFilename(stream, _imageName);
+
+	uint16 numPieces = stream.readUint16LE();
+	_height = stream.readUint16LE();
+
+	readRect(stream, _cwCursorDest);
+	readRect(stream, _ccwCursorDest);
+
+	_pieces.resize(numPieces);
+	for (uint i = 0; i < numPieces; ++i) {
+		Piece &piece = _pieces[i];
+		readRectArray(stream, piece.srcRects, 4);
+		readRectArray(stream, piece.destRects, 4);
+
+		readRect(stream, piece.startRect);
+
+		piece.correctRotation = stream.readUint16LE();
+		piece.layer = stream.readUint16LE();
+		piece.placed = stream.readUint16LE();
+	}
+	stream.skip((12 - numPieces) * 150);
+
+	_rotateSound.readNormal(stream);
+	_pickUpSound.readNormal(stream);
+	_placeDownSound.readNormal(stream);
+
+	_allowWrongPieceHotspot = stream.readUint16LE();
+
+	_wrongPieceSounds.resize(4);
+	_wrongPieceTexts.resize(4);
+
+	for (uint i = 0; i < 4; ++i) {
+		_wrongPieceSounds[i].readNormal(stream);
+	}
+
+	char buf[200];
+	for (uint i = 0; i < 4; ++i) {
+		stream.read(buf, 200);
+		assembleTextLine(buf, _wrongPieceTexts[i], 200);
+	}
+
+	_solveScene.readData(stream);
+	_solveSound.readNormal(stream);
+	stream.read(buf, 200);
+	assembleTextLine(buf, _solveText, 200);
+
+	_exitScene.readData(stream);
+	readRect(stream, _exitHotspot);
+}
+
+void AssemblyPuzzle::execute() {
+	switch (_state) {
+	case kBegin:
+		init();
+		registerGraphics();
+
+		g_nancy->_sound->loadSound(_rotateSound);
+		g_nancy->_sound->loadSound(_pickUpSound);
+		g_nancy->_sound->loadSound(_placeDownSound);
+		
+		_state = kRun;
+		// fall through
+	case kRun:
+		if (_layersAssembled != _height) {
+			return;
+		}
+
+		g_nancy->_sound->loadSound(_solveSound);
+		g_nancy->_sound->playSound(_solveSound);
+		NancySceneState.getTextbox().clear();
+		NancySceneState.getTextbox().addTextLine(_solveText);
+		NancySceneState.setEventFlag(_solveScene._flag);
+		_completed = true;
+
+		_state = kActionTrigger;
+		break;
+	case kActionTrigger:
+		if (g_nancy->_sound->isSoundPlaying(_solveSound)) {
+			return;
+		}
+
+		if (_completed) {
+			NancySceneState.changeScene(_solveScene._sceneChange);
+		} else {
+			_exitScene.execute();
+		}		
+
+		break;
+	}
+}
+
+void AssemblyPuzzle::handleInput(NancyInput &input) {
+	if (_state == kActionTrigger && _completed && g_nancy->_sound->isSoundPlaying(_solveSound)) {
+		return;
+	}
+	
+	if (_pickedUpPiece == -1 && NancySceneState.getViewport().convertViewportToScreen(_exitHotspot).contains(input.mousePos)) {
+		g_nancy->_cursorManager->setCursorType(g_nancy->_cursorManager->_puzzleExitCursor);
+
+		if (input.input & NancyInput::kLeftMouseButtonUp) {
+			_state = kActionTrigger;
+			_completed = false;
+		}
+		return;
+	}
+
+	if (_pickedUpPiece == -1 && NancySceneState.getViewport().convertViewportToScreen(_cwCursorDest).contains(input.mousePos)) {
+		g_nancy->_cursorManager->setCursorType(CursorManager::kRotateCW);
+
+		if (input.input & NancyInput::kLeftMouseButtonUp && !g_nancy->_sound->isSoundPlaying(_rotateSound)) {
+			g_nancy->_sound->playSound(_rotateSound);
+			rotateBase(false);
+			return;
+		}
+	}
+
+	if (_pickedUpPiece == -1 && NancySceneState.getViewport().convertViewportToScreen(_ccwCursorDest).contains(input.mousePos)) {
+		g_nancy->_cursorManager->setCursorType(CursorManager::kRotateCCW);
+
+		if (input.input & NancyInput::kLeftMouseButtonUp && !g_nancy->_sound->isSoundPlaying(_rotateSound)) {
+			g_nancy->_sound->playSound(_rotateSound);
+			rotateBase(true);
+			return;
+		}
+	}
+
+	if (_completed) {
+		return;
+	}
+
+	for (uint i = 0; i < _pieces.size(); ++i) {
+		if (NancySceneState.getViewport().convertViewportToScreen(_pieces[i].startRect).contains(input.mousePos)) {
+			if (_pickedUpPiece == -1 && _pieces[i].placed) {
+				return;
+			}
+
+			g_nancy->_cursorManager->setCursorType(CursorManager::kHotspot);
+
+			if (input.input & NancyInput::kLeftMouseButtonUp) {
+				if (_pickedUpPiece != -1) {
+					_pieces[_pickedUpPiece].putDown();
+					_pieces[_pickedUpPiece].moveTo(_pieces[_pickedUpPiece].startRect);
+					g_nancy->_sound->playSound(_placeDownSound);
+				}
+
+				if (_pickedUpPiece != (int)i && !_pieces[i].placed) {
+					// Clicked on another piece while holding, swap them
+					_pickedUpPiece = i;
+					_pieces[i].pickUp();
+					g_nancy->_sound->playSound(_pickUpSound);
+
+					for (uint j = 1; j < _pieces.size(); ++j) {
+						Piece &piece = _pieces[j];
+						if (!piece.placed && piece.getZOrder() > _pieces[i].getZOrder()) {
+							piece.setZ(piece.getZOrder() - 1);
+							piece.registerGraphics();
+						}
+					}
+					
+					_pieces[i].setZ(_z + _pieces.size() * 2);
+					_pieces[i].registerGraphics();
+				} else {
+					// Clicked the dest of the picked up piece, or an already placed one; simply put it down
+					_pickedUpPiece = -1;
+				}
+			}
+		}
+	}
+
+	if (_pickedUpPiece != -1) {
+		// Piece picked up
+		Piece &pickedUpPiece = _pieces[_pickedUpPiece];
+		pickedUpPiece.handleInput(input);
+
+		bool isWrong = _curRotation != pickedUpPiece.correctRotation;
+		bool otherIsPlaced = false;
+		for (uint i = 0; i < _pieces.size(); ++i) {
+			if (_pieces[i].placed && _pieces[i].curRotation == 0 && _pieces[i].layer - 1 == (int)_layersAssembled) {
+				otherIsPlaced = true;
+				break;
+			}
+		}
+
+		if (!otherIsPlaced && (!isWrong || (_allowWrongPieceHotspot && _layersAssembled + 1 == pickedUpPiece.layer))) {
+			if (NancySceneState.getViewport().convertViewportToScreen(pickedUpPiece.destRects[0]).contains(input.mousePos)) {
+				g_nancy->_cursorManager->setCursorType(CursorManager::kHotspot);
+
+				if (input.input & NancyInput::kLeftMouseButtonUp) {
+					if (!isWrong) {
+						pickedUpPiece.putDown();
+						pickedUpPiece.moveTo(pickedUpPiece.destRects[0]);
+						g_nancy->_sound->playSound(_placeDownSound);
+						pickedUpPiece.placed = true;
+						_pickedUpPiece = -1;
+
+						// Check for finished layer
+						uint placedOnLayer = 0;
+						for (uint i = 0; i < _pieces.size(); ++i) {
+							if (_pieces[i].layer == _layersAssembled + 1 && _pieces[i].placed) {
+								++placedOnLayer;
+							}
+						}
+
+						if (placedOnLayer == 4) {
+							++_layersAssembled;
+						}
+					} else if (_allowWrongPieceHotspot) {
+						// Wrong place, play a sound
+						g_nancy->_sound->loadSound(_wrongPieceSounds[_curRotation]);
+						g_nancy->_sound->playSound(_wrongPieceSounds[_curRotation]);
+						if (!_wrongPieceTexts[_curRotation].empty()) {
+							NancySceneState.getTextbox().addTextLine(_wrongPieceTexts[_curRotation], 4000); // check
+						}
+
+						// and put down the piece
+						pickedUpPiece.putDown();
+						pickedUpPiece.moveTo(pickedUpPiece.startRect);
+						_pickedUpPiece = -1;
+					}
+				}
+			}
+		}
+	}
+}
+
+void AssemblyPuzzle::rotateBase(bool ccw) {
+	// _curRotation moves in the opposite direction to pieces' rotations
+	_curRotation += ccw ? 1 : -1;
+	if (_curRotation < 0) {
+		_curRotation = 3;
+	} else if (_curRotation > 3) {
+		_curRotation = 0;
+	}
+
+	for (uint i = 0; i < _pieces.size(); ++i) {
+		Piece &piece = _pieces[i];
+		if (piece.placed) {
+			piece.curRotation += ccw ? -1 : 1;
+			if (piece.curRotation < 0) {
+				piece.curRotation = 3;
+			} else if (piece.curRotation > 3) {
+				piece.curRotation = 0;
+			}
+
+			uint base = 0;
+			if (piece.curRotation == 0) {
+				base = 2;
+			} else if (piece.curRotation == 1 || piece.curRotation == 3) {
+				base = 1;
+			}
+			
+			piece.setZ(_z + base + 4 * (piece.layer - 1));
+			piece.registerGraphics();
+
+			piece.moveTo(piece.destRects[piece.curRotation]);
+			piece._drawSurface.create(_image, piece.srcRects[piece.curRotation]);
+			piece.setTransparent(true);
+		}
+	}
+}
+
+} // End of namespace Action
+} // End of namespace Nancy
diff --git a/engines/nancy/action/puzzle/assemblypuzzle.h b/engines/nancy/action/puzzle/assemblypuzzle.h
new file mode 100644
index 00000000000..c03d27c3771
--- /dev/null
+++ b/engines/nancy/action/puzzle/assemblypuzzle.h
@@ -0,0 +1,99 @@
+/* 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 <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#ifndef NANCY_ACTION_ASSEMBLYPUZZLE_H
+#define NANCY_ACTION_ASSEMBLYPUZZLE_H
+
+#include "engines/nancy/action/actionrecord.h"
+#include "engines/nancy/misc/mousefollow.h"
+
+namespace Nancy {
+namespace Action {
+
+// Minigame where the player is provided with the broken pieces of something
+// (a piece of pottery in nancy6), and has to assemble it.
+class AssemblyPuzzle : public RenderActionRecord {
+public:
+	AssemblyPuzzle() : RenderActionRecord(7) {}
+	virtual ~AssemblyPuzzle() {}
+
+	void init() override;
+	void registerGraphics() override;
+
+	void readData(Common::SeekableReadStream &stream) override;
+	void execute() override;
+	void handleInput(NancyInput &input) override;
+
+protected:
+	Common::String getRecordTypeName() const override { return "AssemblyPuzzle"; };
+	bool isViewportRelative() const override { return true; }
+
+	void rotateBase(bool ccw);
+
+	struct Piece : Misc::MouseFollowObject {
+		Common::Array<Common::Rect> srcRects;
+		Common::Array<Common::Rect> destRects;
+
+		Common::Rect startRect;
+		uint16 correctRotation = 0;
+		uint16 layer = 0;
+		bool placed = false;
+
+		int curRotation = 0;
+	};
+
+	Common::String _imageName;
+
+	uint16 _height = 0;
+
+	Common::Rect _cwCursorDest;
+	Common::Rect _ccwCursorDest;
+
+	Common::Array<Piece> _pieces;
+
+	SoundDescription _rotateSound;
+	SoundDescription _pickUpSound;	
+	SoundDescription _placeDownSound;
+
+	bool _allowWrongPieceHotspot = false;
+
+	Common::Array<SoundDescription> _wrongPieceSounds;
+	Common::Array<Common::String> _wrongPieceTexts;
+
+	SceneChangeWithFlag _solveScene; // has 9999 in nancy6, so the puzzle doesn't auto-exit
+	SoundDescription _solveSound;
+	Common::String _solveText;
+
+	SceneChangeWithFlag _exitScene;
+	Common::Rect _exitHotspot;
+
+	Graphics::ManagedSurface _image;
+
+	int _pickedUpPiece = -1;
+	int _curRotation = 0;
+	uint _layersAssembled = 0;
+	bool _completed = false;
+};
+
+} // End of namespace Action
+} // End of namespace Nancy
+
+#endif // NANCY_ACTION_ASSEMBLYPUZZLE_H
diff --git a/engines/nancy/module.mk b/engines/nancy/module.mk
index 906dfc9786a..a5cdc91c4e8 100644
--- a/engines/nancy/module.mk
+++ b/engines/nancy/module.mk
@@ -13,6 +13,7 @@ MODULE_OBJS = \
   action/overlay.o \
   action/secondarymovie.o \
   action/secondaryvideo.o \
+  action/puzzle/assemblypuzzle.o \
   action/puzzle/bombpuzzle.o \
   action/puzzle/collisionpuzzle.o \
   action/puzzle/leverpuzzle.o \
diff --git a/engines/nancy/renderobject.h b/engines/nancy/renderobject.h
index f03a24b8d92..4ae27fe9fbc 100644
--- a/engines/nancy/renderobject.h
+++ b/engines/nancy/renderobject.h
@@ -65,14 +65,11 @@ public:
 	Common::Rect convertToScreen(const Common::Rect &rect) const;
 
 	Common::Rect getBounds() const { return Common::Rect(_screenPosition.width(), _screenPosition.height()); }
+	uint16 getZOrder() const { return _z; }
 
 	Graphics::ManagedSurface _drawSurface;
 
 protected:
-	// Z order and blit type are extracted directly from the corresponding
-	// ZRenderStruct from the original engine
-	uint16 getZOrder() const { return _z; }
-
 	// Needed for proper handling of objects inside the viewport
 	virtual bool isViewportRelative() const { return false; }
 


Commit: 489b9c696d7e09374a2cc7cb4cc253f9dfc3a6e5
    https://github.com/scummvm/scummvm/commit/489b9c696d7e09374a2cc7cb4cc253f9dfc3a6e5
Author: Kaloyan Chehlarski (strahy at outlook.com)
Date: 2023-10-17T18:31:08+03:00

Commit Message:
NANCY: Fix Coverity issues

Changed paths:
    engines/nancy/action/arfactory.cpp
    engines/nancy/action/secondarymovie.cpp
    engines/nancy/misc/specialeffect.cpp


diff --git a/engines/nancy/action/arfactory.cpp b/engines/nancy/action/arfactory.cpp
index a090c786cb2..03d835413c3 100644
--- a/engines/nancy/action/arfactory.cpp
+++ b/engines/nancy/action/arfactory.cpp
@@ -144,7 +144,7 @@ ActionRecord *ActionManager::createActionRecord(uint16 type, Common::SeekableRea
 	case 56:
 		if (g_nancy->getGameType() <= kGameTypeNancy6) {
 			return new ConversationVideo();
-		} else if (g_nancy->getGameType() >= kGameTypeNancy7) {
+		} else {
 			return new OverlayAnimTerse();
 		}
 		return nullptr;
diff --git a/engines/nancy/action/secondarymovie.cpp b/engines/nancy/action/secondarymovie.cpp
index b99c091c440..22f8ef1115f 100644
--- a/engines/nancy/action/secondarymovie.cpp
+++ b/engines/nancy/action/secondarymovie.cpp
@@ -95,7 +95,7 @@ void PlaySecondaryMovie::init() {
 	if (!_decoder) {
 		if (_videoType == kVideoPlaytypeAVF) {
 			_decoder = new AVFDecoder();
-		} else if (_videoType == kVideoPlaytypeBink) {
+		} else {
 			_decoder = new Video::BinkDecoder();
 		}
 	}
diff --git a/engines/nancy/misc/specialeffect.cpp b/engines/nancy/misc/specialeffect.cpp
index b039824ba19..248106bb498 100644
--- a/engines/nancy/misc/specialeffect.cpp
+++ b/engines/nancy/misc/specialeffect.cpp
@@ -138,10 +138,10 @@ void SpecialEffect::afterSceneChange() {
 }
 
 bool SpecialEffect::isDone() const {
-	if (_type != kCrossDissolve) {
+	if (_type == kBlackout) {
 		return g_nancy->getTotalPlayTime() > _fadeToBlackEndTime;
 	} else {
-		bool canFinish = _type == kThroughBlack ? _throughBlackStarted2nd : true;
+		bool canFinish = (_type == kThroughBlack) ? _throughBlackStarted2nd : true;
 		return _totalTime ? ((g_nancy->getTotalPlayTime() > _startTime + _totalTime) && (_currentFrame != 0) && canFinish) : (_currentFrame >= (int)_numFrames);
 	}
 }


Commit: cf188ca8fb678080ef5b311b094ca787e0a2ff60
    https://github.com/scummvm/scummvm/commit/cf188ca8fb678080ef5b311b094ca787e0a2ff60
Author: Kaloyan Chehlarski (strahy at outlook.com)
Date: 2023-10-17T18:31:08+03:00

Commit Message:
NANCY: Implement CubePuzzle

Implemented the record type responsible for nancy6's
cube assembly minigame (not to be confused with the
pottery assembly one, which uses a different AR type)

Changed paths:
  A engines/nancy/action/puzzle/cubepuzzle.cpp
  A engines/nancy/action/puzzle/cubepuzzle.h
    engines/nancy/action/arfactory.cpp
    engines/nancy/module.mk


diff --git a/engines/nancy/action/arfactory.cpp b/engines/nancy/action/arfactory.cpp
index 03d835413c3..6f2bf836768 100644
--- a/engines/nancy/action/arfactory.cpp
+++ b/engines/nancy/action/arfactory.cpp
@@ -33,6 +33,7 @@
 #include "engines/nancy/action/puzzle/assemblypuzzle.h"
 #include "engines/nancy/action/puzzle/bombpuzzle.h"
 #include "engines/nancy/action/puzzle/collisionpuzzle.h"
+#include "engines/nancy/action/puzzle/cubepuzzle.h"
 #include "engines/nancy/action/puzzle/leverpuzzle.h"
 #include "engines/nancy/action/puzzle/mazechasepuzzle.h"
 #include "engines/nancy/action/puzzle/mouselightpuzzle.h"
@@ -314,6 +315,8 @@ ActionRecord *ActionManager::createActionRecord(uint16 type, Common::SeekableRea
 		return new TwoDialPuzzle();
 	case 222:
 		return new AssemblyPuzzle();
+	case 223:
+		return new CubePuzzle();
 	case 224:
 		return new OrderingPuzzle(OrderingPuzzle::kKeypadTerse);
 	default:
diff --git a/engines/nancy/action/puzzle/cubepuzzle.cpp b/engines/nancy/action/puzzle/cubepuzzle.cpp
new file mode 100644
index 00000000000..75c3dcd8d11
--- /dev/null
+++ b/engines/nancy/action/puzzle/cubepuzzle.cpp
@@ -0,0 +1,322 @@
+/* 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 <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#include "engines/nancy/nancy.h"
+#include "engines/nancy/graphics.h"
+#include "engines/nancy/resource.h"
+#include "engines/nancy/sound.h"
+#include "engines/nancy/input.h"
+#include "engines/nancy/util.h"
+
+#include "engines/nancy/state/scene.h"
+
+#include "engines/nancy/action/puzzle/cubepuzzle.h"
+
+namespace Nancy {
+namespace Action {
+
+void CubePuzzle::init() {
+	Common::Rect screenBounds = NancySceneState.getViewport().getBounds();
+	_drawSurface.create(screenBounds.width(), screenBounds.height(), g_nancy->_graphicsManager->getInputPixelFormat());
+	_drawSurface.clear(g_nancy->_graphicsManager->getTransColor());
+	setTransparent(true);
+	setVisible(true);
+	moveTo(screenBounds);
+
+	g_nancy->_resource->loadImage(_imageName, _image);
+	_image.setTransparentColor(_drawSurface.getTransparentColor());
+
+	for (uint i = 0; i < 5; ++i) {
+		_drawSurface.blitFrom(_image, _pieceSrcs[i], _pieceDests[i]);
+	}
+
+	_placedPieces.resize(5, false);
+	_curRotation = _startRotation;
+	_drawSurface.blitFrom(_image, _placedSrcs[_curRotation][0], _placedDest);
+}
+
+void CubePuzzle::registerGraphics() {
+	_curPiece.registerGraphics();
+	RenderActionRecord::registerGraphics();
+}
+
+void CubePuzzle::readData(Common::SeekableReadStream &stream) {
+	readFilename(stream, _imageName);
+
+	readRect(stream, _cwCursorDest);
+	readRect(stream, _ccwCursorDest);
+	
+	readRect(stream, _placedDest);
+
+	// four pieces on the side, 1 on top
+	_pieceSrcs.resize(5);
+	_pieceDests.resize(5);
+	for (uint i = 0; i < 5; ++i) {
+		readRect(stream, _pieceSrcs[i]);
+		readRect(stream, _pieceDests[i]);
+	}
+
+	_placedSrcs.resize(4);
+	for (uint i = 0; i < 4; ++i) {
+		readRectArray(stream, _placedSrcs[i], 9);
+	}
+
+	_startRotation = stream.readUint16LE();
+
+	_rotateSound.readNormal(stream);
+	_pickUpSound.readNormal(stream);
+	_placeDownSound.readNormal(stream);
+
+	_solveSceneIDs.resize(4);
+	for (uint i = 0; i < 3; ++i) {
+		_solveSceneIDs[i] = stream.readUint16LE();
+	}
+	_solveScene.readData(stream);
+	_solveSceneIDs[3] = _solveScene._sceneChange.sceneID;
+	_solveSound.readNormal(stream);
+
+	_exitScene.readData(stream);
+	readRect(stream, _exitHotspot);
+}
+
+void CubePuzzle::execute() {
+	switch (_state) {
+	case kBegin:
+		init();
+		registerGraphics();
+
+		g_nancy->_sound->loadSound(_rotateSound);
+		g_nancy->_sound->loadSound(_pickUpSound);
+		g_nancy->_sound->loadSound(_placeDownSound);
+		
+		_state = kRun;
+		// fall through
+	case kRun:
+		for (uint i = 0; i < 5; ++i) {
+			if (!_placedPieces[i]) {
+				return;
+			}	
+		}
+
+		g_nancy->_sound->loadSound(_solveSound);
+		g_nancy->_sound->playSound(_solveSound);
+		NancySceneState.setEventFlag(_solveScene._flag);
+		_completed = true;
+
+		_state = kActionTrigger;
+		break;
+	case kActionTrigger:
+		if (g_nancy->_sound->isSoundPlaying(_solveSound)) {
+			return;
+		}
+
+		if (_completed) {
+			_solveScene._sceneChange.sceneID = _solveSceneIDs[_curRotation];
+			NancySceneState.changeScene(_solveScene._sceneChange);
+		} else {
+			_exitScene.execute();
+		}
+
+		g_nancy->_sound->stopSound(_solveSound);
+		g_nancy->_sound->stopSound(_rotateSound);
+		g_nancy->_sound->stopSound(_pickUpSound);
+		g_nancy->_sound->stopSound(_placeDownSound);
+
+		break;
+	}
+}
+
+void CubePuzzle::handleInput(NancyInput &input) {
+	if (_state != kRun) {
+		return;
+	}
+
+	if (_pickedUpPiece == -1 && NancySceneState.getViewport().convertViewportToScreen(_exitHotspot).contains(input.mousePos)) {
+		g_nancy->_cursorManager->setCursorType(g_nancy->_cursorManager->_puzzleExitCursor);
+
+		if (input.input & NancyInput::kLeftMouseButtonUp) {
+			_state = kActionTrigger;
+			_completed = false;
+		}
+		return;
+	}
+
+	if (_pickedUpPiece == -1 && NancySceneState.getViewport().convertViewportToScreen(_cwCursorDest).contains(input.mousePos)) {
+		g_nancy->_cursorManager->setCursorType(CursorManager::kRotateCW);
+
+		if (input.input & NancyInput::kLeftMouseButtonUp && !g_nancy->_sound->isSoundPlaying(_rotateSound)) {
+			g_nancy->_sound->playSound(_rotateSound);
+			rotateBase(-1);
+			return;
+		}
+	}
+
+	if (_pickedUpPiece == -1 && NancySceneState.getViewport().convertViewportToScreen(_ccwCursorDest).contains(input.mousePos)) {
+		g_nancy->_cursorManager->setCursorType(CursorManager::kRotateCCW);
+
+		if (input.input & NancyInput::kLeftMouseButtonUp && !g_nancy->_sound->isSoundPlaying(_rotateSound)) {
+			g_nancy->_sound->playSound(_rotateSound);
+			rotateBase(1);
+			return;
+		}
+	}
+
+	for (uint i = 0; i < 5; ++i) {
+		if (NancySceneState.getViewport().convertViewportToScreen(_pieceDests[i]).contains(input.mousePos)) {
+			if (_pickedUpPiece == -1 && _placedPieces[i]) {
+				return;
+			}
+			
+			g_nancy->_cursorManager->setCursorType(CursorManager::kHotspot);
+
+			if (input.input & NancyInput::kLeftMouseButtonUp) {
+				if (_pickedUpPiece != -1) {
+					_curPiece.putDown();
+					_curPiece.setVisible(false);
+					g_nancy->_sound->playSound(_placeDownSound);
+
+					_drawSurface.fillRect(_pieceDests[_pickedUpPiece], _drawSurface.getTransparentColor());
+					_drawSurface.blitFrom(_image, _pieceSrcs[_pickedUpPiece], _pieceDests[_pickedUpPiece]);
+					_needsRedraw = true;
+				}
+
+				if (_pickedUpPiece != (int)i && !_placedPieces[i]) {
+					// Clicked on another piece while holding, swap them
+					_drawSurface.fillRect(_pieceDests[i], _drawSurface.getTransparentColor());
+
+					if (_pickedUpPiece != -1) {
+						_drawSurface.fillRect(_pieceDests[_pickedUpPiece], _drawSurface.getTransparentColor());
+						_drawSurface.blitFrom(_image, _pieceSrcs[_pickedUpPiece], _pieceDests[_pickedUpPiece]);
+					}
+
+					_needsRedraw = true;
+
+					_pickedUpPiece = i;
+					g_nancy->_sound->playSound(_pickUpSound);
+
+					_curPiece.pickUp();
+					_curPiece.setVisible(true);
+					_curPiece._drawSurface.create(_image, _pieceSrcs[i]);
+					_curPiece.setVisible(true);
+				} else {
+					// Clicked the dest of the picked up piece, or an already placed one; simply put it down
+					_pickedUpPiece = -1;
+				}
+			}
+		}
+	}
+
+	if (_pickedUpPiece != -1) {
+		// Piece picked up
+		_curPiece.handleInput(input);
+
+		bool canPlace = false;
+
+		if (_pickedUpPiece == 0) {
+			// Top piece can ble placed only in the correct rotation (which is 0)
+			canPlace = _curRotation == 0;
+		} else {
+			canPlace = _curRotation + 1 == _pickedUpPiece;
+		}
+
+		if (canPlace) {
+			if (NancySceneState.getViewport().convertViewportToScreen(_placedDest).contains(input.mousePos)) {
+				g_nancy->_cursorManager->setCursorType(CursorManager::kHotspot);
+
+				if (input.input & NancyInput::kLeftMouseButtonUp) {
+					_placedPieces[_pickedUpPiece] = true;
+					_curPiece.putDown();
+					_curPiece.setVisible(false);
+					_pickedUpPiece = -1;
+					g_nancy->_sound->playSound(_placeDownSound);
+					rotateBase(0);
+				}
+			}
+		}
+	}
+}
+
+void CubePuzzle::rotateBase(int dir) {
+	_drawSurface.fillRect(_placedDest, _drawSurface.getTransparentColor());
+
+	_curRotation += dir;
+	if (_curRotation < 0) {
+		_curRotation = 3;
+	} else if (_curRotation > 3) {
+		_curRotation = 0;
+	}
+
+	uint srcSelect = 0;
+
+	if (_placedPieces[0]) {
+		if (_placedPieces[_curRotation + 1]) {
+			// Front piece is in, other checks are unnecessary
+			srcSelect = 2;
+		} else {
+			int leftIndex = _curRotation + 1;
+			if (leftIndex > 3) {
+				leftIndex = 0;
+			}
+
+			int rightIndex = _curRotation - 1;
+			if (rightIndex < 0) {
+				rightIndex = 3;
+			}
+
+			int backIndex = (_curRotation + 2) % 4;
+
+			if (_placedPieces[leftIndex + 1]) {
+				// Left piece is in, check for right or back
+				if (_placedPieces[rightIndex + 1]) {
+					// Draw left & right piece
+					srcSelect = 8;
+				} else if (_placedPieces[backIndex]) {
+					// Draw left & back piece
+					srcSelect = 6;
+				} else {
+					// Draw left piece only
+					srcSelect = 3;
+				}
+			} else if (_placedPieces[rightIndex + 1]) {
+				// Right piece is in, check for back
+				if (_placedPieces[backIndex + 1]) {
+					// Draw right & back piece
+					srcSelect = 7;
+				} else {
+					// Draw right piece only
+					srcSelect = 4;
+				}
+			} else if (_placedPieces[backIndex + 1]) {
+				// Draw back piece only
+				srcSelect = 5;
+			} else {
+				// Draw top piece only
+				srcSelect = 1;
+			}
+		}
+	}
+
+	_drawSurface.blitFrom(_image, _placedSrcs[_curRotation][srcSelect], _placedDest);
+	_needsRedraw = true;
+}
+
+} // End of namespace Action
+} // End of namespace Nancy
diff --git a/engines/nancy/action/puzzle/cubepuzzle.h b/engines/nancy/action/puzzle/cubepuzzle.h
new file mode 100644
index 00000000000..1a00d0ea872
--- /dev/null
+++ b/engines/nancy/action/puzzle/cubepuzzle.h
@@ -0,0 +1,101 @@
+/* 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 <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#ifndef NANCY_ACTION_CUBEPUZZLE_H
+#define NANCY_ACTION_CUBEPUZZLE_H
+
+#include "engines/nancy/action/actionrecord.h"
+#include "engines/nancy/misc/mousefollow.h"
+
+namespace Nancy {
+namespace Action {
+
+// Similar idea to AssemblyPuzzle; the player is provided with broken pieces of
+// a cube, and has to assemble it back together. However, the data is completely
+// different to AssemblyPuzzle's, so we need separate implementations.
+class CubePuzzle : public RenderActionRecord {
+public:
+	CubePuzzle() : RenderActionRecord(7) {}
+	virtual ~CubePuzzle() {}
+
+	void init() override;
+	void registerGraphics() override;
+
+	void readData(Common::SeekableReadStream &stream) override;
+	void execute() override;
+	void handleInput(NancyInput &input) override;
+
+protected:
+	Common::String getRecordTypeName() const override { return "CubePuzzle"; };
+	bool isViewportRelative() const override { return true; }
+
+	void rotateBase(int dir);
+
+	Common::String _imageName;
+
+	Common::Rect _cwCursorDest;
+	Common::Rect _ccwCursorDest;
+
+	Common::Rect _placedDest;
+
+	Common::Array<Common::Rect> _pieceSrcs; // Used only when not placed
+	Common::Array<Common::Rect> _pieceDests; // Used only when not placed
+
+	// 4 arrays with 9 rects each; every rect corresponds to a specific permutation of placed pieces:
+	// - bottom piece only (start state)
+	// - bottom & top piece
+	// - front piece placed
+	// - left piece placed
+	// - back piece placed
+	// - right piece placed
+	// - left + back piece placed
+	// - right + back piece placed
+	// - left + right piece placed
+	Common::Array<Common::Array<Common::Rect>> _placedSrcs;
+
+	uint16 _startRotation = 0;
+
+	SoundDescription _rotateSound;
+	SoundDescription _pickUpSound;	
+	SoundDescription _placeDownSound;
+
+	// Multiple solve scenes, one for each cube orientation
+	Common::Array<uint> _solveSceneIDs;
+	SceneChangeWithFlag _solveScene;
+	SoundDescription _solveSound;
+
+	SceneChangeWithFlag _exitScene;
+	Common::Rect _exitHotspot;
+
+	Graphics::ManagedSurface _image;
+	Misc::MouseFollowObject _curPiece;
+
+	Common::Array<bool> _placedPieces;
+	int _pickedUpPiece = -1;
+
+	int _curRotation = 0;
+	bool _completed = false;
+};
+
+} // End of namespace Action
+} // End of namespace Nancy
+
+#endif // NANCY_ACTION_CUBEPUZZLE_H
diff --git a/engines/nancy/module.mk b/engines/nancy/module.mk
index a5cdc91c4e8..d305ab4f348 100644
--- a/engines/nancy/module.mk
+++ b/engines/nancy/module.mk
@@ -16,6 +16,7 @@ MODULE_OBJS = \
   action/puzzle/assemblypuzzle.o \
   action/puzzle/bombpuzzle.o \
   action/puzzle/collisionpuzzle.o \
+  action/puzzle/cubepuzzle.o \
   action/puzzle/leverpuzzle.o \
   action/puzzle/mazechasepuzzle.o \
   action/puzzle/mouselightpuzzle.o \




More information about the Scummvm-git-logs mailing list