[Scummvm-git-logs] scummvm master -> 1bab29825a9ddd42e1a4f0913c6e4533c38b18e4

fracturehill noreply at scummvm.org
Fri Apr 14 19:41:33 UTC 2023


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

Summary:
8ca65cea3b NANCY: Implement item highlighting
a25540441b NANCY: Load missing CIF info data
9e25e1c7a0 NANCY: Don't assume the end line token is last
76310e9ef9 NANCY: Implement nancy2 Conversation ARs
a67dbb9e09 NANCY: Implement textbox hover highlighting
1bab29825a NANCY: ConversationCel fixes


Commit: 8ca65cea3b83dc8e4b27827d8460bef987a7cf05
    https://github.com/scummvm/scummvm/commit/8ca65cea3b83dc8e4b27827d8460bef987a7cf05
Author: Kaloyan Chehlarski (strahy at outlook.com)
Date: 2023-04-14T22:39:09+03:00

Commit Message:
NANCY: Implement item highlighting

Added code to highlight items when they're hovered inside
the inventory box.

Changed paths:
    engines/nancy/ui/inventorybox.cpp
    engines/nancy/ui/inventorybox.h


diff --git a/engines/nancy/ui/inventorybox.cpp b/engines/nancy/ui/inventorybox.cpp
index ec358b5e1fa..286fb21ae78 100644
--- a/engines/nancy/ui/inventorybox.cpp
+++ b/engines/nancy/ui/inventorybox.cpp
@@ -39,7 +39,8 @@ namespace UI {
 InventoryBox::InventoryBox() :
 		RenderObject(6),
 		_scrollbar(nullptr),
-		_scrollbarPos(0) {}
+		_scrollbarPos(0),
+		_highlightedHotspot(-1) {}
 
 InventoryBox::~InventoryBox() {
 	_fullInventorySurface.free();
@@ -99,6 +100,8 @@ void InventoryBox::handleInput(NancyInput &input) {
 		_scrollbar->handleInput(input);
 	}
 
+	int hoveredHotspot = -1;
+
 	for (uint i = 0; i < 4; ++i) {
 		if (_itemHotspots[i].hotspot.contains(input.mousePos)) {
 			if (NancySceneState.getHeldItem() != -1) {
@@ -109,17 +112,36 @@ void InventoryBox::handleInput(NancyInput &input) {
 				}
 			} else if (_itemHotspots[i].itemID != -1) {
 				g_nancy->_cursorManager->setCursorType(CursorManager::kHotspotArrow);
+				
+				hoveredHotspot = i;
+
 				if (input.input & NancyInput::kLeftMouseButtonUp) {
 					NancySceneState.removeItemFromInventory(_itemHotspots[i].itemID);
+					_highlightedHotspot = -1;
+					hoveredHotspot = -1;
 					g_nancy->_sound->playSound("GLOB");
 				}
 			}
 			break;
 		}
 	}
+
+	if (_highlightedHotspot != hoveredHotspot) {
+		if (_highlightedHotspot != -1) {
+			// Un-highlight last hovered item
+			drawItemInSlot(_itemHotspots[_highlightedHotspot].itemID, _itemHotspots[_highlightedHotspot].itemOrder, false);
+			_highlightedHotspot = -1;
+		}
+
+		if (hoveredHotspot != -1) {
+			// Highlight hovered item
+			drawItemInSlot(_itemHotspots[hoveredHotspot].itemID, _itemHotspots[hoveredHotspot].itemOrder, true);
+			_highlightedHotspot = hoveredHotspot;
+		}
+	}
 }
 
-void InventoryBox::addItem(int16 itemID) {
+void InventoryBox::addItem(const int16 itemID) {
 	if (_order.size() == 0) {
 		// Adds first item, start curtains animation
 		_curtains.setOpen(true);
@@ -132,7 +154,7 @@ void InventoryBox::addItem(int16 itemID) {
 	onReorder();
 }
 
-void InventoryBox::removeItem(int16 itemID) {
+void InventoryBox::removeItem(const int16 itemID) {
 	for (auto &i : _order) {
 		if (i == itemID) {
 			_order.erase(&i);
@@ -147,13 +169,7 @@ void InventoryBox::onReorder() {
 
 	_fullInventorySurface.clear();
 	for (uint i = 0; i < _order.size(); ++i) {
-		Common::Rect dest;
-		dest.setWidth(_screenPosition.width() / 2);
-		dest.setHeight(_screenPosition.height() / 2);
-		dest.moveTo((i % 2) * dest.width(), (i / 2) * dest.height());
-		Common::Point destPoint = Common::Point (dest.left, dest.top);
-
-		_fullInventorySurface.blitFrom(_iconsSurface, g_nancy->_inventoryData->itemDescriptions[_order[i]].sourceRect, destPoint);
+		drawItemInSlot(_order[i], i);
 	}
 
 	if (_order.size() > 0) {
@@ -165,16 +181,31 @@ void InventoryBox::onReorder() {
 	_needsRedraw = true;
 }
 
-void InventoryBox::setHotspots(uint pageNr) {
+void InventoryBox::setHotspots(const uint pageNr) {
 	for (uint i = 0; i < 4; ++i) {
 		if (i + pageNr * 4 < _order.size()) {
 			_itemHotspots[i].itemID = _order[i + pageNr * 4];
+			_itemHotspots[i].itemOrder = i + pageNr * 4;
 		} else {
 			_itemHotspots[i].itemID = -1;
+			_itemHotspots[i].itemOrder = -1;
 		}
 	}
 }
 
+void InventoryBox::drawItemInSlot(const uint itemID, const uint slotID, const bool highlighted) {
+	auto &item = g_nancy->_inventoryData->itemDescriptions[itemID];
+	Common::Rect dest;
+
+	dest.setWidth(_screenPosition.width() / 2);
+	dest.setHeight(_screenPosition.height() / 2);
+	dest.moveTo((slotID % 2) * dest.width(), (slotID / 2) * dest.height());
+	Common::Point destPoint = Common::Point (dest.left, dest.top);
+
+	_fullInventorySurface.blitFrom(_iconsSurface, highlighted ? item.highlightedSourceRect : item.sourceRect, destPoint);
+	_needsRedraw = true;
+}
+
 void InventoryBox::onScrollbarMove() {
 	float scrollPos = _scrollbar->getPos();
 
diff --git a/engines/nancy/ui/inventorybox.h b/engines/nancy/ui/inventorybox.h
index 5b8b4d71615..ffca1b2313e 100644
--- a/engines/nancy/ui/inventorybox.h
+++ b/engines/nancy/ui/inventorybox.h
@@ -61,11 +61,12 @@ public:
 
 private:
 	// These are private since they should only be called from Scene
-	void addItem(int16 itemID);
-	void removeItem(int16 itemID);
+	void addItem(const int16 itemID);
+	void removeItem(const int16 itemID);
 
 	void onReorder();
-	void setHotspots(uint pageNr);
+	void setHotspots(const uint pageNr);
+	void drawItemInSlot(const uint itemID, const uint slotID, const bool highlighted = false);
 
 	class Curtains : public RenderObject {
 	public:
@@ -91,6 +92,7 @@ private:
 
 	struct ItemHotspot {
 		int16 itemID = -1;
+		int itemOrder = -1; // index of the item into the _order array
 		Common::Rect hotspot; // in screen coordinates
 	};
 
@@ -104,6 +106,7 @@ private:
 
 	Common::Array<int16> _order;
 	ItemHotspot _itemHotspots[4];
+	int _highlightedHotspot;
 };
 
 } // End of namespace UI


Commit: a25540441b0e846f3024bfef141e9550c103798f
    https://github.com/scummvm/scummvm/commit/a25540441b0e846f3024bfef141e9550c103798f
Author: Kaloyan Chehlarski (strahy at outlook.com)
Date: 2023-04-14T22:39:09+03:00

Commit Message:
NANCY: Load missing CIF info data

The src and dest rects that were previously skipped over
when reading CIF info are now loaded and passed onto
loadImage() callers. They get used in the ConversationCel
action record type introduced in nancy2.

Changed paths:
    engines/nancy/resource.cpp
    engines/nancy/resource.h


diff --git a/engines/nancy/resource.cpp b/engines/nancy/resource.cpp
index 0f8f6d134d5..b8af481f200 100644
--- a/engines/nancy/resource.cpp
+++ b/engines/nancy/resource.cpp
@@ -27,6 +27,7 @@
 #include "engines/nancy/resource.h"
 #include "engines/nancy/decompress.h"
 #include "engines/nancy/graphics.h"
+#include "engines/nancy/util.h"
 
 namespace Nancy {
 
@@ -122,7 +123,8 @@ protected:
 };
 
 void CifFile21::readCifInfo(Common::File &f) {
-	f.skip(32);
+	readRect(f, _cifInfo.src);
+	readRect(f, _cifInfo.dest);
 	readCifInfo20(f, _cifInfo);
 }
 
@@ -415,7 +417,8 @@ void CifTree21::readCifInfo(Common::File &f, CifInfoChain &chain) {
 		chain.next = f.readUint16LE();
 	}
 
-	f.skip(32); // TODO
+	readRect(f, info.src);
+	readRect(f, info.dest);
 
 	readCifInfo20(f, info, (_hasOffsetFirst ? nullptr : &chain.dataOffset));
 
@@ -745,13 +748,19 @@ byte *ResourceManager::loadData(const Common::String &name, uint &size) {
 	return buf;
 }
 
-bool ResourceManager::loadImage(const Common::String &name, Graphics::Surface &surf) {
+bool ResourceManager::loadImage(const Common::String &name, Graphics::Surface &surf, const Common::String treeName, Common::Rect *outSrc, Common::Rect *outDest) {
 	CifInfo info;
 	surf.free();
 
-	byte *buf = getCifData(name, info);
+	byte *buf = nullptr;
 
-	if (!buf) {
+	if (treeName.size()) {
+		buf = getCifData(treeName, name, info);
+	} else {
+		buf = getCifData(name, info);
+	}
+
+	if (!buf && treeName.size() > 0) {
 		// Couldn't find image in a cif tree, try to open a .bmp file
 		// This is used by The Vampire Diaries
 		Common::File f;
@@ -780,6 +789,14 @@ bool ResourceManager::loadImage(const Common::String &name, Graphics::Surface &s
 		}
 	}
 
+	if (outSrc) {
+		*outSrc = info.src;
+	}
+
+	if (outDest) {
+		*outDest = info.dest;
+	}
+
 	surf.w = info.width;
 	surf.h = info.height;
 	surf.pitch = info.pitch;
@@ -788,14 +805,20 @@ bool ResourceManager::loadImage(const Common::String &name, Graphics::Surface &s
 	return true;
 }
 
-bool ResourceManager::loadImage(const Common::String &name, Graphics::ManagedSurface &surf) {
+bool ResourceManager::loadImage(const Common::String &name, Graphics::ManagedSurface &surf, const Common::String treeName, Common::Rect *outSrc, Common::Rect *outDest) {
 	CifInfo info;
 	bool loadedFromBitmapFile = false;
 	surf.free();
 
-	byte *buf = getCifData(name, info);
+	byte *buf = nullptr;
 
-	if (!buf) {
+	if (treeName.size()) {
+		buf = getCifData(treeName, name, info);
+	} else {
+		buf = getCifData(name, info);
+	}
+
+	if (!buf && treeName.size() > 0) {
 		// Couldn't find image in a cif tree, try to open a .bmp file
 		// This is used by The Vampire Diaries
 		Common::File f;
@@ -825,6 +848,14 @@ bool ResourceManager::loadImage(const Common::String &name, Graphics::ManagedSur
 			return false;
 		}
 
+		if (outSrc) {
+			*outSrc = info.src;
+		}
+
+		if (outDest) {
+			*outDest = info.dest;
+		}
+
 		GraphicsManager::copyToManaged(buf, surf, info.width, info.height, g_nancy->_graphicsManager->getInputPixelFormat());
 		return true;
 	}
diff --git a/engines/nancy/resource.h b/engines/nancy/resource.h
index 3bc220a2032..322eb6a0098 100644
--- a/engines/nancy/resource.h
+++ b/engines/nancy/resource.h
@@ -23,6 +23,7 @@
 #define NANCY_RESOURCE_H
 
 #include "common/array.h"
+#include "common/rect.h"
 
 namespace Graphics {
 struct Surface;
@@ -56,6 +57,7 @@ public:
 		uint16 width, pitch, height;
 		byte depth; // Bit depth
 		uint32 compressedSize, size;
+		Common::Rect src, dest; // Used when drawing conversation cels
 	};
 
 	ResourceManager();
@@ -63,9 +65,8 @@ public:
 
 	void initialize();
 	bool loadCifTree(const Common::String &name, const Common::String &ext);
-	bool loadImage(const Common::String &name, Graphics::Surface &surf);
-	bool loadImage(const Common::String &name, Graphics::ManagedSurface &surf);
-	void freeImage(Graphics::Surface &surf);
+	bool loadImage(const Common::String &name, Graphics::Surface &surf, const Common::String treeName = "", Common::Rect *outSrc = nullptr, Common::Rect *outDest = nullptr);
+	bool loadImage(const Common::String &name, Graphics::ManagedSurface &surf, const Common::String treeName = "", Common::Rect *outSrc = nullptr, Common::Rect *outDest = nullptr);
 	byte *loadData(const Common::String &name, uint &size);
 
 	// Debugger functions


Commit: 9e25e1c7a04712affd452bd132e3af66cbd5dc50
    https://github.com/scummvm/scummvm/commit/9e25e1c7a04712affd452bd132e3af66cbd5dc50
Author: Kaloyan Chehlarski (strahy at outlook.com)
Date: 2023-04-14T22:39:09+03:00

Commit Message:
NANCY: Don't assume the end line token is last

Fixed the incorrect assumption that the <o> token is the last
thng to appear in a text line, since nancy2's dialogue has
newline tokens at the end instead.

Changed paths:
    engines/nancy/ui/textbox.cpp


diff --git a/engines/nancy/ui/textbox.cpp b/engines/nancy/ui/textbox.cpp
index 183e5154947..e478026cb43 100644
--- a/engines/nancy/ui/textbox.cpp
+++ b/engines/nancy/ui/textbox.cpp
@@ -134,13 +134,17 @@ void Textbox::drawTextbox() {
 		bool hasHotspot = false;
 		Rect hotspot;
 
-		// Trim the begin and end tokens from the line
-		if (currentLine.hasPrefix(_CCBeginToken) && currentLine.hasSuffix(_CCEndToken)) {
-			currentLine = currentLine.substr(ARRAYSIZE(_CCBeginToken) - 1, currentLine.size() - ARRAYSIZE(_CCBeginToken) - ARRAYSIZE(_CCEndToken) + 2);
+		// Erase the begin and end tokens from the line
+		uint32 newLinePos;
+		while (newLinePos = currentLine.find(_CCBeginToken), newLinePos != String::npos) {
+			currentLine.erase(newLinePos, ARRAYSIZE(_CCBeginToken) - 1);
+		}
+
+		while (newLinePos = currentLine.find(_CCEndToken), newLinePos != String::npos) {
+			currentLine.erase(newLinePos, ARRAYSIZE(_CCEndToken) - 1);
 		}
 
 		// Replace every newline token with \n
-		uint32 newLinePos;
 		while (newLinePos = currentLine.find(_newLineToken), newLinePos != String::npos) {
 			currentLine.replace(newLinePos, ARRAYSIZE(_newLineToken) - 1, "\n");
 		}


Commit: 76310e9ef9f13ef040b6c40e0ee548191077d601
    https://github.com/scummvm/scummvm/commit/76310e9ef9f13ef040b6c40e0ee548191077d601
Author: Kaloyan Chehlarski (strahy at outlook.com)
Date: 2023-04-14T22:39:09+03:00

Commit Message:
NANCY: Implement nancy2 Conversation ARs

Implemented the three Conversation action record types
introduced in nancy2. These replace primary video, with
ConversationVideo being just a renamed
PlayPrimaryVideoChan0.

Changed paths:
  A engines/nancy/action/conversation.cpp
  A engines/nancy/action/conversation.h
  R engines/nancy/action/primaryvideo.cpp
  R engines/nancy/action/primaryvideo.h
    engines/nancy/action/arfactory.cpp
    engines/nancy/module.mk
    engines/nancy/nancy.cpp
    engines/nancy/state/scene.cpp
    engines/nancy/state/scene.h
    engines/nancy/ui/inventorybox.cpp


diff --git a/engines/nancy/action/arfactory.cpp b/engines/nancy/action/arfactory.cpp
index 7f52edcbf22..66eb63c7500 100644
--- a/engines/nancy/action/arfactory.cpp
+++ b/engines/nancy/action/arfactory.cpp
@@ -20,7 +20,7 @@
  */
 
 #include "engines/nancy/action/recordtypes.h"
-#include "engines/nancy/action/primaryvideo.h"
+#include "engines/nancy/action/conversation.h"
 #include "engines/nancy/action/secondaryvideo.h"
 #include "engines/nancy/action/secondarymovie.h"
 #include "engines/nancy/action/overlay.h"
@@ -62,7 +62,7 @@ ActionRecord *ActionManager::createActionRecord(uint16 type) {
 			return new SpecialEffect();
 		}		
 	case 50:
-		return new PlayPrimaryVideoChan0();
+		return new ConversationVideo(); // PlayPrimaryVideoChan0
 	case 51:
 		return new PlaySecondaryVideo(0);
 	case 52:
@@ -73,6 +73,12 @@ ActionRecord *ActionManager::createActionRecord(uint16 type) {
 		return new Overlay(false); // PlayStaticBitmapAnimation
 	case 55:
 		return new Overlay(true); // PlayIntStaticBitmapAnimation
+	case 56:
+		return new ConversationVideo();
+	case 57:
+		return new ConversationCel();
+	case 58:
+		return new ConversationSound();
 	case 60:
 		return new MapCall();
 	case 61:
diff --git a/engines/nancy/action/primaryvideo.cpp b/engines/nancy/action/conversation.cpp
similarity index 70%
rename from engines/nancy/action/primaryvideo.cpp
rename to engines/nancy/action/conversation.cpp
index c7f31e8775e..66919c98ba6 100644
--- a/engines/nancy/action/primaryvideo.cpp
+++ b/engines/nancy/action/conversation.cpp
@@ -23,172 +23,53 @@
 #include "common/random.h"
 #include "common/config-manager.h"
 #include "common/serializer.h"
+#include "common/memstream.h"
 
 #include "engines/nancy/nancy.h"
 #include "engines/nancy/sound.h"
 #include "engines/nancy/input.h"
 #include "engines/nancy/util.h"
 #include "engines/nancy/graphics.h"
+#include "engines/nancy/resource.h"
 
-#include "engines/nancy/action/primaryvideo.h"
+#include "engines/nancy/action/conversation.h"
 
 #include "engines/nancy/state/scene.h"
 
 namespace Nancy {
 namespace Action {
 
-void PlayPrimaryVideoChan0::PrimaryVideoFlag::read(Common::SeekableReadStream &stream) {
-	type = stream.readByte();
-	flag.label = stream.readSint16LE();
-	flag.flag = stream.readByte();
-	orFlag = stream.readByte();
-}
-
-bool PlayPrimaryVideoChan0::PrimaryVideoFlag::isSatisfied() const {
-	switch (type) {
-	case kFlagEvent:
-		return NancySceneState.getEventFlag(flag);
-	case kFlagInventory:
-		return NancySceneState.hasItem(flag.label) == flag.flag;
-	default:
-		return false;
-	}
-}
-
-void PlayPrimaryVideoChan0::PrimaryVideoFlag::set() const {
-	switch (type) {
-	case kFlagEvent:
-		NancySceneState.setEventFlag(flag);
-		break;
-	case kFlagInventory:
-		if (flag.flag == kInvHolding) {
-			NancySceneState.addItemToInventory(flag.label);
-		} else {
-			NancySceneState.removeItemFromInventory(flag.label);
-		}
-
-		break;
-	default:
-		break;
-	}
-}
-
-void PlayPrimaryVideoChan0::PrimaryVideoFlags::read(Common::SeekableReadStream &stream) {
-	uint16 numFlags = stream.readUint16LE();
-
-	conditionFlags.resize(numFlags);
-	for (uint i = 0; i < numFlags; ++i) {
-		conditionFlags[i].read(stream);
-	}
-}
-
-bool PlayPrimaryVideoChan0::PrimaryVideoFlags::isSatisfied() const {
-	Common::Array<bool> conditionsMet(conditionFlags.size(), false);
-
-	for (uint i = 0; i < conditionFlags.size(); ++i) {
-		if (conditionFlags[i].isSatisfied()) {
-			conditionsMet[i] = true;
-		}
-
-		if (conditionFlags[i].orFlag && i < conditionFlags.size() - 1) {
-			if (conditionsMet[i] == true) {
-				conditionsMet[i + 1] = true;
-				++i;
-			} else if (conditionFlags[i + 1].isSatisfied()) {
-				conditionsMet[i] = true;
-				conditionsMet[i + 1] = true;
-				++i;
-			}
-		}
-	}
-
-	for (uint i = 0; i < conditionsMet.size(); ++i) {
-		if (conditionsMet[i] == false) {
-			return false;
-		}
-	}
-
-	return true;
-}
-
-PlayPrimaryVideoChan0::~PlayPrimaryVideoChan0() {
-	_decoder.close();
-
-	if (NancySceneState.getActivePrimaryVideo() == this) {
-		NancySceneState.setActivePrimaryVideo(nullptr);
+ConversationSound::~ConversationSound() {
+	if (NancySceneState.getActiveConversation() == this) {
+		NancySceneState.setActiveConversation(nullptr);
 	}
 
 	NancySceneState.setShouldClearTextbox(true);
 	NancySceneState.getTextbox().setVisible(false);
 }
 
-void PlayPrimaryVideoChan0::init() {
-	if (!_decoder.loadFile(_videoName + ".avf")) {
-		error("Couldn't load video file %s", _videoName.c_str());
-	}
-
-	_decoder.seekToFrame(_startFrame);
-
-	if (!_paletteName.empty()) {
-		GraphicsManager::loadSurfacePalette(_drawSurface, _paletteName);
-		setTransparent(true);
-	}
-
+void ConversationSound::init() {
 	RenderObject::init();
-
 	NancySceneState.setShouldClearTextbox(false);
 }
 
-void PlayPrimaryVideoChan0::updateGraphics() {
-	if (!_decoder.isVideoLoaded()) {
-		return;
-	}
-
-	if (!_decoder.isPlaying()) {
-		_decoder.start();
-	}
-
-	if (_decoder.getCurFrame() == _endFrame) {
-		_decoder.pauseVideo(true);
-	}
-
-	if (_decoder.needsUpdate()) {
-		GraphicsManager::copyToManaged(*_decoder.decodeNextFrame(), _drawSurface, _videoFormat == kSmallVideoFormat);
-
-		_needsRedraw = true;
-	}
-
-	RenderObject::updateGraphics();
-}
-
-void PlayPrimaryVideoChan0::onPause(bool pause) {
-	_decoder.pauseVideo(pause);
-}
-
-void PlayPrimaryVideoChan0::readData(Common::SeekableReadStream &stream) {
+void ConversationSound::readData(Common::SeekableReadStream &stream) {
 	Common::Serializer ser(&stream, nullptr);
 	ser.setVersion(g_nancy->getGameType());
 
-	readFilename(stream, _videoName);
-	readFilename(ser, _paletteName, kGameTypeVampire, kGameTypeVampire);
-
-	ser.skip(2);
-	ser.syncAsUint16LE(_videoFormat);
-	ser.skip(3); // Quality
-	ser.skip(4, kGameTypeVampire, kGameTypeVampire); // paletteStart, paletteSize
-	ser.syncAsUint16LE(_startFrame);
-	ser.syncAsUint16LE(_endFrame);
-	ser.skip(8);
-
-	ser.skip(0x10); // Bounds
-	readRect(stream, _screenPosition);
+	if (ser.getVersion() >= kGameTypeNancy2) {
+		_sound.read(stream, SoundDescription::kNormal);
+	}
 
 	char *rawText = new char[1500];
 	ser.syncBytes((byte *)rawText, 1500);
 	UI::Textbox::assembleTextLine(rawText, _text, 1500);
 	delete[] rawText;
 
-	_sound.read(stream, SoundDescription::kNormal);
+	if (ser.getVersion() <= kGameTypeNancy1) {
+		_sound.read(stream, SoundDescription::kNormal);
+	}
+
 	_responseGenericSound.read(stream, SoundDescription::kNormal);
 	ser.skip(1);
 	ser.syncAsByte(_conditionalResponseCharacterID);
@@ -196,16 +77,16 @@ void PlayPrimaryVideoChan0::readData(Common::SeekableReadStream &stream) {
 	ser.syncAsByte(_defaultNextScene);
 	ser.syncAsByte(_popNextScene);
 	_sceneChange.readData(stream, ser.getVersion() == kGameTypeVampire);
-	ser.skip(0x32);
+	ser.skip(0x32, kGameTypeVampire, kGameTypeNancy1);
+	ser.skip(2, kGameTypeNancy2);
 
 	uint16 numResponses = 0;
 	ser.syncAsUint16LE(numResponses);
 	rawText = new char[400];
 
-	_responses.reserve(numResponses);
+	_responses.resize(numResponses);
 	for (uint i = 0; i < numResponses; ++i) {
-		_responses.push_back(ResponseStruct());
-		ResponseStruct &response = _responses.back();
+		ResponseStruct &response = _responses[i];
 		response.conditionFlags.read(stream);
 		ser.syncBytes((byte*)rawText, 400);
 		UI::Textbox::assembleTextLine(rawText, response.text, 400);
@@ -214,7 +95,8 @@ void PlayPrimaryVideoChan0::readData(Common::SeekableReadStream &stream) {
 		response.sceneChange.readData(stream, ser.getVersion() == kGameTypeVampire);
 		ser.syncAsSint16LE(response.flagDesc.label);
 		ser.syncAsByte(response.flagDesc.flag);
-		ser.skip(0x32);
+		ser.skip(0x32, kGameTypeVampire, kGameTypeNancy1);
+		ser.skip(2, kGameTypeNancy2);
 	}
 
 	delete[] rawText;
@@ -238,25 +120,24 @@ void PlayPrimaryVideoChan0::readData(Common::SeekableReadStream &stream) {
 	}
 }
 
-void PlayPrimaryVideoChan0::execute() {
-	PlayPrimaryVideoChan0 *activeVideo = NancySceneState.getActivePrimaryVideo();
-	if (activeVideo != this && activeVideo != nullptr) {
-		if (	!activeVideo->_isDone ||
-				activeVideo->_defaultNextScene == kDefaultNextSceneEnabled ||
-				activeVideo->_pickedResponse != -1	) {
+void ConversationSound::execute() {
+	ConversationSound *activeConversation = NancySceneState.getActiveConversation();
+	if (activeConversation != this && activeConversation != nullptr) {
+		if (	!activeConversation->_isDone ||
+				activeConversation->_defaultNextScene == kDefaultNextSceneEnabled ||
+				activeConversation->_pickedResponse != -1	) {
 
 			return;
 		} else {
 			// Chained videos, hide the previous one and start this
-			activeVideo->setVisible(false);
-			NancySceneState.setActivePrimaryVideo(this);
+			activeConversation->setVisible(false);
+			NancySceneState.setActiveConversation(this);
 		}
 	}
 
 	switch (_state) {
 	case kBegin: {
 		init();
-		registerGraphics();
 		g_nancy->_sound->loadSound(_sound);
 
 		if (!ConfMan.getBool("speech_mute") && ConfMan.getBool("character_speech")) {
@@ -282,7 +163,7 @@ void PlayPrimaryVideoChan0::execute() {
 		}
 
 		_state = kRun;
-		NancySceneState.setActivePrimaryVideo(this);
+		NancySceneState.setActiveConversation(this);
 
 		// Do not fall through to give the execution one loop for event flag changes
 		// This fixes TVD scene 750
@@ -316,7 +197,7 @@ void PlayPrimaryVideoChan0::execute() {
 			}
 		}
 
-		if (!g_nancy->_sound->isSoundPlaying(_sound) && (_decoder.endOfVideo() || _decoder.getCurFrame() == _endFrame)) {
+		if (!g_nancy->_sound->isSoundPlaying(_sound) && isVideoDonePlaying()) {
 			g_nancy->_sound->stopSound(_sound);
 
 			bool hasResponses = false;
@@ -406,7 +287,7 @@ void PlayPrimaryVideoChan0::execute() {
 	}
 }
 
-void PlayPrimaryVideoChan0::addConditionalDialogue() {
+void ConversationSound::addConditionalDialogue() {
 	for (const auto &res : g_nancy->getStaticData().conditionalDialogue[_conditionalResponseCharacterID]) {
 		bool isSatisfied = true;
 
@@ -435,7 +316,7 @@ void PlayPrimaryVideoChan0::addConditionalDialogue() {
 	}
 }
 
-void PlayPrimaryVideoChan0::addGoodbye() {
+void ConversationSound::addGoodbye() {
 	auto &res = g_nancy->getStaticData().goodbyes[_goodbyeResponseCharacterID];
 	_responses.push_back(ResponseStruct());
 	ResponseStruct &newResponse = _responses.back();
@@ -478,5 +359,233 @@ void PlayPrimaryVideoChan0::addGoodbye() {
 	newResponse.sceneChange.continueSceneSound = kContinueSceneSound;
 }
 
+void ConversationSound::ConversationFlag::read(Common::SeekableReadStream &stream) {
+	type = stream.readByte();
+	flag.label = stream.readSint16LE();
+	flag.flag = stream.readByte();
+	orFlag = stream.readByte();
+}
+
+bool ConversationSound::ConversationFlag::isSatisfied() const {
+	switch (type) {
+	case kFlagEvent:
+		return NancySceneState.getEventFlag(flag);
+	case kFlagInventory:
+		return NancySceneState.hasItem(flag.label) == flag.flag;
+	default:
+		return false;
+	}
+}
+
+void ConversationSound::ConversationFlag::set() const {
+	switch (type) {
+	case kFlagEvent:
+		NancySceneState.setEventFlag(flag);
+		break;
+	case kFlagInventory:
+		if (flag.flag == kInvHolding) {
+			NancySceneState.addItemToInventory(flag.label);
+		} else {
+			NancySceneState.removeItemFromInventory(flag.label);
+		}
+
+		break;
+	default:
+		break;
+	}
+}
+
+void ConversationSound::ConversationFlags::read(Common::SeekableReadStream &stream) {
+	uint16 numFlags = stream.readUint16LE();
+
+	conditionFlags.resize(numFlags);
+	for (uint i = 0; i < numFlags; ++i) {
+		conditionFlags[i].read(stream);
+	}
+}
+
+bool ConversationSound::ConversationFlags::isSatisfied() const {
+	Common::Array<bool> conditionsMet(conditionFlags.size(), false);
+
+	for (uint i = 0; i < conditionFlags.size(); ++i) {
+		if (conditionFlags[i].isSatisfied()) {
+			conditionsMet[i] = true;
+		}
+
+		if (conditionFlags[i].orFlag && i < conditionFlags.size() - 1) {
+			if (conditionsMet[i] == true) {
+				conditionsMet[i + 1] = true;
+				++i;
+			} else if (conditionFlags[i + 1].isSatisfied()) {
+				conditionsMet[i] = true;
+				conditionsMet[i + 1] = true;
+				++i;
+			}
+		}
+	}
+
+	for (uint i = 0; i < conditionsMet.size(); ++i) {
+		if (conditionsMet[i] == false) {
+			return false;
+		}
+	}
+
+	return true;
+}
+
+void ConversationVideo::init() {
+	if (!_decoder.loadFile(_videoName + ".avf")) {
+		error("Couldn't load video file %s", _videoName.c_str());
+	}
+
+	_decoder.seekToFrame(_firstFrame);
+
+	if (!_paletteName.empty()) {
+		GraphicsManager::loadSurfacePalette(_drawSurface, _paletteName);
+		setTransparent(true);
+	}
+	
+	registerGraphics();
+}
+
+void ConversationVideo::readData(Common::SeekableReadStream &stream) {
+	Common::Serializer ser(&stream, nullptr);
+	ser.setVersion(g_nancy->getGameType());
+
+	readFilename(stream, _videoName);
+	readFilename(ser, _paletteName, kGameTypeVampire, kGameTypeVampire);
+
+	ser.skip(2, kGameTypeVampire, kGameTypeNancy1);
+	ser.syncAsUint16LE(_videoFormat);
+	ser.skip(3); // Quality
+	ser.skip(4, kGameTypeVampire, kGameTypeVampire); // paletteStart, paletteSize
+	ser.syncAsUint16LE(_firstFrame);
+	ser.syncAsUint16LE(_lastFrame);
+	ser.skip(8, kGameTypeVampire, kGameTypeNancy1);
+	ser.skip(6, kGameTypeNancy2);
+
+	ser.skip(0x10); // Bounds
+	readRect(stream, _screenPosition);
+
+	ConversationSound::readData(stream);
+}
+
+void ConversationVideo::updateGraphics() {
+	if (!_decoder.isVideoLoaded()) {
+		return;
+	}
+
+	if (!_decoder.isPlaying()) {
+		_decoder.start();
+	}
+
+	if (_decoder.getCurFrame() == _lastFrame) {
+		_decoder.pauseVideo(true);
+	}
+
+	if (_decoder.needsUpdate()) {
+		GraphicsManager::copyToManaged(*_decoder.decodeNextFrame(), _drawSurface, _videoFormat == kSmallVideoFormat);
+
+		_needsRedraw = true;
+	}
+
+	RenderObject::updateGraphics();
+}
+
+void ConversationVideo::onPause(bool pause) {
+	_decoder.pauseVideo(pause);
+}
+
+bool ConversationVideo::isVideoDonePlaying() {
+	return _decoder.endOfVideo() || _decoder.getCurFrame() == _lastFrame;
+}
+
+Common::String ConversationVideo::getRecordTypeName() const {
+	if (g_nancy->getGameType() <= kGameTypeNancy1) {
+		return "PlayPrimaryVideo";
+	} else {
+		return "ConversationVideo";
+	}
+}
+
+void ConversationCel::init() {
+	registerGraphics();
+}
+
+void ConversationCel::registerGraphics() {
+	RenderObject::registerGraphics();
+	_headRObj.registerGraphics();
+}
+
+void ConversationCel::updateGraphics() {
+	uint32 currentTime = g_nancy->getTotalPlayTime();
+
+	if (currentTime > _nextFrameTime && ++_curFrame < (int)_cels.size()) {
+		Cel &curCel = _cels[_curFrame];
+
+		_drawSurface.create(curCel.bodySurf, curCel.bodySrc);
+		moveTo(curCel.bodyDest);
+
+		_headRObj._drawSurface.create(curCel.headSurf, curCel.headSrc);
+		_headRObj.moveTo(curCel.headDest);
+
+		_nextFrameTime = currentTime + _frameTime;
+	}
+}
+
+void ConversationCel::readData(Common::SeekableReadStream &stream) {
+	Common::String xsheetName, bodyTreeName, headTreeName;
+	readFilename(stream, xsheetName);
+	readFilename(stream, bodyTreeName);
+	readFilename(stream, headTreeName);
+	
+	uint xsheetDataSize = 0;
+	byte *xsbuf = g_nancy->_resource->loadData(xsheetName, xsheetDataSize);
+	if (!xsbuf) {
+		return;
+	}
+
+	Common::MemoryReadStream xsheet(xsbuf, xsheetDataSize, DisposeAfterUse::YES);
+	
+	// Read the xsheet and load all images into the arrays
+	// Completely unoptimized, the original engine uses a buffer
+	xsheet.seek(0);
+	Common::String signature = xsheet.readString('\0', 18);
+	if (signature != "XSHEET WayneSikes") {
+		warning("XSHEET signature doesn't match!");
+		return;
+	}
+
+	xsheet.seek(0x22);
+	uint numFrames = xsheet.readUint16LE();
+	xsheet.skip(2);
+	_frameTime = xsheet.readUint16LE();
+	xsheet.skip(2);
+
+	Common::String imageName;
+	_cels.resize(numFrames);
+	for (uint i = 0; i < numFrames; ++i) {
+		Cel &cel = _cels[i];
+		readFilename(xsheet, imageName);
+		g_nancy->_resource->loadImage(imageName, cel.bodySurf, bodyTreeName, &cel.bodySrc, &cel.bodyDest);
+		readFilename(xsheet, imageName);
+		g_nancy->_resource->loadImage(imageName, cel.headSurf, headTreeName, &cel.headSrc, &cel.headDest);
+		xsheet.skip(28);
+	}
+
+	// Continue reading the AR stream
+	stream.skip(0x17);
+	_firstFrame = stream.readUint16LE();
+	_lastFrame = stream.readUint16LE();
+
+	stream.skip(0x8E);
+
+	ConversationSound::readData(stream);
+}
+
+bool ConversationCel::isVideoDonePlaying() {
+	return _curFrame == _lastFrame && _nextFrameTime <= g_nancy->getTotalPlayTime();
+}
+	
 } // End of namespace Action
 } // End of namespace Nancy
diff --git a/engines/nancy/action/conversation.h b/engines/nancy/action/conversation.h
new file mode 100644
index 00000000000..ddb066f5076
--- /dev/null
+++ b/engines/nancy/action/conversation.h
@@ -0,0 +1,184 @@
+/* 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_CONVERSATION_H
+#define NANCY_ACTION_CONVERSATION_H
+
+#include "engines/nancy/action/actionrecord.h"
+#include "engines/nancy/renderobject.h"
+#include "engines/nancy/video.h"
+
+namespace Nancy {
+namespace Action {
+
+// The base class for conversations, with no video data
+class ConversationSound : public ActionRecord, public RenderObject {
+public:
+	ConversationSound() : RenderObject(8) {}
+	virtual ~ConversationSound();
+
+	void init() override;
+	void readData(Common::SeekableReadStream &stream) override;
+	void execute() override;
+
+	virtual bool isVideoDonePlaying() { return true; }
+
+protected:
+	struct ConversationFlag {
+		byte type;
+		FlagDescription flag;
+		byte orFlag;
+
+		void read(Common::SeekableReadStream &stream);
+		bool isSatisfied() const;
+		void set() const;
+	};
+
+	struct ConversationFlags {
+		Common::Array<ConversationFlag> conditionFlags;
+
+		void read(Common::SeekableReadStream &stream);
+		bool isSatisfied() const;
+	};
+
+	struct ResponseStruct {
+		ConversationFlags conditionFlags; // 0x01
+		Common::String text; // 0x06
+		Common::String soundName; // 0x196
+		SceneChangeDescription sceneChange; // 0x1A0
+		FlagDescription flagDesc; // 0x1A8
+
+		bool isOnScreen = false;
+	};
+
+	struct FlagsStruct {
+		ConversationFlags conditions;
+		ConversationFlag flagToSet;
+	};
+
+	struct SceneBranchStruct {
+		ConversationFlags conditions;
+		SceneChangeDescription sceneChange;
+	};
+
+	static const byte kDefaultNextSceneEnabled	= 1;
+	static const byte kDefaultNextSceneDisabled	= 2;
+
+	static const byte kPopNextScene				= 1;
+	static const byte kNoPopNextScene			= 2;
+
+	Common::String getRecordTypeName() const override { return "ConversationSound"; }
+	bool isViewportRelative() const override { return true; }
+
+	// Functions for handling the built-in dialogue responses found in the executable
+	void addConditionalDialogue();
+	void addGoodbye();
+
+	Common::String _text;
+
+	SoundDescription _sound;
+	SoundDescription _responseGenericSound;
+
+	byte _conditionalResponseCharacterID = 0;
+	byte _goodbyeResponseCharacterID = 0;
+	byte _defaultNextScene = kDefaultNextSceneEnabled;
+	byte _popNextScene = kNoPopNextScene;
+	SceneChangeDescription _sceneChange;
+
+	Common::Array<ResponseStruct> _responses;
+	Common::Array<FlagsStruct> _flagsStructs;
+	Common::Array<SceneBranchStruct> _sceneBranchStructs;
+
+	bool _hasDrawnTextbox = false;
+	int16 _pickedResponse = -1;
+};
+
+// Conversation with an AVF video. Originally called PlayPrimaryVideoChan0
+class ConversationVideo : public ConversationSound {
+public:
+	void init() override;
+	void updateGraphics() override;
+	void onPause(bool pause) override;
+
+	void readData(Common::SeekableReadStream &stream) override;
+
+	bool isVideoDonePlaying() override;
+
+protected:
+	Common::String getRecordTypeName() const override;
+
+	Common::String _videoName;
+	Common::String _paletteName;
+	uint _videoFormat = kLargeVideoFormat;
+	uint16 _firstFrame = 0;
+	int16 _lastFrame = 0;
+	AVFDecoder _decoder;
+};
+
+// Conversation with separate cels for the body and head of the character.
+// Cels are separate images bundled inside a .cal file
+class ConversationCel : public ConversationSound {
+public:
+	struct Cel {
+		Graphics::ManagedSurface bodySurf;
+		Common::Rect bodySrc;
+		Common::Rect bodyDest;
+		Graphics::ManagedSurface headSurf;
+		Common::Rect headSrc;
+		Common::Rect headDest;
+	};
+
+	class HeadCel : public RenderObject {
+	public:
+		HeadCel() : RenderObject(9) {}
+		bool isViewportRelative() const override { return true; }
+	};
+
+	ConversationCel() {}
+
+	void init() override;
+	void registerGraphics() override;
+	void updateGraphics() override;
+
+	void readData(Common::SeekableReadStream &stream) override;
+
+	bool isVideoDonePlaying() override;
+
+	Common::Array<Cel> _cels;
+	uint16 _frameTime = 0;
+	uint _videoFormat = kLargeVideoFormat;
+	uint16 _firstFrame = 0;
+	int16 _lastFrame = 0;
+
+	int _curFrame = -1; 
+	uint16 _nextFrameTime = 0;
+
+	// We use the built-in RenderObject for the body
+	HeadCel _headRObj;
+
+protected:
+	Common::String getRecordTypeName() const override { return "ConversationCel"; }
+};
+
+} // End of namespace Action
+} // End of namespace Nancy
+
+#endif // NANCY_ACTION_CONVERSATION_H
diff --git a/engines/nancy/action/primaryvideo.h b/engines/nancy/action/primaryvideo.h
deleted file mode 100644
index 80839f99606..00000000000
--- a/engines/nancy/action/primaryvideo.h
+++ /dev/null
@@ -1,131 +0,0 @@
-/* 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_PRIMARYVIDEO_H
-#define NANCY_ACTION_PRIMARYVIDEO_H
-
-#include "engines/nancy/video.h"
-#include "engines/nancy/renderobject.h"
-
-#include "engines/nancy/action/actionrecord.h"
-
-namespace Nancy {
-
-class NancyEngine;
-
-namespace Action {
-
-// ActionRecord subclass that handles all NPC dialog and nancy1's intro video
-class PlayPrimaryVideoChan0 : public ActionRecord, public RenderObject {
-
-struct PrimaryVideoFlag {
-	byte type;
-	FlagDescription flag;
-	byte orFlag;
-
-	void read(Common::SeekableReadStream &stream);
-	bool isSatisfied() const;
-	void set() const;
-};
-
-struct PrimaryVideoFlags {
-	Common::Array<PrimaryVideoFlag> conditionFlags;
-
-	void read(Common::SeekableReadStream &stream);
-	bool isSatisfied() const;
-};
-
-struct ResponseStruct {
-	PrimaryVideoFlags conditionFlags; // 0x01
-	Common::String text; // 0x06
-	Common::String soundName; // 0x196
-	SceneChangeDescription sceneChange; // 0x1A0
-	FlagDescription flagDesc; // 0x1A8
-
-	bool isOnScreen = false;
-};
-
-struct FlagsStruct {
-	PrimaryVideoFlags conditions;
-	PrimaryVideoFlag flagToSet;
-};
-
-struct SceneBranchStruct {
-	PrimaryVideoFlags conditions;
-	SceneChangeDescription sceneChange;
-};
-
-public:
-	static const byte kDefaultNextSceneEnabled	= 1;
-	static const byte kDefaultNextSceneDisabled	= 2;
-
-	static const byte kPopNextScene				= 1;
-	static const byte kNoPopNextScene			= 2;
-
-	PlayPrimaryVideoChan0() : RenderObject(8) {}
-	virtual ~PlayPrimaryVideoChan0();
-
-	void init() override;
-	void updateGraphics() override;
-	void onPause(bool pause) override;
-
-	void readData(Common::SeekableReadStream &stream) override;
-	void execute() override;
-
-	// Functions for handling the built-in dialogue responses found in the executable
-	void addConditionalDialogue();
-	void addGoodbye();
-
-	Common::String _videoName;
-	Common::String _paletteName;
-	uint _videoFormat = kLargeVideoFormat;
-	Common::String _text;
-
-	SoundDescription _sound;
-	SoundDescription _responseGenericSound;
-
-	uint16 _startFrame = 0;
-	int16 _endFrame = 0;
-
-	byte _conditionalResponseCharacterID = 0;
-	byte _goodbyeResponseCharacterID = 0;
-	byte _defaultNextScene = kDefaultNextSceneEnabled;
-	byte _popNextScene = kNoPopNextScene;
-	SceneChangeDescription _sceneChange;
-
-	Common::Array<ResponseStruct> _responses;
-	Common::Array<FlagsStruct> _flagsStructs;
-	Common::Array<SceneBranchStruct> _sceneBranchStructs;
-
-	AVFDecoder _decoder;
-
-	bool _hasDrawnTextbox = false;
-	int16 _pickedResponse = -1;
-
-protected:
-	Common::String getRecordTypeName() const override { return "PlayPrimaryVideoChan0"; }
-	bool isViewportRelative() const override { return true; }
-};
-
-} // End of namespace Action
-} // End of namespace Nancy
-
-#endif // NANCY_ACTION_PRIMARYVIDEO_H
diff --git a/engines/nancy/module.mk b/engines/nancy/module.mk
index 83034d57663..d20c568e6f0 100644
--- a/engines/nancy/module.mk
+++ b/engines/nancy/module.mk
@@ -7,7 +7,7 @@ MODULE_OBJS = \
   action/leverpuzzle.o \
   action/orderingpuzzle.o \
   action/passwordpuzzle.o \
-  action/primaryvideo.o \
+  action/conversation.o \
   action/recordtypes.o \
   action/rotatinglockpuzzle.o \
   action/secondarymovie.o \
diff --git a/engines/nancy/nancy.cpp b/engines/nancy/nancy.cpp
index ff90fcbc758..f4da3796e91 100644
--- a/engines/nancy/nancy.cpp
+++ b/engines/nancy/nancy.cpp
@@ -36,7 +36,7 @@
 #include "engines/nancy/console.h"
 #include "engines/nancy/util.h"
 
-#include "engines/nancy/action/primaryvideo.h"
+#include "engines/nancy/action/conversation.h"
 
 #include "engines/nancy/state/logo.h"
 #include "engines/nancy/state/scene.h"
@@ -132,7 +132,7 @@ bool NancyEngine::canLoadGameStateCurrently() {
 
 bool NancyEngine::canSaveGameStateCurrently() {
 	// TODO also disable during secondary movie
-	return State::Scene::hasInstance() && NancySceneState.getActivePrimaryVideo() == nullptr;
+	return State::Scene::hasInstance() && NancySceneState.getActiveConversation() == nullptr;
 }
 
 bool NancyEngine::canSaveAutosaveCurrently() {
diff --git a/engines/nancy/state/scene.cpp b/engines/nancy/state/scene.cpp
index 0ab67a713b5..614c427e3d0 100644
--- a/engines/nancy/state/scene.cpp
+++ b/engines/nancy/state/scene.cpp
@@ -112,7 +112,7 @@ Scene::Scene() :
 		_clock(nullptr),
 		_actionManager(),
 		_difficulty(0),
-		_activePrimaryVideo(nullptr),
+		_activeConversation(nullptr),
 		_lightning(nullptr) {}
 
 Scene::~Scene()  {
@@ -535,12 +535,12 @@ void Scene::init() {
 	g_nancy->_graphicsManager->redrawAll();
 }
 
-void Scene::setActivePrimaryVideo(Action::PlayPrimaryVideoChan0 *activeVideo) {
-	_activePrimaryVideo = activeVideo;
+void Scene::setActiveConversation(Action::ConversationSound *activeConversation) {
+	_activeConversation = activeConversation;
 }
 
-Action::PlayPrimaryVideoChan0 *Scene::getActivePrimaryVideo() {
-	return _activePrimaryVideo;
+Action::ConversationSound *Scene::getActiveConversation() {
+	return _activeConversation;
 }
 
 void Scene::beginLightning(int16 distance, uint16 pulseTime, int16 rgbPercent) {
@@ -661,7 +661,7 @@ void Scene::handleInput() {
 	NancyInput input = g_nancy->_input->getInput();
 
 	// Warp the mouse below the inactive zone during dialogue scenes
-	if (_activePrimaryVideo != nullptr) {
+	if (_activeConversation != nullptr) {
 		const Common::Rect &inactiveZone = g_nancy->_cursorManager->getPrimaryVideoInactiveZone();
 		const Common::Point cursorHotspot = g_nancy->_cursorManager->getCurrentCursorHotspot();
 		Common::Point adjustedMousePos = input.mousePos;
diff --git a/engines/nancy/state/scene.h b/engines/nancy/state/scene.h
index 208447fa22e..ea367da1529 100644
--- a/engines/nancy/state/scene.h
+++ b/engines/nancy/state/scene.h
@@ -48,7 +48,7 @@ struct SceneChangeDescription;
 
 namespace Action {
 class SliderPuzzle;
-class PlayPrimaryVideoChan0;
+class ConversationSound;
 }
 
 namespace Misc {
@@ -181,8 +181,8 @@ public:
 	SceneInfo &getNextSceneInfo() { return _sceneState.nextScene; }
 	const SceneSummary &getSceneSummary() const { return _sceneState.summary; }
 
-	void setActivePrimaryVideo(Action::PlayPrimaryVideoChan0 *activeVideo);
-	Action::PlayPrimaryVideoChan0 *getActivePrimaryVideo();
+	void setActiveConversation(Action::ConversationSound *activeConversation);
+	Action::ConversationSound *getActiveConversation();
 
 	// The Vampire Diaries only;
 	void beginLightning(int16 distance, uint16 pulseTime, int16 rgbPercent);
@@ -274,7 +274,7 @@ private:
 	Common::Rect _mapHotspot;
 
 	Action::ActionManager _actionManager;
-	Action::PlayPrimaryVideoChan0 *_activePrimaryVideo;
+	Action::ConversationSound *_activeConversation;
 
 	State _state;
 
diff --git a/engines/nancy/ui/inventorybox.cpp b/engines/nancy/ui/inventorybox.cpp
index 286fb21ae78..b47a1153d3d 100644
--- a/engines/nancy/ui/inventorybox.cpp
+++ b/engines/nancy/ui/inventorybox.cpp
@@ -92,7 +92,7 @@ void InventoryBox::registerGraphics() {
 
 void InventoryBox::handleInput(NancyInput &input) {
 	// Disable input when primary video is playing
-	if (NancySceneState.getActivePrimaryVideo()) {
+	if (NancySceneState.getActiveConversation()) {
 		return;
 	}
 


Commit: a67dbb9e096df4b0a0e61c443a8e5d139bf4044c
    https://github.com/scummvm/scummvm/commit/a67dbb9e096df4b0a0e61c443a8e5d139bf4044c
Author: Kaloyan Chehlarski (strahy at outlook.com)
Date: 2023-04-14T22:39:10+03:00

Commit Message:
NANCY: Implement textbox hover highlighting

Added highlighting to hovered response text in nancy2's
textbox.

Changed paths:
    engines/nancy/enginedata.cpp
    engines/nancy/enginedata.h
    engines/nancy/renderobject.h
    engines/nancy/ui/textbox.cpp
    engines/nancy/ui/textbox.h


diff --git a/engines/nancy/enginedata.cpp b/engines/nancy/enginedata.cpp
index 82df0bc6a25..7e7b7af600a 100644
--- a/engines/nancy/enginedata.cpp
+++ b/engines/nancy/enginedata.cpp
@@ -216,7 +216,16 @@ TBOX::TBOX(Common::SeekableReadStream *chunkStream) {
 	}
 
 	chunkStream->seek(0x1FE);
-	fontID = chunkStream->readUint16LE();
+	defaultFontID = chunkStream->readUint16LE();
+	
+	if (g_nancy->getGameType() >= kGameTypeNancy2) {
+		chunkStream->skip(2);
+		conversationFontID = chunkStream->readUint16LE();
+		highlightConversationFontID = chunkStream->readUint16LE();
+	} else {
+		conversationFontID = defaultFontID;
+		highlightConversationFontID = defaultFontID;
+	}
 
 	delete chunkStream;
 }
diff --git a/engines/nancy/enginedata.h b/engines/nancy/enginedata.h
index 3d195bee912..fb4106f634e 100644
--- a/engines/nancy/enginedata.h
+++ b/engines/nancy/enginedata.h
@@ -115,7 +115,9 @@ struct TBOX {
 	Common::Array<Common::Rect> ornamentSrcs;
 	Common::Array<Common::Rect> ornamentDests;
 
-	uint16 fontID;
+	uint16 defaultFontID;
+	uint16 conversationFontID;
+	uint16 highlightConversationFontID;
 };
 
 struct MAP {
diff --git a/engines/nancy/renderobject.h b/engines/nancy/renderobject.h
index 423a7971170..80caea45269 100644
--- a/engines/nancy/renderobject.h
+++ b/engines/nancy/renderobject.h
@@ -48,6 +48,7 @@ public:
 	void moveTo(const Common::Rect &bounds);
 	void setVisible(bool visible);
 	void setTransparent(bool isTransparent);
+	bool isVisible() const { return _isVisible; }
 
 	// Only used by The Vampire Diaries
 	void grabPalette(byte *colors, uint paletteStart = 0, uint paletteSize = 256);
diff --git a/engines/nancy/ui/textbox.cpp b/engines/nancy/ui/textbox.cpp
index e478026cb43..a481f26680c 100644
--- a/engines/nancy/ui/textbox.cpp
+++ b/engines/nancy/ui/textbox.cpp
@@ -48,7 +48,8 @@ Textbox::Textbox() :
 		_scrollbar(nullptr),
 		_scrollbarPos(0),
 		_numLines(0),
-		_lastResponseisMultiline(false) {}
+		_lastResponseisMultiline(false),
+		_highlightRObj(7) {}
 
 Textbox::~Textbox() {
 	delete _scrollbar;
@@ -59,7 +60,10 @@ void Textbox::init() {
 	assert(tbox);
 
 	moveTo(g_nancy->_bootSummary->textboxScreenPosition);
+	_highlightRObj.moveTo(g_nancy->_bootSummary->textboxScreenPosition);
 	_fullSurface.create(tbox->innerBoundingBox.width(), tbox->innerBoundingBox.height(), g_nancy->_graphicsManager->getScreenPixelFormat());
+	_textHighlightSurface.create(tbox->innerBoundingBox.width(), tbox->innerBoundingBox.height(), g_nancy->_graphicsManager->getScreenPixelFormat());
+	_textHighlightSurface.setTransparentColor(g_nancy->_graphicsManager->getTransColor());
 
 	Common::Rect outerBoundingBox = _screenPosition;
 	outerBoundingBox.moveTo(0, 0);
@@ -78,6 +82,7 @@ void Textbox::init() {
 void Textbox::registerGraphics() {
 	RenderObject::registerGraphics();
 	_scrollbar->registerGraphics();
+	_highlightRObj.registerGraphics();
 }
 
 void Textbox::updateGraphics() {
@@ -97,12 +102,24 @@ void Textbox::updateGraphics() {
 void Textbox::handleInput(NancyInput &input) {
 	_scrollbar->handleInput(input);
 
+	bool hasHighlight = false;
 	for (uint i = 0; i < _hotspots.size(); ++i) {
 		Common::Rect hotspot = _hotspots[i];
 		hotspot.translate(0, -_drawSurface.getOffsetFromOwner().y);
-		if (convertToScreen(hotspot).findIntersectingRect(_screenPosition).contains(input.mousePos)) {
+		Common::Rect hotspotOnScreen = convertToScreen(hotspot).findIntersectingRect(_screenPosition);
+		if (hotspotOnScreen.contains(input.mousePos)) {
 			g_nancy->_cursorManager->setCursorType(CursorManager::kHotspotArrow);
 
+			// Highlight the selected response
+			if (g_nancy->getGameType() >= kGameTypeNancy2) {
+				_highlightRObj.setVisible(true);
+				Common::Rect hotspotInside = convertToLocal(hotspotOnScreen);
+				hotspotInside.translate(0, _drawSurface.getOffsetFromOwner().y);
+				_highlightRObj._drawSurface.create(_textHighlightSurface, hotspotInside);
+				_highlightRObj.moveTo(hotspotOnScreen);
+				hasHighlight = true;
+			}
+
 			if (input.input & NancyInput::kLeftMouseButtonUp) {
 				input.input &= ~NancyInput::kLeftMouseButtonUp;
 				NancySceneState.clearLogicConditions();
@@ -112,6 +129,10 @@ void Textbox::handleInput(NancyInput &input) {
 			break;
 		}
 	}
+
+	if (!hasHighlight && _highlightRObj.isVisible()) {
+		_highlightRObj.setVisible(false);
+	}
 }
 
 void Textbox::drawTextbox() {
@@ -122,7 +143,8 @@ void Textbox::drawTextbox() {
 
 	_numLines = 0;
 
-	const Font *font = g_nancy->_graphicsManager->getFont(tbox->fontID);
+	const Font *font = g_nancy->_graphicsManager->getFont(tbox->conversationFontID);
+	const Font *highlightFont = g_nancy->_graphicsManager->getFont(tbox->highlightConversationFontID);
 
 	uint maxWidth = _fullSurface.w - tbox->maxWidthDifference - tbox->borderWidth - 2;
 	uint lineDist = tbox->lineHeight + tbox->lineHeight / 4;
@@ -225,8 +247,8 @@ void Textbox::drawTextbox() {
 
 			if (hasHotspot) {
 				hotspot.left = tbox->borderWidth;
-				hotspot.top = tbox->firstLineOffset - font->getFontHeight() + (_numLines + 1 - numColorLines) * lineDist;
-				hotspot.setHeight(MAX<int16>((wrappedLines.size() + numColorLines - 1), 1) * tbox->lineHeight + lineDist);
+				hotspot.top = tbox->firstLineOffset - tbox->lineHeight + (_numLines - numColorLines) * lineDist - 1;
+				hotspot.setHeight((wrappedLines.size() + MAX<int16>((numColorLines - 1), 0)) * lineDist - (lineDist - tbox->lineHeight));
 				hotspot.setWidth(0);
 			}
 
@@ -234,6 +256,7 @@ void Textbox::drawTextbox() {
 			for (uint i = 0; i < wrappedLines.size(); ++i) {
 				font->drawString(&_fullSurface, wrappedLines[i], tbox->borderWidth + (i == 0 ? horizontalOffset : 0), tbox->firstLineOffset - font->getFontHeight() + _numLines * lineDist, maxWidth, 0);
 				if (hasHotspot) {
+					highlightFont->drawString(&_textHighlightSurface, wrappedLines[i], tbox->borderWidth + (i == 0 ? horizontalOffset : 0), tbox->firstLineOffset - highlightFont->getFontHeight() + _numLines * lineDist, maxWidth, 0);
 					hotspot.setWidth(MAX<int16>(hotspot.width(), font->getStringWidth(wrappedLines[i]) + (i == 0 ? horizontalOffset : 0)));
 				}
 				++_numLines;
@@ -267,6 +290,7 @@ void Textbox::drawTextbox() {
 
 void Textbox::clear() {
 	_fullSurface.clear();
+	_textHighlightSurface.clear(_textHighlightSurface.getTransparentColor());
 	_textLines.clear();
 	_hotspots.clear();
 	_scrollbar->resetPosition();
@@ -315,8 +339,10 @@ void Textbox::onScrollbarMove() {
 		Common::Rect bounds = getBounds();
 		bounds.moveTo(0, (inner - outer) * _scrollbarPos);
 		_drawSurface.create(_fullSurface, bounds);
+		_highlightRObj._drawSurface.create(_textHighlightSurface, bounds);
 	} else {
 		_drawSurface.create(_fullSurface, getBounds());
+		_highlightRObj._drawSurface.create(_textHighlightSurface, getBounds());
 	}
 
 	_needsRedraw = true;
diff --git a/engines/nancy/ui/textbox.h b/engines/nancy/ui/textbox.h
index ab44c85936a..bb2bb3f745f 100644
--- a/engines/nancy/ui/textbox.h
+++ b/engines/nancy/ui/textbox.h
@@ -62,6 +62,9 @@ private:
 	};
 
 	Graphics::ManagedSurface _fullSurface;
+	Graphics::ManagedSurface _textHighlightSurface;
+
+	RenderObject _highlightRObj;
 
 	Scrollbar *_scrollbar;
 


Commit: 1bab29825a9ddd42e1a4f0913c6e4533c38b18e4
    https://github.com/scummvm/scummvm/commit/1bab29825a9ddd42e1a4f0913c6e4533c38b18e4
Author: Kaloyan Chehlarski (strahy at outlook.com)
Date: 2023-04-14T22:39:10+03:00

Commit Message:
NANCY: ConversationCel fixes

Fixed wrong timing and responses not triggering.

Changed paths:
    engines/nancy/action/conversation.cpp
    engines/nancy/action/conversation.h


diff --git a/engines/nancy/action/conversation.cpp b/engines/nancy/action/conversation.cpp
index 66919c98ba6..0b7e0ee998e 100644
--- a/engines/nancy/action/conversation.cpp
+++ b/engines/nancy/action/conversation.cpp
@@ -510,6 +510,7 @@ Common::String ConversationVideo::getRecordTypeName() const {
 
 void ConversationCel::init() {
 	registerGraphics();
+	ConversationSound::init();
 }
 
 void ConversationCel::registerGraphics() {
@@ -520,7 +521,7 @@ void ConversationCel::registerGraphics() {
 void ConversationCel::updateGraphics() {
 	uint32 currentTime = g_nancy->getTotalPlayTime();
 
-	if (currentTime > _nextFrameTime && ++_curFrame < (int)_cels.size()) {
+	if (currentTime > _nextFrameTime && _curFrame < _cels.size()) {
 		Cel &curCel = _cels[_curFrame];
 
 		_drawSurface.create(curCel.bodySurf, curCel.bodySrc);
@@ -529,7 +530,12 @@ void ConversationCel::updateGraphics() {
 		_headRObj._drawSurface.create(curCel.headSurf, curCel.headSrc);
 		_headRObj.moveTo(curCel.headDest);
 
-		_nextFrameTime = currentTime + _frameTime;
+		if (_nextFrameTime == 0) {
+			_nextFrameTime = currentTime;
+		}
+		
+		_nextFrameTime += _frameTime;
+		++_curFrame;
 	}
 }
 
@@ -584,7 +590,7 @@ void ConversationCel::readData(Common::SeekableReadStream &stream) {
 }
 
 bool ConversationCel::isVideoDonePlaying() {
-	return _curFrame == _lastFrame && _nextFrameTime <= g_nancy->getTotalPlayTime();
+	return _curFrame >= _lastFrame && _nextFrameTime <= g_nancy->getTotalPlayTime();
 }
 	
 } // End of namespace Action
diff --git a/engines/nancy/action/conversation.h b/engines/nancy/action/conversation.h
index ddb066f5076..cdcb57f506a 100644
--- a/engines/nancy/action/conversation.h
+++ b/engines/nancy/action/conversation.h
@@ -166,9 +166,9 @@ public:
 	uint16 _frameTime = 0;
 	uint _videoFormat = kLargeVideoFormat;
 	uint16 _firstFrame = 0;
-	int16 _lastFrame = 0;
+	uint16 _lastFrame = 0;
 
-	int _curFrame = -1; 
+	uint _curFrame = 0; 
 	uint16 _nextFrameTime = 0;
 
 	// We use the built-in RenderObject for the body




More information about the Scummvm-git-logs mailing list