[Scummvm-git-logs] scummvm master -> 07726745d47b1a1468abf7befb1f04756c908f7f

elasota noreply at scummvm.org
Tue Jun 13 02:45:26 UTC 2023


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

Summary:
89c8f002c0 MTROPOLIS: Better fix for corrupted mToon graphics in MTI
07726745d4 MTROPOLIS: Implement Miniscript global references and MTI Shanghai minigame


Commit: 89c8f002c0a1a4ce1058214c35300b9d553765ca
    https://github.com/scummvm/scummvm/commit/89c8f002c0a1a4ce1058214c35300b9d553765ca
Author: elasota (ejlasota at gmail.com)
Date: 2023-06-12T22:44:59-04:00

Commit Message:
MTROPOLIS: Better fix for corrupted mToon graphics in MTI

Changed paths:
    engines/mtropolis/assets.cpp
    engines/mtropolis/assets.h


diff --git a/engines/mtropolis/assets.cpp b/engines/mtropolis/assets.cpp
index 0284cf58bf6..896c57bd2c1 100644
--- a/engines/mtropolis/assets.cpp
+++ b/engines/mtropolis/assets.cpp
@@ -208,7 +208,7 @@ void CachedMToon::decompressFrames(const Common::Array<uint8> &data) {
 }
 
 template<class TNumber, uint32 TLiteralMask, uint32 TTransparentRowSkipMask>
-bool CachedMToon::decompressMToonRLE(const RleFrame &frame, const Common::Array<TNumber> &coefsArray, Graphics::ManagedSurface &surface, bool isBottomUp, uint hackFlags) {
+bool CachedMToon::decompressMToonRLE(const RleFrame &frame, const Common::Array<TNumber> &coefsArray, Graphics::ManagedSurface &surface, bool isBottomUp, bool isKeyFrame, uint hackFlags) {
 	assert(sizeof(TNumber) == surface.format.bytesPerPixel);
 
 	size_t size = coefsArray.size();
@@ -247,19 +247,13 @@ bool CachedMToon::decompressMToonRLE(const RleFrame &frame, const Common::Array<
 				// Vertical skip
 				uint32 skipAmount = transparentCountCode - TTransparentRowSkipMask;
 
-				// Not sure why this special code exists, but several mToons in MTI use it, such as the pants and shoe pull-outs
-				// in the treasure chest in the first area, and the Hispaniola TV in the MPZ-1000.
-				//
-				// The pants graphic depends on avoiding the x=0 here.
-				if (skipAmount != (TTransparentRowSkipMask - 2)) {
-					y += skipAmount;
-					x = 0;
-					if (y < h) {
-						rowData = static_cast<TNumber *>(surface.getBasePtr(0, isBottomUp ? (h - 1 - y) : y));
-						continue;
-					} else {
-						break;
-					}
+				y += skipAmount;
+				x = 0;
+				if (y < h) {
+					rowData = static_cast<TNumber *>(surface.getBasePtr(0, isBottomUp ? (h - 1 - y) : y));
+					continue;
+				} else {
+					break;
 				}
 			} else {
 				// Horizontal skip
@@ -278,7 +272,7 @@ bool CachedMToon::decompressMToonRLE(const RleFrame &frame, const Common::Array<
 			size -= numLiterals;
 			x += numLiterals;
 		} else {
-			// Literals
+			// Run
 			const size_t numCopies = rleCode;
 			if (numCopies > remainingInRow || size == 0)
 				return false;
@@ -288,6 +282,21 @@ bool CachedMToon::decompressMToonRLE(const RleFrame &frame, const Common::Array<
 			coefs++;
 			size--;
 			x += numCopies;
+
+			if (size >= 2) {
+				// Handle some strange cases in MTI that appear to be caused by some kind of mToon
+				// encoder RLE flush problem: Numerous mToons have a 0-length RLE run after a max-length
+				// run.  In most cases, the repeated value is 0, which has no effect, but in some cases
+				// this causes decode problems because the value is non-zero and gets decoded as a skip.
+				//
+				// In particular, it causes problems with the MPZ-1000 Hispaniola TV, the shoe and pants
+				// pull-outs in the chest in the first area, and the target animations in the Hispaniola
+				// cannon minigame.
+				if (numCopies == (TLiteralMask - 1) && coefs[0] == 0 && coefs[1] == repeatedValue) {
+					coefs += 2;
+					size -= 2;
+				}
+			}
 		}
 
 		if (x == w) {
@@ -310,13 +319,15 @@ void CachedMToon::decompressRLEFrameToImage(size_t frameIndex, Graphics::Managed
 
 	bool isBottomUp = (_metadata->imageFormat == MToonMetadata::kImageFormatWindows);
 
+	bool isKeyFrame = _metadata->frames[frameIndex].isKeyFrame;
+
 	bool decompressedOK = false;
 	if (_rleOptimizedFormat.bytesPerPixel == 4) {
-		decompressedOK = decompressMToonRLE<uint32, 0x80000000u, 0x80000000u>(_rleData[frameIndex], _rleData[frameIndex].data32, surface, isBottomUp, _hackFlags);
+		decompressedOK = decompressMToonRLE<uint32, 0x80000000u, 0x80000000u>(_rleData[frameIndex], _rleData[frameIndex].data32, surface, isBottomUp, isKeyFrame, _hackFlags);
 	} else if (_rleOptimizedFormat.bytesPerPixel == 2) {
-		decompressedOK = decompressMToonRLE<uint16, 0x8000u, 0x8000u>(_rleData[frameIndex], _rleData[frameIndex].data16, surface, isBottomUp, _hackFlags);
+		decompressedOK = decompressMToonRLE<uint16, 0x8000u, 0x8000u>(_rleData[frameIndex], _rleData[frameIndex].data16, surface, isBottomUp, isKeyFrame, _hackFlags);
 	} else if (_rleOptimizedFormat.bytesPerPixel == 1) {
-		decompressedOK = decompressMToonRLE<uint8, 0x80u, 0x80u>(_rleData[frameIndex], _rleData[frameIndex].data8, surface, isBottomUp, _hackFlags);
+		decompressedOK = decompressMToonRLE<uint8, 0x80u, 0x80u>(_rleData[frameIndex], _rleData[frameIndex].data8, surface, isBottomUp, isKeyFrame, _hackFlags);
 	} else
 		error("Unknown mToon encoding");
 
@@ -652,12 +663,14 @@ void CachedMToon::getOrRenderFrame(uint32 prevFrame, uint32 targetFrame, Common:
 		bool isBottomUp = (_metadata->imageFormat == MToonMetadata::kImageFormatWindows);
 
 		for (size_t i = firstFrameToRender; i <= targetFrame; i++) {
+			bool isKeyFrame = _metadata->frames[i].isKeyFrame;
+
 			if (_rleOptimizedFormat.bytesPerPixel == 1)
-				decompressMToonRLE<uint8, 0x80u, 0x80u>(_rleData[i], _rleData[i].data8, *surface, isBottomUp, _hackFlags);
+				decompressMToonRLE<uint8, 0x80u, 0x80u>(_rleData[i], _rleData[i].data8, *surface, isBottomUp, isKeyFrame, _hackFlags);
 			else if (_rleOptimizedFormat.bytesPerPixel == 2)
-				decompressMToonRLE<uint16, 0x8000u, 0x8000u>(_rleData[i], _rleData[i].data16, *surface, isBottomUp, _hackFlags);
+				decompressMToonRLE<uint16, 0x8000u, 0x8000u>(_rleData[i], _rleData[i].data16, *surface, isBottomUp, isKeyFrame, _hackFlags);
 			else if (_rleOptimizedFormat.bytesPerPixel == 4)
-				decompressMToonRLE<uint32, 0x80000000u, 0x80000000u>(_rleData[i], _rleData[i].data32, *surface, isBottomUp, _hackFlags);
+				decompressMToonRLE<uint32, 0x80000000u, 0x80000000u>(_rleData[i], _rleData[i].data32, *surface, isBottomUp, isKeyFrame, _hackFlags);
 		}
 	}
 }
diff --git a/engines/mtropolis/assets.h b/engines/mtropolis/assets.h
index 1a4b7218acb..85e17331eb3 100644
--- a/engines/mtropolis/assets.h
+++ b/engines/mtropolis/assets.h
@@ -152,7 +152,7 @@ private:
 	void rleReformat(RleFrame &frame, const Common::Array<TSrcNumber> &srcData, const Graphics::PixelFormat &srcFormatRef, Common::Array<TDestNumber> &destData, const Graphics::PixelFormat &destFormatRef, uint hackFlags);
 
 	template<class TNumber, uint32 TLiteralMask, uint32 TTransparentRowSkipMask>
-	static bool decompressMToonRLE(const RleFrame &frame, const Common::Array<TNumber> &coefsArray, Graphics::ManagedSurface &surface, bool isBottomUp, uint hackFlags);
+	static bool decompressMToonRLE(const RleFrame &frame, const Common::Array<TNumber> &coefsArray, Graphics::ManagedSurface &surface, bool isBottomUp, bool isKeyFrame, uint hackFlags);
 
 	Common::Array<RleFrame> _rleData;
 	bool _isRLETemporalCompressed;


Commit: 07726745d47b1a1468abf7befb1f04756c908f7f
    https://github.com/scummvm/scummvm/commit/07726745d47b1a1468abf7befb1f04756c908f7f
Author: elasota (ejlasota at gmail.com)
Date: 2023-06-12T22:44:59-04:00

Commit Message:
MTROPOLIS: Implement Miniscript global references and MTI Shanghai minigame

Changed paths:
    engines/mtropolis/miniscript.cpp
    engines/mtropolis/miniscript.h
    engines/mtropolis/plugin/mti.cpp
    engines/mtropolis/plugin/mti.h
    engines/mtropolis/plugin/mti_data.cpp
    engines/mtropolis/plugin/mti_data.h


diff --git a/engines/mtropolis/miniscript.cpp b/engines/mtropolis/miniscript.cpp
index 122f719bdf0..50a2afa3dc5 100644
--- a/engines/mtropolis/miniscript.cpp
+++ b/engines/mtropolis/miniscript.cpp
@@ -28,6 +28,35 @@
 
 namespace MTropolis {
 
+class MiniscriptInstructionParserFeedback : public IMiniscriptInstructionParserFeedback {
+public:
+	explicit MiniscriptInstructionParserFeedback(Common::Array<MiniscriptReferences::GlobalRef> *globalRefs);
+
+	uint registerGlobalGUIDIndex(uint32 guid) override;
+
+private:
+	Common::Array<MiniscriptReferences::GlobalRef> *_globalRefs;
+};
+
+MiniscriptInstructionParserFeedback::MiniscriptInstructionParserFeedback(Common::Array<MiniscriptReferences::GlobalRef> *globalRefs) : _globalRefs(globalRefs) {
+}
+
+uint MiniscriptInstructionParserFeedback::registerGlobalGUIDIndex(uint32 guid) {
+	for (uint i = 0; i < _globalRefs->size(); i++) {
+		if ((*_globalRefs)[i].guid == guid)
+			return i;
+	}
+
+	uint newIndex = _globalRefs->size();
+
+	MiniscriptReferences::GlobalRef globalRef;
+	globalRef.guid = guid;
+
+	_globalRefs->push_back(globalRef);
+
+	return newIndex;
+}
+
 bool miniscriptEvaluateTruth(const DynamicValue &value) {
 	// NOTE: Comparing equal to "true" only passes for 1 exactly, but for conditions,
 	// any non-zero value is true.
@@ -45,21 +74,29 @@ bool miniscriptEvaluateTruth(const DynamicValue &value) {
 	}
 }
 
+IMiniscriptInstructionParserFeedback::~IMiniscriptInstructionParserFeedback() {
+}
+
 MiniscriptInstruction::~MiniscriptInstruction() {
 }
 
 MiniscriptReferences::LocalRef::LocalRef() : guid(0) {
 }
 
-MiniscriptReferences::MiniscriptReferences(const Common::Array<LocalRef> &localRefs) : _localRefs(localRefs) {
+MiniscriptReferences::GlobalRef::GlobalRef() : guid(0) {
+}
+
+MiniscriptReferences::MiniscriptReferences(const Common::Array<LocalRef> &localRefs, const Common::Array<GlobalRef> &globalRefs) : _localRefs(localRefs), _globalRefs(globalRefs) {
 }
 
 void MiniscriptReferences::linkInternalReferences(ObjectLinkingScope *scope) {
 	// Resolve using name lookups since there are some known cases where the GUID is broken
 	// e.g. "bArriveFromCutScene" in "Set bArriveFromCutScene on PE" in Obsidian
-	for (Common::Array<LocalRef>::iterator it = _localRefs.begin(), itEnd = _localRefs.end(); it != itEnd; ++it) {
+	for (Common::Array<LocalRef>::iterator it = _localRefs.begin(), itEnd = _localRefs.end(); it != itEnd; ++it)
 		it->resolution = scope->resolve(it->guid, it->name, false);
-	}
+
+	for (Common::Array<GlobalRef>::iterator it = _globalRefs.begin(), itEnd = _globalRefs.end(); it != itEnd; ++it)
+		it->resolution = scope->resolve(it->guid, "", true);
 }
 
 void MiniscriptReferences::visitInternalReferences(IStructuralReferenceVisitor *visitor) {
@@ -77,6 +114,21 @@ void MiniscriptReferences::visitInternalReferences(IStructuralReferenceVisitor *
 			}
 		}
 	}
+
+	for (GlobalRef &ref : _globalRefs) {
+		Common::SharedPtr<RuntimeObject> obj = ref.resolution.lock();
+		if (obj) {
+			if (obj->isModifier()) {
+				Common::WeakPtr<Modifier> mod = obj.staticCast<Modifier>();
+				visitor->visitWeakModifierRef(mod);
+				ref.resolution = mod;
+			} else if (obj->isStructural()) {
+				Common::WeakPtr<Structural> struc = obj.staticCast<Structural>();
+				visitor->visitWeakStructuralRef(struc);
+				ref.resolution = struc;
+			}
+		}
+	}
 }
 
 Common::WeakPtr<RuntimeObject> MiniscriptReferences::getRefByIndex(uint index) const {
@@ -85,6 +137,13 @@ Common::WeakPtr<RuntimeObject> MiniscriptReferences::getRefByIndex(uint index) c
 	return _localRefs[index].resolution;
 }
 
+Common::WeakPtr<RuntimeObject> MiniscriptReferences::getGlobalRefByIndex(uint index) const {
+	if (index >= _globalRefs.size())
+		return Common::WeakPtr<RuntimeObject>();
+	return _globalRefs[index].resolution;
+	
+}
+
 MiniscriptProgram::MiniscriptProgram(const Common::SharedPtr<Common::Array<uint8> > &programData, const Common::Array<MiniscriptInstruction *> &instructions, const Common::Array<Attribute> &attributes)
 	: _programData(programData), _instructions(instructions), _attributes(attributes) {
 }
@@ -105,18 +164,18 @@ const Common::Array<MiniscriptProgram::Attribute> &MiniscriptProgram::getAttribu
 
 template<class T>
 struct MiniscriptInstructionLoader {
-	static bool loadInstruction(void *dest, uint32 instrFlags, Data::DataReader &instrDataReader);
+	static bool loadInstruction(void *dest, uint32 instrFlags, Data::DataReader &instrDataReader, IMiniscriptInstructionParserFeedback &feedback);
 };
 
 template<class T>
-bool MiniscriptInstructionLoader<T>::loadInstruction(void *dest, uint32 instrFlags, Data::DataReader &instrDataReader) {
+bool MiniscriptInstructionLoader<T>::loadInstruction(void *dest, uint32 instrFlags, Data::DataReader &instrDataReader, IMiniscriptInstructionParserFeedback &feedback) {
 	// Default loader for simple instructions with no private data
 	new (dest) T();
 	return true;
 }
 
 template<>
-bool MiniscriptInstructionLoader<MiniscriptInstructions::Send>::loadInstruction(void *dest, uint32 instrFlags, Data::DataReader &instrDataReader) {
+bool MiniscriptInstructionLoader<MiniscriptInstructions::Send>::loadInstruction(void *dest, uint32 instrFlags, Data::DataReader &instrDataReader, IMiniscriptInstructionParserFeedback &feedback) {
 	Data::Event dataEvent;
 	if (!dataEvent.load(instrDataReader))
 		return false;
@@ -135,7 +194,7 @@ bool MiniscriptInstructionLoader<MiniscriptInstructions::Send>::loadInstruction(
 }
 
 template<>
-bool MiniscriptInstructionLoader<MiniscriptInstructions::BuiltinFunc>::loadInstruction(void *dest, uint32 instrFlags, Data::DataReader &instrDataReader) {
+bool MiniscriptInstructionLoader<MiniscriptInstructions::BuiltinFunc>::loadInstruction(void *dest, uint32 instrFlags, Data::DataReader &instrDataReader, IMiniscriptInstructionParserFeedback &feedback) {
 	uint32 functionID;
 	if (!instrDataReader.readU32(functionID))
 		return false;
@@ -148,7 +207,7 @@ bool MiniscriptInstructionLoader<MiniscriptInstructions::BuiltinFunc>::loadInstr
 }
 
 template<>
-bool MiniscriptInstructionLoader<MiniscriptInstructions::GetChild>::loadInstruction(void *dest, uint32 instrFlags, Data::DataReader &instrDataReader) {
+bool MiniscriptInstructionLoader<MiniscriptInstructions::GetChild>::loadInstruction(void *dest, uint32 instrFlags, Data::DataReader &instrDataReader, IMiniscriptInstructionParserFeedback &feedback) {
 	uint32 childAttribute;
 	if (!instrDataReader.readU32(childAttribute))
 		return false;
@@ -158,7 +217,7 @@ bool MiniscriptInstructionLoader<MiniscriptInstructions::GetChild>::loadInstruct
 }
 
 template<>
-bool MiniscriptInstructionLoader<MiniscriptInstructions::PushGlobal>::loadInstruction(void *dest, uint32 instrFlags, Data::DataReader &instrDataReader) {
+bool MiniscriptInstructionLoader<MiniscriptInstructions::PushGlobal>::loadInstruction(void *dest, uint32 instrFlags, Data::DataReader &instrDataReader, IMiniscriptInstructionParserFeedback &feedback) {
 	uint32 globalID;
 	if (!instrDataReader.readU32(globalID))
 		return false;
@@ -168,7 +227,7 @@ bool MiniscriptInstructionLoader<MiniscriptInstructions::PushGlobal>::loadInstru
 }
 
 template<>
-bool MiniscriptInstructionLoader<MiniscriptInstructions::Jump>::loadInstruction(void *dest, uint32 instrFlags, Data::DataReader &instrDataReader) {
+bool MiniscriptInstructionLoader<MiniscriptInstructions::Jump>::loadInstruction(void *dest, uint32 instrFlags, Data::DataReader &instrDataReader, IMiniscriptInstructionParserFeedback &feedback) {
 	uint32 jumpFlags, unknown, instrOffset;
 	if (!instrDataReader.readU32(jumpFlags) || !instrDataReader.readU32(unknown) || !instrDataReader.readU32(instrOffset))
 		return false;
@@ -185,7 +244,7 @@ bool MiniscriptInstructionLoader<MiniscriptInstructions::Jump>::loadInstruction(
 }
 
 template<>
-bool MiniscriptInstructionLoader<MiniscriptInstructions::PushValue>::loadInstruction(void *dest, uint32 instrFlags, Data::DataReader &instrDataReader) {
+bool MiniscriptInstructionLoader<MiniscriptInstructions::PushValue>::loadInstruction(void *dest, uint32 instrFlags, Data::DataReader &instrDataReader, IMiniscriptInstructionParserFeedback &feedback) {
 	uint16 dataType;
 	if (!instrDataReader.readU16(dataType))
 		return false;
@@ -217,7 +276,9 @@ bool MiniscriptInstructionLoader<MiniscriptInstructions::PushValue>::loadInstruc
 		if (!instrDataReader.readU32(refValue))
 			return false;
 
-		new (dest) MiniscriptInstructions::PushValue(MiniscriptInstructions::PushValue::kDataTypeGlobalRef, &refValue, (instrFlags & 1) != 0);
+		uint32 indexedRef = feedback.registerGlobalGUIDIndex(refValue);
+
+		new (dest) MiniscriptInstructions::PushValue(MiniscriptInstructions::PushValue::kDataTypeGlobalRef, &indexedRef, (instrFlags & 1) != 0);
 	} else if (dataType == 0x1d) {
 		MiniscriptInstructions::PushValue::Label label;
 		if (!instrDataReader.readU32(label.superGroup) || !instrDataReader.readU32(label.id))
@@ -231,7 +292,7 @@ bool MiniscriptInstructionLoader<MiniscriptInstructions::PushValue>::loadInstruc
 }
 
 template<>
-bool MiniscriptInstructionLoader<MiniscriptInstructions::PushString>::loadInstruction(void *dest, uint32 instrFlags, Data::DataReader &instrDataReader) {
+bool MiniscriptInstructionLoader<MiniscriptInstructions::PushString>::loadInstruction(void *dest, uint32 instrFlags, Data::DataReader &instrDataReader, IMiniscriptInstructionParserFeedback &feedback) {
 	uint16 strLength;
 	if (!instrDataReader.readU16(strLength))
 		return false;
@@ -247,14 +308,14 @@ bool MiniscriptInstructionLoader<MiniscriptInstructions::PushString>::loadInstru
 }
 
 struct SIMiniscriptInstructionFactory {
-	bool (*create)(void *dest, uint32 instrFlags, Data::DataReader &instrDataReader, MiniscriptInstruction *&outMiniscriptInstructionPtr);
+	bool (*create)(void *dest, uint32 instrFlags, Data::DataReader &instrDataReader, MiniscriptInstruction *&outMiniscriptInstructionPtr, IMiniscriptInstructionParserFeedback &feedback);
 	void (*getSizeAndAlignment)(size_t &outSize, size_t &outAlignment);
 };
 
 template<class T>
 class MiniscriptInstructionFactory {
 public:
-	static bool create(void *dest, uint32 instrFlags, Data::DataReader &instrDataReader, MiniscriptInstruction *&outMiniscriptInstructionPtr);
+	static bool create(void *dest, uint32 instrFlags, Data::DataReader &instrDataReader, MiniscriptInstruction *&outMiniscriptInstructionPtr, IMiniscriptInstructionParserFeedback &feedback);
 	static void getSizeAndAlignment(size_t &outSize, size_t &outAlignment);
 
 	static SIMiniscriptInstructionFactory *getInstance();
@@ -264,8 +325,8 @@ private:
 };
 
 template<class T>
-bool MiniscriptInstructionFactory<T>::create(void *dest, uint32 instrFlags, Data::DataReader &instrDataReader, MiniscriptInstruction *&outMiniscriptInstructionPtr) {
-	if (!MiniscriptInstructionLoader<T>::loadInstruction(dest, instrFlags, instrDataReader))
+bool MiniscriptInstructionFactory<T>::create(void *dest, uint32 instrFlags, Data::DataReader &instrDataReader, MiniscriptInstruction *&outMiniscriptInstructionPtr, IMiniscriptInstructionParserFeedback &feedback) {
+	if (!MiniscriptInstructionLoader<T>::loadInstruction(dest, instrFlags, instrDataReader, feedback))
 		return false;
 
 	outMiniscriptInstructionPtr = static_cast<MiniscriptInstruction *>(static_cast<T *>(dest));
@@ -295,6 +356,7 @@ MiniscriptParser::InstructionData::InstructionData()
 
 bool MiniscriptParser::parse(const Data::MiniscriptProgram &program, Common::SharedPtr<MiniscriptProgram> &outProgram, Common::SharedPtr<MiniscriptReferences> &outReferences) {
 	Common::Array<MiniscriptReferences::LocalRef> localRefs;
+	Common::Array<MiniscriptReferences::GlobalRef> globalRefs;
 	Common::Array<MiniscriptProgram::Attribute> attributes;
 	Common::SharedPtr<Common::Array<uint8> > programDataPtr;
 	Common::Array<MiniscriptInstruction *> miniscriptInstructions;
@@ -302,7 +364,7 @@ bool MiniscriptParser::parse(const Data::MiniscriptProgram &program, Common::Sha
 	// If the program is empty then just return an empty program
 	if (program.bytecode.size() == 0 || program.numOfInstructions == 0) {
 		outProgram = Common::SharedPtr<MiniscriptProgram>(new MiniscriptProgram(programDataPtr, miniscriptInstructions, attributes));
-		outReferences = Common::SharedPtr<MiniscriptReferences>(new MiniscriptReferences(localRefs));
+		outReferences = Common::SharedPtr<MiniscriptReferences>(new MiniscriptReferences(localRefs, globalRefs));
 		return true;
 	}
 
@@ -377,6 +439,8 @@ bool MiniscriptParser::parse(const Data::MiniscriptProgram &program, Common::Sha
 
 	miniscriptInstructions.resize(program.numOfInstructions);
 
+	MiniscriptInstructionParserFeedback parserFeedback(&globalRefs);
+
 	// Create instructions
 	for (size_t i = 0; i < program.numOfInstructions; i++) {
 		const InstructionData &rawInstruction = rawInstructions[i];
@@ -388,7 +452,7 @@ bool MiniscriptParser::parse(const Data::MiniscriptProgram &program, Common::Sha
 		Common::MemoryReadStreamEndian instrContentsStream(static_cast<const byte *>(dataLoc), rawInstruction.contents.size(), reader.isBigEndian());
 		Data::DataReader instrContentsReader(0, instrContentsStream, reader.getProjectFormat());
 
-		if (!rawInstruction.instrFactory->create(&programData[baseOffset + rawInstruction.pdPosition], rawInstruction.flags, instrContentsReader, miniscriptInstructions[i])) {
+		if (!rawInstruction.instrFactory->create(&programData[baseOffset + rawInstruction.pdPosition], rawInstruction.flags, instrContentsReader, miniscriptInstructions[i], parserFeedback)) {
 			// Destroy any already-created instructions
 			for (size_t di = 0; di < i; di++) {
 				miniscriptInstructions[i - 1 - di]->~MiniscriptInstruction();
@@ -400,7 +464,7 @@ bool MiniscriptParser::parse(const Data::MiniscriptProgram &program, Common::Sha
 
 	// Done
 	outProgram = Common::SharedPtr<MiniscriptProgram>(new MiniscriptProgram(programDataPtr, miniscriptInstructions, attributes));
-	outReferences = Common::SharedPtr<MiniscriptReferences>(new MiniscriptReferences(localRefs));
+	outReferences = Common::SharedPtr<MiniscriptReferences>(new MiniscriptReferences(localRefs, globalRefs));
 
 	return true;
 }
@@ -1662,8 +1726,8 @@ MiniscriptInstructionOutcome PushValue::execute(MiniscriptThread *thread) const
 		value.setObject(ObjectReference(thread->getRefs()->getRefByIndex(_value.ref)));
 		break;
 	case DataType::kDataTypeGlobalRef:
-		thread->error("Global references are not implemented");
-		return kMiniscriptInstructionOutcomeFailed;
+		value.setObject(ObjectReference(thread->getRefs()->getGlobalRefByIndex(_value.ref)));
+		break;
 	case DataType::kDataTypeLabel: {
 		MTropolis::Label label;
 		label.id = _value.lbl.id;
diff --git a/engines/mtropolis/miniscript.h b/engines/mtropolis/miniscript.h
index 7ff7c1a0193..49c06fddfea 100644
--- a/engines/mtropolis/miniscript.h
+++ b/engines/mtropolis/miniscript.h
@@ -40,6 +40,13 @@ public:
 	virtual MiniscriptInstructionOutcome execute(MiniscriptThread *thread) const = 0;
 };
 
+class IMiniscriptInstructionParserFeedback {
+public:
+	virtual ~IMiniscriptInstructionParserFeedback();
+
+	virtual uint registerGlobalGUIDIndex(uint32 guid) = 0;
+};
+
 class MiniscriptReferences {
 public:
 	struct LocalRef {
@@ -50,15 +57,24 @@ public:
 		Common::WeakPtr<RuntimeObject> resolution;
 	};
 
-	explicit MiniscriptReferences(const Common::Array<LocalRef> &localRefs);
+	struct GlobalRef {
+		GlobalRef();
+
+		uint32 guid;
+		Common::WeakPtr<RuntimeObject> resolution;
+	};
+
+	explicit MiniscriptReferences(const Common::Array<LocalRef> &localRefs, const Common::Array<GlobalRef> &globalRefs);
 
 	void linkInternalReferences(ObjectLinkingScope *scope);
 	void visitInternalReferences(IStructuralReferenceVisitor *visitor);
 
 	Common::WeakPtr<RuntimeObject> getRefByIndex(uint index) const;
+	Common::WeakPtr<RuntimeObject> getGlobalRefByIndex(uint index) const;
 
 private:
 	Common::Array<LocalRef> _localRefs;
+	Common::Array<GlobalRef> _globalRefs;
 
 };
 
diff --git a/engines/mtropolis/plugin/mti.cpp b/engines/mtropolis/plugin/mti.cpp
index 674442c563f..3315f4e9f12 100644
--- a/engines/mtropolis/plugin/mti.cpp
+++ b/engines/mtropolis/plugin/mti.cpp
@@ -26,21 +26,147 @@
 
 #include "mtropolis/miniscript.h"
 
+#include "common/random.h"
+
 namespace MTropolis {
 
 namespace MTI {
+	
+
+/*
+Board layout:
+
+Layer 0:
+     0  1  2  3  4  5  6  7  8  9 10 11 12
+  +-----+-----+-----+-----+-----+-----+
+0 +  0  |  1  |  2  |  3  |  4  |  5  |
+1 +-----+-----+-----+-----+-----+-----+
+2 +-----+  7  |  8  |  9  | 10  +-----+-----+
+3 |  6  +-----+-----+-----+-----+ 11  | 12  |
+4 +-----+ 13  | 14  | 15  | 16  +-----+-----+
+5 +-----+-----+-----+-----+-----+-----+
+6 + 17  | 18  | 19  | 20  | 21  | 22  |
+  +-----+-----+-----+-----+-----+-----+
+
+Layer 1:
+     0  1  2  3  4  5  6  7  8  9 10 11 12
+  
+0 
+1             +-----+-----+
+2             | 23  | 24  |
+3             +-----+-----+
+4             | 25  | 26  |
+5             +-----+-----+
+6
+
+Layer 2:
+     0  1  2  3  4  5  6  7  8  9 10 11 12
+  
+0 
+1             
+2                +-----+
+3                | 27  |
+4                +-----+
+5
+6
+
+*/
+
+ShanghaiModifier::TileCoordinate ShanghaiModifier::_tileCoordinates[ShanghaiModifier::kNumTiles] = {
+	{0, 0, 0},
+	{2, 0, 0},
+	{4, 0, 0},
+	{6, 0, 0},
+	{8, 0, 0},
+	{10, 0, 0},
+
+	{0, 3, 0},
+	{2, 2, 0},
+	{4, 2, 0},
+	{6, 2, 0},
+	{8, 2, 0},
+	{10, 3, 0},
+	{12, 3, 0},
+
+	{2, 4, 0},
+	{4, 4, 0},
+	{6, 4, 0},
+	{8, 4, 0},
+
+	{0, 6, 0},
+	{2, 6, 0},
+	{4, 6, 0},
+	{6, 6, 0},
+	{8, 6, 0},
+	{10, 6, 0},
+
+	{4, 2, 0},
+	{6, 2, 0},
+
+	{4, 4, 1},
+	{6, 4, 1},
+
+	{5, 3, 2},
+};
 
 ShanghaiModifier::ShanghaiModifier() {
+	for (uint x = 0; x < kBoardSizeX; x++)
+		for (uint y = 0; y < kBoardSizeY; y++)
+			for (uint z = 0; z < kBoardSizeZ; z++)
+				_tileAtCoordinate[x][y][z] = -1;
+
+	for (uint i = 0; i < kNumTiles; i++) {
+		const TileCoordinate &coord = _tileCoordinates[i];
+		assert(coord.x < kBoardSizeX);
+		assert(coord.y < kBoardSizeY);
+		assert(coord.z < kBoardSizeZ);
+		_tileAtCoordinate[coord.x][coord.y][coord.z] = i;
+	}
 }
 
 ShanghaiModifier::~ShanghaiModifier() {
 }
 
 bool ShanghaiModifier::respondsToEvent(const Event &evt) const {
+	if (_resetTileSetWhen.respondsTo(evt))
+		return true;
+
 	return false;
 }
 
 VThreadState ShanghaiModifier::consumeMessage(Runtime *runtime, const Common::SharedPtr<MessageProperties> &msg) {
+	if (_resetTileSetWhen.respondsTo(msg->getEvent())) {
+		uint tileFaces[kNumTiles];
+
+		resetTiles(*runtime->getRandom(), tileFaces);
+
+		Modifier *varMod = this->_tileSetRef.resolution.lock().get();
+
+		if (varMod == nullptr || !varMod->isVariable()) {
+			warning("Shanghai reset var ref was unavailable");
+			return kVThreadError;
+		}
+
+		VariableModifier *var = static_cast<VariableModifier *>(varMod);
+
+		Common::SharedPtr<DynamicList> list(new DynamicList());
+
+		for (uint i = 0; i < kNumTiles; i++) {
+			DynamicValue tileValue;
+			tileValue.setInt(tileFaces[i]);
+
+			list->setAtIndex(i, tileValue);
+		}
+
+		DynamicValue listValue;
+		listValue.setList(list);
+
+		MiniscriptThread thread(runtime, nullptr, nullptr, nullptr, this);
+		var->varSetValue(&thread, listValue);
+
+		return kVThreadReturn;
+	}
+
 	return kVThreadReturn;
 }
 
@@ -48,9 +174,217 @@ void ShanghaiModifier::disable(Runtime *runtime) {
 }
 
 bool ShanghaiModifier::load(const PlugInModifierLoaderContext &context, const Data::MTI::ShanghaiModifier &data) {
+	if (data.resetWhen.type != Data::PlugInTypeTaggedValue::kEvent)
+		return false;
+
+	if (!_resetTileSetWhen.load(data.resetWhen.value.asEvent))
+		return false;
+
+	if (data.tileSetVar.type != Data::PlugInTypeTaggedValue::kVariableReference)
+		return false;
+
+	_tileSetRef = VarReference(data.tileSetVar.value.asVarRefGUID, "");
+
+	return true;
+}
+
+void ShanghaiModifier::linkInternalReferences(ObjectLinkingScope *scope) {
+	_tileSetRef.linkInternalReferences(scope);
+}
+
+void ShanghaiModifier::visitInternalReferences(IStructuralReferenceVisitor *visitor) {
+	_tileSetRef.visitInternalReferences(visitor);
+}
+
+void ShanghaiModifier::resetTiles(Common::RandomSource &rng, uint (&tileFaces)[kNumTiles]) const {
+	uint possibleFaces[kNumFaces];
+	uint numPossibleFaces = kNumFaces;
+
+	for (uint i = 0; i < kNumFaces; i++)
+		possibleFaces[i] = i + 1;
+
+	uint facesToInsert[kNumTiles / 2];
+	uint numFacesToInsert = kNumTiles / 2;
+
+	// Pick random faces, each one gets inserted twice
+	for (uint i = 0; i < kNumTiles / 4u; i++) {
+		uint faceToInsert = selectAndRemoveOne(rng, possibleFaces, numPossibleFaces);
+		facesToInsert[i * 2 + 0] = faceToInsert;
+		facesToInsert[i * 2 + 1] = faceToInsert;
+	}
+
+	// We build the board by adding all tiles and then randomly picking 2 exposed tiles and
+	// assigning them a matching pair.  A pair is only valid if the resulting board state has
+	// valid moves.
+	BoardState_t boardState = emptyBoardState();
+	for (uint i = 0; i < kNumTiles; i++)
+		boardState = boardState | boardStateBit(i);
+
+	for (uint pair = 0; pair < kNumTiles / 2u; pair++) {
+		uint exposedTiles[kNumTiles];
+		uint numExposedTiles = 0;
+
+		for (uint i = 0; i < kNumTiles; i++) {
+			if (boardState & boardStateBit(i)) {
+				if (tileIsExposed(boardState, i))
+					exposedTiles[numExposedTiles++] = i;
+			}
+		}
+
+		uint firstExposedTile = selectAndRemoveOne(rng, exposedTiles, numExposedTiles);
+
+		BoardState_t withFirstRemoved = boardState ^ boardStateBit(firstExposedTile);
+
+		uint secondExposedTile = selectAndRemoveOne(rng, exposedTiles, numExposedTiles);
+		BoardState_t withBothRemoved = withFirstRemoved ^ boardStateBit(secondExposedTile);
+
+		if (numExposedTiles > 0) {
+			// If this isn't the last move, validate that this won't result in a stuck board state (e.g. only one tile exposed)
+			// If it would result in such a state, pick a different move.
+			for (;;) {
+				if (boardStateHasValidMove(withBothRemoved))
+					break;
+
+				if (numExposedTiles == 0) {
+					error("Shanghai board creation failed, board state was %x, removed %u to produce board state %x", static_cast<uint>(boardState), firstExposedTile, static_cast<uint>(withFirstRemoved));
+					break;
+				}
+
+				secondExposedTile = selectAndRemoveOne(rng, exposedTiles, numExposedTiles);
+				withBothRemoved = withFirstRemoved ^ boardStateBit(secondExposedTile);
+			}
+		}
+
+		boardState = withBothRemoved;
+
+		uint faceToInsert = selectAndRemoveOne(rng, facesToInsert, numFacesToInsert);
+		tileFaces[firstExposedTile] = faceToInsert;
+		tileFaces[secondExposedTile] = faceToInsert;
+	}
+}
+
+uint ShanghaiModifier::selectAndRemoveOne(Common::RandomSource &rng, uint *valuesList, uint &listSize) {
+	if (listSize == 0) {
+		error("Internal error: selectAndRemoveOne ran out of values");
+		return 0;
+	}
+
+	if (listSize == 1) {
+		listSize = 0;
+		return valuesList[0];
+	}
+
+	uint selectedIndex = rng.getRandomNumber(listSize - 1);
+	uint selectedValue = valuesList[selectedIndex];
+
+	valuesList[selectedIndex] = valuesList[listSize - 1];
+	listSize--;
+
+	return selectedValue;
+}
+
+bool ShanghaiModifier::boardStateHasValidMove(BoardState_t boardState) const {
+	uint numExposedTiles = 0;
+	for (uint i = 0; i < kNumTiles; i++) {
+		if (boardState & boardStateBit(i)) {
+			if (tileIsExposed(boardState, i)) {
+				numExposedTiles++;
+				if (numExposedTiles == 2)
+					return true;
+			}
+		}
+	}
+
+	return false;
+}
+
+bool ShanghaiModifier::tileIsExposed(BoardState_t boardState, uint tile) const {
+	uint tileX = _tileCoordinates[tile].x;
+	uint tileY = _tileCoordinates[tile].y;
+	uint tileZ = _tileCoordinates[tile].z;
+
+	uint blockMinY = tileY;
+	uint blockMaxY = tileY;
+	if (blockMinY > 0)
+		blockMinY--;
+	if (blockMaxY < kBoardSizeY - 1u)
+		blockMaxY++;
+
+	bool blockedOnLeft = false;
+
+	if (tileX >= 2) {
+		// Check for left-side blocks
+		for (uint y = blockMinY; y <= blockMaxY; y++) {
+			if (tileExistsAtCoordinate(boardState, tileX - 2, y, tileZ)) {
+				blockedOnLeft = true;
+				break;
+			}
+		}
+	}
+
+	if (blockedOnLeft) {
+		bool blockedOnRight = false;
+
+		// Check for right-side blocks
+		if (tileX < kBoardSizeX - 2u) {
+			for (uint y = blockMinY; y <= blockMaxY; y++) {
+				if (tileExistsAtCoordinate(boardState, tileX + 2, y, tileZ)) {
+					blockedOnRight = true;
+					break;
+				}
+			}
+		}
+
+		// Tile is blocked on left and right
+		if (blockedOnRight)
+			return false;
+	}
+
+	// Check upper blocks
+	uint blockMinX = tileX;
+	uint blockMaxX = tileX;
+	if (blockMinX > 0)
+		blockMinX--;
+	if (blockMaxX < kBoardSizeX - 1u)
+		blockMaxX++;
+
+	for (uint z = tileZ + 1; z < kBoardSizeZ; z++) {
+		for (uint x = blockMinX; x <= blockMaxX; x++) {
+			for (uint y = blockMinY; y <= blockMaxY; y++) {
+				if (tileExistsAtCoordinate(boardState, x, y, z))
+					return false;
+			}
+		}
+	}
+
 	return true;
 }
 
+bool ShanghaiModifier::tileExistsAtCoordinate(BoardState_t boardState, uint x, uint y, uint z) const {
+	assert(x < kBoardSizeX);
+	assert(y < kBoardSizeY);
+	assert(z < kBoardSizeZ);
+
+	int8 tile = _tileAtCoordinate[x][y][z];
+
+	if (tile < 0)
+		return false;
+
+	if (boardState & boardStateBit(static_cast<uint>(tile)))
+		return true;
+
+	return false;
+}
+
+ShanghaiModifier::BoardState_t ShanghaiModifier::boardStateBit(uint bit) {
+	return static_cast<BoardState_t>(1) << bit;
+}
+
+ShanghaiModifier::BoardState_t ShanghaiModifier::emptyBoardState() {
+	return 0;
+}
+
+
 #ifdef MTROPOLIS_DEBUG_ENABLE
 void ShanghaiModifier::debugInspect(IDebugInspectionReport *report) const {
 }
@@ -64,7 +398,6 @@ const char *ShanghaiModifier::getDefaultName() const {
 	return "Shanghai Modifier";	// ???
 }
 
-
 PrintModifier::PrintModifier() {
 }
 
diff --git a/engines/mtropolis/plugin/mti.h b/engines/mtropolis/plugin/mti.h
index f48a65df086..1b49e60451a 100644
--- a/engines/mtropolis/plugin/mti.h
+++ b/engines/mtropolis/plugin/mti.h
@@ -27,6 +27,12 @@
 #include "mtropolis/plugin/mti_data.h"
 #include "mtropolis/runtime.h"
 
+namespace Common {
+
+class RandomSource;
+
+} // End of namespace Common
+
 namespace MTropolis {
 
 namespace MTI {
@@ -44,14 +50,47 @@ public:
 
 	bool load(const PlugInModifierLoaderContext &context, const Data::MTI::ShanghaiModifier &data);
 
+	void linkInternalReferences(ObjectLinkingScope *scope) override;
+	void visitInternalReferences(IStructuralReferenceVisitor *visitor) override;
+
 #ifdef MTROPOLIS_DEBUG_ENABLE
 	const char *debugGetTypeName() const override { return "Shanghai Modifier"; }
 	void debugInspect(IDebugInspectionReport *report) const override;
 #endif
 
 private:
+	static const uint kNumTiles = 28;
+	static const uint kNumFaces = 26;
+
+	typedef uint32 BoardState_t;
+
+	static const uint kBoardSizeX = 13;
+	static const uint kBoardSizeY = 7;
+	static const uint kBoardSizeZ = 3;
+
+	struct TileCoordinate {
+		uint x;
+		uint y;
+		uint z;
+	};
+
 	Common::SharedPtr<Modifier> shallowClone() const override;
 	const char *getDefaultName() const override;
+
+	void resetTiles(Common::RandomSource &rng, uint (&tileFaces)[kNumTiles]) const;
+	static uint selectAndRemoveOne(Common::RandomSource &rng, uint *valuesList, uint &listSize);
+	bool boardStateHasValidMove(BoardState_t boardState) const;
+	bool tileIsExposed(BoardState_t boardState, uint tile) const;
+	bool tileExistsAtCoordinate(BoardState_t boardState, uint x, uint y, uint z) const;
+
+	static BoardState_t boardStateBit(uint bit);
+	static BoardState_t emptyBoardState();
+
+	Event _resetTileSetWhen;
+	VarReference _tileSetRef;
+
+	static TileCoordinate _tileCoordinates[kNumTiles];
+	int8 _tileAtCoordinate[kBoardSizeX][kBoardSizeY][kBoardSizeZ];
 };
 
 class PrintModifier : public Modifier {
diff --git a/engines/mtropolis/plugin/mti_data.cpp b/engines/mtropolis/plugin/mti_data.cpp
index ddfbef9cf35..2c2f9bf0751 100644
--- a/engines/mtropolis/plugin/mti_data.cpp
+++ b/engines/mtropolis/plugin/mti_data.cpp
@@ -31,7 +31,7 @@ DataReadErrorCode ShanghaiModifier::load(PlugIn &plugIn, const PlugInModifier &p
 	if (prefix.plugInRevision != 0)
 		return kDataReadErrorUnsupportedRevision;
 
-	if (!unknown1Event.load(reader) || !unknown2VarRef.load(reader))
+	if (!resetWhen.load(reader) || !tileSetVar.load(reader))
 		return kDataReadErrorReadFailed;
 
 	return kDataReadErrorNone;
diff --git a/engines/mtropolis/plugin/mti_data.h b/engines/mtropolis/plugin/mti_data.h
index f1f94188b52..dc7912da69c 100644
--- a/engines/mtropolis/plugin/mti_data.h
+++ b/engines/mtropolis/plugin/mti_data.h
@@ -34,8 +34,8 @@ namespace MTI {
 // Shanghai - ???
 
 struct ShanghaiModifier : public PlugInModifierData {
-	PlugInTypeTaggedValue unknown1Event;  // Probably "Enable When"
-	PlugInTypeTaggedValue unknown2VarRef; // VarRef (Probably tile set)
+	PlugInTypeTaggedValue resetWhen;  // Reset When
+	PlugInTypeTaggedValue tileSetVar; // VarRef
 
 protected:
 	DataReadErrorCode load(PlugIn &plugIn, const PlugInModifier &prefix, DataReader &reader) override;




More information about the Scummvm-git-logs mailing list