[Scummvm-git-logs] scummvm master -> 142460fcb2c7c63f522af6a54af6531cae62dff6

npjg noreply at scummvm.org
Sun Jan 26 19:33:16 UTC 2025


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

Summary:
d9d8f43807 MEDIASTATION: Run context exit event handler when branching to screen
d8ea01f259 MEDIASTATION: Implement remaining comparison operators
4a57bbf375 MEDIASTATION: Implement remaining math operators
1e247d272d MEDIASTATION: Implement remaining logical operators
7c0cae08f3 MEDIASTATION: Activate any assets marked active on context load
fd7552dc4c MEDIASTATION: Add support for calling methods on arrays (collections)
19eb4a84a6 MEDIASTATION: Centralize time event hander processing
ca602eabc2 MEDIASTATION: Clean up Sound asset
8e37b9a596 MEDIASTATION: Implement hotspot enter/exit handling logic
142460fcb2 MEDIASTATION: Stub out more script methods


Commit: d9d8f438077d7f1265bd3de2afbfb9bd149c3170
    https://github.com/scummvm/scummvm/commit/d9d8f438077d7f1265bd3de2afbfb9bd149c3170
Author: Nathanael Gentry (nathanael.gentrydb8 at gmail.com)
Date: 2025-01-26T14:03:43-05:00

Commit Message:
MEDIASTATION: Run context exit event handler when branching to screen

Changed paths:
    engines/mediastation/mediastation.cpp
    engines/mediastation/mediastation.h


diff --git a/engines/mediastation/mediastation.cpp b/engines/mediastation/mediastation.cpp
index b64b3693041..4ccc276c0b9 100644
--- a/engines/mediastation/mediastation.cpp
+++ b/engines/mediastation/mediastation.cpp
@@ -324,7 +324,19 @@ Operand MediaStationEngine::callMethod(BuiltInMethod methodId, Common::Array<Ope
 }
 
 void MediaStationEngine::branchToScreen(uint32 contextId) {
+	if (_currentContext != nullptr) {
+		EventHandler *exitEvent = _currentContext->_screenAsset->_eventHandlers.getValOrDefault(kExitEvent);
+		if (exitEvent != nullptr) {
+			debugC(5, kDebugScript, "Executing context exit event handler");
+			exitEvent->execute(_currentContext->_screenAsset->_id);
+		} else {
+			debugC(5, kDebugScript, "No context exit event handler");
+		}
+	}
+
 	Context *context = loadContext(contextId);
+	_currentContext = context;
+
 	if (context->_screenAsset != nullptr) {
 		// TODO: Make the screen an asset just like everything else so we can
 		// run event handlers with runEventHandlerIfExists.
diff --git a/engines/mediastation/mediastation.h b/engines/mediastation/mediastation.h
index a086d70b7c6..87a1955d0ce 100644
--- a/engines/mediastation/mediastation.h
+++ b/engines/mediastation/mediastation.h
@@ -86,6 +86,7 @@ public:
 
 	Graphics::Screen *_screen = nullptr;
 	Audio::Mixer *_mixer = nullptr;
+	Context *_currentContext = nullptr;
 
 	// All Media Station titles run at 640x480.
 	const uint16 SCREEN_WIDTH = 640;


Commit: d8ea01f2594322dd0b2d4114470353a0679478cb
    https://github.com/scummvm/scummvm/commit/d8ea01f2594322dd0b2d4114470353a0679478cb
Author: Nathanael Gentry (nathanael.gentrydb8 at gmail.com)
Date: 2025-01-26T14:03:43-05:00

Commit Message:
MEDIASTATION: Implement remaining comparison operators

Changed paths:
    engines/mediastation/mediascript/codechunk.cpp
    engines/mediastation/mediascript/operand.cpp
    engines/mediastation/mediascript/operand.h


diff --git a/engines/mediastation/mediascript/codechunk.cpp b/engines/mediastation/mediascript/codechunk.cpp
index aa1ebd401e8..fb0d20f14f7 100644
--- a/engines/mediastation/mediascript/codechunk.cpp
+++ b/engines/mediastation/mediascript/codechunk.cpp
@@ -193,6 +193,58 @@ Operand CodeChunk::executeNextStatement() {
 			return returnValue;
 		}
 
+		case kOpcodeNotEquals: {
+			debugCN(5, kDebugScript, "\n    lhs: ");
+			Operand value1 = executeNextStatement();
+			debugCN(5, kDebugScript, "    rhs: ");
+			Operand value2 = executeNextStatement();
+
+			// TODO: Confirm this is the correct value type?
+			Operand returnValue(kOperandTypeLiteral1);
+			bool notEqual = !(value1 == value2);
+			returnValue.putInteger(static_cast<uint>(notEqual));
+			return returnValue;
+		}
+
+		case kOpcodeLessThan: {
+			debugCN(5, kDebugScript, "\n    lhs: ");
+			Operand value1 = executeNextStatement();
+			debugCN(5, kDebugScript, "    rhs: ");
+			Operand value2 = executeNextStatement();
+
+			// TODO: Confirm this is the correct value type?
+			Operand returnValue(kOperandTypeLiteral1);
+			bool lessThan = (value1 < value2);
+			returnValue.putInteger(static_cast<uint>(lessThan));
+			return returnValue;
+		}
+
+		case kOpcodeGreaterThan: {
+			debugCN(5, kDebugScript, "\n    lhs: ");
+			Operand value1 = executeNextStatement();
+			debugCN(5, kDebugScript, "    rhs: ");
+			Operand value2 = executeNextStatement();
+
+			// TODO: Confirm this is the correct value type?
+			Operand returnValue(kOperandTypeLiteral1);
+			bool greaterThan = (value1 > value2);
+			returnValue.putInteger(static_cast<uint>(greaterThan));
+			return returnValue;
+		}
+
+		case kOpcodeLessThanOrEqualTo: {
+			debugCN(5, kDebugScript, "\n    lhs: ");
+			Operand value1 = executeNextStatement();
+			debugCN(5, kDebugScript, "    rhs: ");
+			Operand value2 = executeNextStatement();
+
+			// TODO: Confirm this is the correct value type?
+			Operand returnValue(kOperandTypeLiteral1);
+			bool lessThanOrEqualTo = (value1 < value2) || (value1 == value2);
+			returnValue.putInteger(static_cast<uint>(lessThanOrEqualTo));
+			return returnValue;
+		}
+
 		case kOpcodeGreaterThanOrEqualTo: {
 			debugCN(5, kDebugScript, "\n    lhs: ");
 			Operand value1 = executeNextStatement();
@@ -201,7 +253,7 @@ Operand CodeChunk::executeNextStatement() {
 
 			// TODO: Confirm this is the correct value type?
 			Operand returnValue(kOperandTypeLiteral1);
-			bool greaterThanOrEqualTo = value1 >= value2;
+			bool greaterThanOrEqualTo = (value1 > value2) || (value1 == value2);
 			returnValue.putInteger(static_cast<uint>(greaterThanOrEqualTo));
 			return returnValue;
 		}
diff --git a/engines/mediastation/mediascript/operand.cpp b/engines/mediastation/mediascript/operand.cpp
index 367dd36724c..0b47ac8bf38 100644
--- a/engines/mediastation/mediascript/operand.cpp
+++ b/engines/mediastation/mediascript/operand.cpp
@@ -270,9 +270,10 @@ Operand Operand::getLiteralValue() const {
 bool Operand::operator==(const Operand &other) const {
 	Operand lhs = getLiteralValue();
 	Operand rhs = other.getLiteralValue();
-	// TODO: Maybe some better type checking here. If the types being compared end up being incompatible, the respective get
-	// method on the rhs will raise the error. But better might be checking
-	// both before we try getting values to report a more descriptive error.
+	// TODO: Maybe some better type checking here. If the types being compared 
+	// end up being incompatible, the respective get method on the rhs will 
+	// raise the error. But better might be checking both before we try getting 
+	// values to report a more descriptive error.
 	switch (lhs.getType()) {
 	case kOperandTypeLiteral1: 
 	case kOperandTypeLiteral2:
@@ -293,7 +294,7 @@ bool Operand::operator==(const Operand &other) const {
 	}
 }
 
-bool Operand::operator>=(const Operand &other) const {
+bool Operand::operator<(const Operand &other) const {
 	Operand lhs = getLiteralValue();
 	Operand rhs = other.getLiteralValue();
 	// If the types being compared end up being incompatible, the respective get
@@ -301,14 +302,33 @@ bool Operand::operator>=(const Operand &other) const {
 	switch (lhs.getType()) {
 	case kOperandTypeLiteral1: 
 	case kOperandTypeLiteral2:
-		return lhs.getInteger() >= rhs.getInteger();
+		return lhs.getInteger() < rhs.getInteger();
 
 	case kOperandTypeFloat1:
 	case kOperandTypeFloat2:
-		return lhs.getDouble() >= rhs.getDouble();
+		return lhs.getDouble() < rhs.getDouble();
 
 	default:
-		error("Operand::operator>=(): Unsupported operand types %d and %d", static_cast<uint>(lhs.getType()), static_cast<uint>(rhs.getType()));
+		error("Operand::operator<(): Unsupported operand types %d and %d", static_cast<uint>(lhs.getType()), static_cast<uint>(rhs.getType()));
+	}
+}
+
+bool Operand::operator>(const Operand &other) const {
+	Operand lhs = getLiteralValue();
+	Operand rhs = other.getLiteralValue();
+	// If the types being compared end up being incompatible, the respective get
+	// method on the rhs will raise the error.
+	switch (lhs.getType()) {
+	case kOperandTypeLiteral1: 
+	case kOperandTypeLiteral2:
+		return lhs.getInteger() > rhs.getInteger();
+
+	case kOperandTypeFloat1:
+	case kOperandTypeFloat2:
+		return lhs.getDouble() > rhs.getDouble();
+
+	default:
+		error("Operand::operator>(): Unsupported operand types %d and %d", static_cast<uint>(lhs.getType()), static_cast<uint>(rhs.getType()));
 	}
 }
 
diff --git a/engines/mediastation/mediascript/operand.h b/engines/mediastation/mediascript/operand.h
index 4c8b1c35135..8e6a96b472f 100644
--- a/engines/mediastation/mediascript/operand.h
+++ b/engines/mediastation/mediascript/operand.h
@@ -62,7 +62,9 @@ public:
 	Operand getLiteralValue() const;
 
 	bool operator==(const Operand &other) const;
-	bool operator>=(const Operand &other) const;
+	bool operator<(const Operand &other) const;
+	bool operator>(const Operand &other) const;
+
 	bool operator||(const Operand &other) const;
 
 	Operand operator-(const Operand &other) const;


Commit: 4a57bbf375ce4015957140dca28aee1d33546cf7
    https://github.com/scummvm/scummvm/commit/4a57bbf375ce4015957140dca28aee1d33546cf7
Author: Nathanael Gentry (nathanael.gentrydb8 at gmail.com)
Date: 2025-01-26T14:03:43-05:00

Commit Message:
MEDIASTATION: Implement remaining math operators

And re-order two that already existed so it reads nicely.

Changed paths:
    engines/mediastation/mediascript/codechunk.cpp
    engines/mediastation/mediascript/operand.cpp
    engines/mediastation/mediascript/operand.h


diff --git a/engines/mediastation/mediascript/codechunk.cpp b/engines/mediastation/mediascript/codechunk.cpp
index fb0d20f14f7..b1f15f48436 100644
--- a/engines/mediastation/mediascript/codechunk.cpp
+++ b/engines/mediastation/mediascript/codechunk.cpp
@@ -143,23 +143,6 @@ Operand CodeChunk::executeNextStatement() {
 			return returnValue;
 		}
 
-		case kOpcodeSubtract: {
-			debugCN(5, kDebugScript, "\n    lhs: ");
-			Operand value1 = executeNextStatement();
-			debugCN(5, kDebugScript, "    rhs: ");
-			Operand value2 = executeNextStatement();
-
-			Operand returnValue = value1 - value2;
-			return returnValue;
-		}
-
-		case kOpcodeNegate: {
-			Operand value = executeNextStatement();
-			debugCN(5, kDebugScript, "    value: ");
-
-			return -value;
-		}
-
 		case kOpcodeIfElse: {
 			debugCN(5, kDebugScript, "\n    condition: ");
 			Operand condition = executeNextStatement();
@@ -258,6 +241,63 @@ Operand CodeChunk::executeNextStatement() {
 			return returnValue;
 		}
 
+		case kOpcodeAdd: {
+			debugCN(5, kDebugScript, "\n    lhs: ");
+			Operand value1 = executeNextStatement();
+			debugCN(5, kDebugScript, "    rhs: ");
+			Operand value2 = executeNextStatement();
+
+			Operand returnValue = value1 + value2;
+			return returnValue;
+		}
+
+		case kOpcodeSubtract: {
+			debugCN(5, kDebugScript, "\n    lhs: ");
+			Operand value1 = executeNextStatement();
+			debugCN(5, kDebugScript, "    rhs: ");
+			Operand value2 = executeNextStatement();
+
+			Operand returnValue = value1 - value2;
+			return returnValue;
+		}
+
+		case kOpcodeMultiply: {
+			debugCN(5, kDebugScript, "\n    lhs: ");
+			Operand value1 = executeNextStatement();
+			debugCN(5, kDebugScript, "    rhs: ");
+			Operand value2 = executeNextStatement();
+
+			Operand returnValue = value1 * value2;
+			return returnValue;
+		}
+
+		case kOpcodeDivide: {
+			debugCN(5, kDebugScript, "\n    lhs: ");
+			Operand value1 = executeNextStatement();
+			debugCN(5, kDebugScript, "    rhs: ");
+			Operand value2 = executeNextStatement();
+
+			Operand returnValue = value1 / value2;
+			return returnValue;
+		}
+
+		case kOpcodeModulo: {
+			debugCN(5, kDebugScript, "\n    lhs: ");
+			Operand value1 = executeNextStatement();
+			debugCN(5, kDebugScript, "    rhs: ");
+			Operand value2 = executeNextStatement();
+
+			Operand returnValue = value1 % value2;
+			return returnValue;
+		}
+
+		case kOpcodeNegate: {
+			Operand value = executeNextStatement();
+			debugCN(5, kDebugScript, "    value: ");
+
+			return -value;
+		}
+
 		default: {
 			error("CodeChunk::getNextStatement(): Got unknown opcode %s (%d)", opcodeToStr(opcode), static_cast<uint>(opcode));
 		}
diff --git a/engines/mediastation/mediascript/operand.cpp b/engines/mediastation/mediascript/operand.cpp
index 0b47ac8bf38..c4e42001116 100644
--- a/engines/mediastation/mediascript/operand.cpp
+++ b/engines/mediastation/mediascript/operand.cpp
@@ -347,18 +347,119 @@ bool Operand::operator||(const Operand &other) const {
 	}
 }
 
+Operand Operand::operator+(const Operand &other) const {
+	Operand lhs = getLiteralValue();
+	Operand rhs = other.getLiteralValue();
+	Operand returnValue(lhs.getType());
+	// If the types being compared end up being incompatible, the respective get
+	// method on the rhs will raise the error.
+	switch (lhs.getType()) {
+	case kOperandTypeLiteral1: 
+	case kOperandTypeLiteral2:
+		returnValue.putInteger(lhs.getInteger() + rhs.getInteger());
+		return returnValue;
+
+	case kOperandTypeFloat1:
+	case kOperandTypeFloat2:
+		returnValue.putDouble(lhs.getDouble() + rhs.getDouble());
+		return returnValue;
+
+	default:
+		error("Operand::operator+(): Unsupported operand types %s and %s", operandTypeToStr(lhs.getType()), operandTypeToStr(rhs.getType()));
+	}
+}
+
 Operand Operand::operator-(const Operand &other) const {
-	Operand returnValue;
-	if (this->_type == kOperandTypeLiteral1 && other._type == kOperandTypeLiteral1) {
-		returnValue._type = kOperandTypeLiteral1;
-		returnValue._u.i = this->_u.i - other._u.i;
-	} else if (this->_type == kOperandTypeFloat1 && other._type == kOperandTypeFloat1) {
-		returnValue._type = kOperandTypeFloat1;
-		returnValue._u.d = this->_u.d - other._u.d;
-	} else {
-		error("Operand::operator-(): Unsupported operand types %d and %d", static_cast<uint>(this->_type), static_cast<uint>(other._type));
+	Operand lhs = getLiteralValue();
+	Operand rhs = other.getLiteralValue();
+	Operand returnValue(lhs.getType());
+	// If the types being compared end up being incompatible, the respective get
+	// method on the rhs will raise the error.
+	switch (lhs.getType()) {
+	case kOperandTypeLiteral1: 
+	case kOperandTypeLiteral2:
+		returnValue.putInteger(lhs.getInteger() - rhs.getInteger());
+		return returnValue;
+
+	case kOperandTypeFloat1:
+	case kOperandTypeFloat2:
+		returnValue.putDouble(lhs.getDouble() - rhs.getDouble());
+		return returnValue;
+
+	default:
+		error("Operand::operator-(): Unsupported operand types %s and %s", operandTypeToStr(lhs.getType()), operandTypeToStr(rhs.getType()));
+	}
+}
+
+Operand Operand::operator*(const Operand &other) const {
+	Operand lhs = getLiteralValue();
+	Operand rhs = other.getLiteralValue();
+	Operand returnValue(lhs.getType());
+	// If the types being compared end up being incompatible, the respective get
+	// method on the rhs will raise the error.
+	switch (lhs.getType()) {
+	case kOperandTypeLiteral1: 
+	case kOperandTypeLiteral2:
+		returnValue.putInteger(lhs.getInteger() * rhs.getInteger());
+		return returnValue;
+
+	case kOperandTypeFloat1:
+	case kOperandTypeFloat2:
+		returnValue.putDouble(lhs.getDouble() * rhs.getDouble());
+		return returnValue;
+
+	default:
+		error("Operand::operator*(): Unsupported operand types %s and %s", operandTypeToStr(lhs.getType()), operandTypeToStr(rhs.getType()));
+	}
+}
+
+Operand Operand::operator/(const Operand &other) const {
+	Operand lhs = getLiteralValue();
+	Operand rhs = other.getLiteralValue();
+	Operand returnValue(lhs.getType());
+	// If the types being compared end up being incompatible, the respective get
+	// method on the rhs will raise the error.
+	switch (lhs.getType()) {
+	case kOperandTypeLiteral1: 
+	case kOperandTypeLiteral2:
+		if (rhs.getInteger() == 0) {
+			error("Operand::operator/(): Attempted divide by zero");
+		}
+		// Standard integer divison here.
+		returnValue.putInteger(lhs.getInteger() / rhs.getInteger());
+		return returnValue;
+
+	case kOperandTypeFloat1:
+	case kOperandTypeFloat2:
+		if (rhs.getDouble() == 0) {
+			error("Operand::operator/(): Attempted divide by zero");
+		}
+		returnValue.putDouble(lhs.getDouble() / rhs.getDouble());
+		return returnValue;
+
+	default:
+		error("Operand::operator/(): Unsupported operand types %s and %s", operandTypeToStr(lhs.getType()), operandTypeToStr(rhs.getType()));
+	}
+}
+
+Operand Operand::operator%(const Operand &other) const {
+	Operand lhs = getLiteralValue();
+	Operand rhs = other.getLiteralValue();
+	Operand returnValue(lhs.getType());
+	// If the types being compared end up being incompatible, the respective get
+	// method on the rhs will raise the error.
+	switch (lhs.getType()) {
+	case kOperandTypeLiteral1: 
+	case kOperandTypeLiteral2:
+		if (rhs.getInteger() == 0) {
+			error("Operand::operator%%(): Attempted mod by zero");
+		}
+		returnValue.putInteger(lhs.getInteger() % rhs.getInteger());
+		return returnValue;
+
+	default:
+		error("Operand::operator/(): Unsupported operand types %s and %s", operandTypeToStr(lhs.getType()), operandTypeToStr(rhs.getType()));
 	}
-	return returnValue;
 }
 
 Operand Operand::operator-() const {
diff --git a/engines/mediastation/mediascript/operand.h b/engines/mediastation/mediascript/operand.h
index 8e6a96b472f..3511c39fb66 100644
--- a/engines/mediastation/mediascript/operand.h
+++ b/engines/mediastation/mediascript/operand.h
@@ -67,7 +67,11 @@ public:
 
 	bool operator||(const Operand &other) const;
 
+	Operand operator+(const Operand &other) const;
 	Operand operator-(const Operand &other) const;
+	Operand operator*(const Operand &other) const;
+	Operand operator/(const Operand &other) const;
+	Operand operator%(const Operand &other) const;
 	Operand operator-() const;
 
 private:


Commit: 1e247d272d43b12505846254a1d8fb35646b7055
    https://github.com/scummvm/scummvm/commit/1e247d272d43b12505846254a1d8fb35646b7055
Author: Nathanael Gentry (nathanael.gentrydb8 at gmail.com)
Date: 2025-01-26T14:03:43-05:00

Commit Message:
MEDIASTATION: Implement remaining logical operators

Changed paths:
    engines/mediastation/mediascript/codechunk.cpp
    engines/mediastation/mediascript/operand.cpp
    engines/mediastation/mediascript/operand.h
    engines/mediastation/mediascript/scriptconstants.cpp
    engines/mediastation/mediascript/scriptconstants.h


diff --git a/engines/mediastation/mediascript/codechunk.cpp b/engines/mediastation/mediascript/codechunk.cpp
index b1f15f48436..02fdc467dca 100644
--- a/engines/mediastation/mediascript/codechunk.cpp
+++ b/engines/mediastation/mediascript/codechunk.cpp
@@ -143,6 +143,28 @@ Operand CodeChunk::executeNextStatement() {
 			return returnValue;
 		}
 
+		case kOpcodeNot: {
+			debugCN(5, kDebugScript, "\n    value: ");
+			Operand value = executeNextStatement();
+
+			Operand returnValue(kOperandTypeLiteral1);
+			bool logicalNot = !(static_cast<bool>(value.getInteger()));
+			returnValue.putInteger(static_cast<uint>(logicalNot));
+			return returnValue;
+		}
+
+		case kOpcodeAnd: {
+			debugCN(5, kDebugScript, "\n    value: ");
+			Operand value1 = executeNextStatement();
+			debugCN(5, kDebugScript, "    rhs: ");
+			Operand value2 = executeNextStatement();
+
+			Operand returnValue(kOperandTypeLiteral1);
+			bool logicalAnd = (value1 && value2);
+			returnValue.putInteger(static_cast<uint>(logicalAnd));
+			return returnValue;
+		}
+
 		case kOpcodeIfElse: {
 			debugCN(5, kDebugScript, "\n    condition: ");
 			Operand condition = executeNextStatement();
diff --git a/engines/mediastation/mediascript/operand.cpp b/engines/mediastation/mediascript/operand.cpp
index c4e42001116..c9dd125d915 100644
--- a/engines/mediastation/mediascript/operand.cpp
+++ b/engines/mediastation/mediascript/operand.cpp
@@ -347,6 +347,35 @@ bool Operand::operator||(const Operand &other) const {
 	}
 }
 
+bool Operand::operator!() const {
+	Operand literalValue = getLiteralValue();
+	// If the types being compared end up being incompatible, the respective get
+	// method on the rhs will raise the error.
+	switch (literalValue.getType()) {
+	case kOperandTypeLiteral1: 
+	case kOperandTypeLiteral2:
+		return !literalValue.getInteger();
+
+	default:
+		error("Operand::operator!(): Unsupported operand type %d", static_cast<uint>(literalValue.getType()));
+	}
+}
+
+bool Operand::operator&&(const Operand &other) const {
+	Operand lhs = getLiteralValue();
+	Operand rhs = other.getLiteralValue();
+	// If the types being compared end up being incompatible, the respective get
+	// method on the rhs will raise the error.
+	switch (lhs.getType()) {
+	case kOperandTypeLiteral1: 
+	case kOperandTypeLiteral2:
+		return lhs.getInteger() && rhs.getInteger();
+
+	default:
+		error("Operand::operator&&(): Unsupported operand types %s and %s", operandTypeToStr(lhs.getType()), operandTypeToStr(rhs.getType()));
+	}
+}
+
 Operand Operand::operator+(const Operand &other) const {
 	Operand lhs = getLiteralValue();
 	Operand rhs = other.getLiteralValue();
diff --git a/engines/mediastation/mediascript/operand.h b/engines/mediastation/mediascript/operand.h
index 3511c39fb66..45ab73ca832 100644
--- a/engines/mediastation/mediascript/operand.h
+++ b/engines/mediastation/mediascript/operand.h
@@ -66,6 +66,8 @@ public:
 	bool operator>(const Operand &other) const;
 
 	bool operator||(const Operand &other) const;
+	bool operator!() const;
+	bool operator&&(const Operand &other) const;
 
 	Operand operator+(const Operand &other) const;
 	Operand operator-(const Operand &other) const;
diff --git a/engines/mediastation/mediascript/scriptconstants.cpp b/engines/mediastation/mediascript/scriptconstants.cpp
index 3a88cf20ce7..55ec25bc4fa 100644
--- a/engines/mediastation/mediascript/scriptconstants.cpp
+++ b/engines/mediastation/mediascript/scriptconstants.cpp
@@ -46,6 +46,8 @@ const char *opcodeToStr(Opcode opcode) {
 		return "AssignVariable";
 	case kOpcodeOr:
 		return "Or";
+	case kOpcodeNot:
+		return "Not";
 	case kOpcodeAnd:
 		return "And";
 	case kOpcodeEquals:
diff --git a/engines/mediastation/mediascript/scriptconstants.h b/engines/mediastation/mediascript/scriptconstants.h
index afe0c5ffd39..2eb9f906c23 100644
--- a/engines/mediastation/mediascript/scriptconstants.h
+++ b/engines/mediastation/mediascript/scriptconstants.h
@@ -36,6 +36,7 @@ enum Opcode {
 	kOpcodeIfElse = 202,
 	kOpcodeAssignVariable = 203,
 	kOpcodeOr = 204,
+	kOpcodeNot = 205,
 	kOpcodeAnd = 206,
 	kOpcodeEquals = 207,
 	kOpcodeNotEquals = 208,


Commit: 7c0cae08f3fe2d52f8583db1d6c8894f5a6381a5
    https://github.com/scummvm/scummvm/commit/7c0cae08f3fe2d52f8583db1d6c8894f5a6381a5
Author: Nathanael Gentry (nathanael.gentrydb8 at gmail.com)
Date: 2025-01-26T14:03:43-05:00

Commit Message:
MEDIASTATION: Activate any assets marked active on context load

Changed paths:
    engines/mediastation/assets/image.cpp
    engines/mediastation/assets/image.h
    engines/mediastation/assets/sprite.cpp
    engines/mediastation/assets/sprite.h
    engines/mediastation/context.cpp
    engines/mediastation/context.h
    engines/mediastation/mediastation.cpp


diff --git a/engines/mediastation/assets/image.cpp b/engines/mediastation/assets/image.cpp
index 89fe6266b36..edaa22a75c9 100644
--- a/engines/mediastation/assets/image.cpp
+++ b/engines/mediastation/assets/image.cpp
@@ -24,6 +24,12 @@
 
 namespace MediaStation {
 
+Image::Image(AssetHeader *header) : Asset(header) {
+	if (header->_startup == kAssetStartupActive) {
+		_isActive = true;
+	}
+}
+
 Image::~Image() {
 	delete _bitmap;
 	_bitmap = nullptr;
diff --git a/engines/mediastation/assets/image.h b/engines/mediastation/assets/image.h
index 4a0763f9e27..a950e207b88 100644
--- a/engines/mediastation/assets/image.h
+++ b/engines/mediastation/assets/image.h
@@ -33,7 +33,7 @@ namespace MediaStation {
 
 class Image : public Asset {
 public:
-	Image(AssetHeader *header) : Asset(header) {};
+	Image(AssetHeader *header);
 	virtual ~Image() override;
 
 	virtual void readChunk(Chunk &chunk) override;
diff --git a/engines/mediastation/assets/sprite.cpp b/engines/mediastation/assets/sprite.cpp
index f8f5b246926..201987b6dea 100644
--- a/engines/mediastation/assets/sprite.cpp
+++ b/engines/mediastation/assets/sprite.cpp
@@ -66,6 +66,12 @@ uint32 SpriteFrame::index() {
 	return _bitmapHeader->_index;
 }
 
+Sprite::Sprite(AssetHeader *header) : Asset(header) {
+	if (header->_startup == kAssetStartupActive) {
+		_isActive = true;
+	}
+}
+
 Sprite::~Sprite() {
 	for (SpriteFrame *frame : _frames) {
 		delete frame;
diff --git a/engines/mediastation/assets/sprite.h b/engines/mediastation/assets/sprite.h
index ca41f754918..c1a3114fffb 100644
--- a/engines/mediastation/assets/sprite.h
+++ b/engines/mediastation/assets/sprite.h
@@ -60,7 +60,7 @@ private:
 
 class Sprite : public Asset {
 public:
-	Sprite(AssetHeader *header) : Asset(header) {};
+	Sprite(AssetHeader *header);
 	~Sprite();
 
 	virtual Operand callMethod(BuiltInMethod methodId, Common::Array<Operand> &args) override;
diff --git a/engines/mediastation/context.cpp b/engines/mediastation/context.cpp
index 845b9baf531..f475ba05e40 100644
--- a/engines/mediastation/context.cpp
+++ b/engines/mediastation/context.cpp
@@ -103,6 +103,14 @@ Function *Context::getFunctionById(uint functionId) {
 	return _functions.getValOrDefault(functionId);
 }
 
+void Context::registerActiveAssets() {
+	for (auto it = _assets.begin(); it != _assets.end(); ++it) {
+		if (it->_value->isActive()) {
+			g_engine->addPlayingAsset(it->_value);
+		}
+	}
+}
+
 bool Context::readPreamble() {
 	uint16 signature = _stream->readUint16LE();
 	if (signature != 0x4949) { // "II"
diff --git a/engines/mediastation/context.h b/engines/mediastation/context.h
index 6b3e19b7746..618c3c9d310 100644
--- a/engines/mediastation/context.h
+++ b/engines/mediastation/context.h
@@ -64,6 +64,7 @@ public:
 	Asset *getAssetById(uint assetId);
 	Asset *getAssetByChunkReference(uint chunkReference);
 	Function *getFunctionById(uint functionId);
+	void registerActiveAssets();
 
 private:
 	Common::HashMap<uint, Asset *> _assets;
diff --git a/engines/mediastation/mediastation.cpp b/engines/mediastation/mediastation.cpp
index 4ccc276c0b9..4f84cac81ba 100644
--- a/engines/mediastation/mediastation.cpp
+++ b/engines/mediastation/mediastation.cpp
@@ -272,6 +272,7 @@ Context *MediaStationEngine::loadContext(uint32 contextId) {
 		_screen->setPalette(*context->_palette);
 	}
 
+	context->registerActiveAssets();
 	_loadedContexts.setVal(contextId, context);
 	return context;
 }


Commit: fd7552dc4c4eaf495e0031322de3303c7330a2df
    https://github.com/scummvm/scummvm/commit/fd7552dc4c4eaf495e0031322de3303c7330a2df
Author: Nathanael Gentry (nathanael.gentrydb8 at gmail.com)
Date: 2025-01-26T14:29:06-05:00

Commit Message:
MEDIASTATION: Add support for calling methods on arrays (collections)

Changed paths:
    engines/mediastation/mediascript/codechunk.cpp
    engines/mediastation/mediascript/operand.cpp
    engines/mediastation/mediascript/operand.h
    engines/mediastation/mediascript/scriptconstants.cpp
    engines/mediastation/mediascript/scriptconstants.h
    engines/mediastation/mediascript/variable.cpp
    engines/mediastation/mediascript/variable.h


diff --git a/engines/mediastation/mediascript/codechunk.cpp b/engines/mediastation/mediascript/codechunk.cpp
index 02fdc467dca..8247b5e05f2 100644
--- a/engines/mediastation/mediascript/codechunk.cpp
+++ b/engines/mediastation/mediascript/codechunk.cpp
@@ -486,6 +486,12 @@ Operand CodeChunk::callBuiltInMethod(BuiltInMethod method, Operand self, Common:
 		}
 	}
 
+	case kOperandTypeCollection: {
+		Collection *collection = literalSelf.getCollection();
+		Operand returnValue = collection->callMethod(method, args);
+		return returnValue;
+	}
+
 	default:
 		error("CodeChunk::callBuiltInMethod(): Attempt to call method on unsupported operand type %s (%d)", 
 			operandTypeToStr(literalType), static_cast<uint>(literalType));
diff --git a/engines/mediastation/mediascript/operand.cpp b/engines/mediastation/mediascript/operand.cpp
index c9dd125d915..d6e09f12ff5 100644
--- a/engines/mediastation/mediascript/operand.cpp
+++ b/engines/mediastation/mediascript/operand.cpp
@@ -257,6 +257,45 @@ uint32 Operand::getAssetId() {
 	}
 }
 
+void Operand::putCollection(Collection *collection) {
+	switch (_type) {
+	case kOperandTypeCollection: {
+		_u.collection = collection;
+		break;
+	}
+
+	case kOperandTypeVariableDeclaration: {
+		assert(_u.variable->_type == kVariableTypeCollection);
+		_u.variable->_value.collection = collection;
+		break;
+	}
+
+	default: {
+		error("Operand::putCollection(): Attempt to put collection into operand type %s (%d)", 
+			operandTypeToStr(_type), static_cast<uint>(_type));
+	}
+	}
+}
+
+Collection *Operand::getCollection() {
+	switch (_type) {
+	case kOperandTypeCollection: {
+		return _u.collection;
+	}
+
+	case kOperandTypeVariableDeclaration: {
+		assert(_u.variable->_type == kVariableTypeCollection);
+		return _u.variable->_value.collection;
+		break;
+	}
+
+	default: {
+		error("Operand::getCollection(): Attempt to get collection from operand type %s (%d)",
+			operandTypeToStr(_type), static_cast<uint>(_type));
+	}
+	}
+}
+
 Operand Operand::getLiteralValue() const {
 	// This function dereferences any variable to get the actual
 	// "direct" value (a literal asset ID or otherwise).
diff --git a/engines/mediastation/mediascript/operand.h b/engines/mediastation/mediascript/operand.h
index 45ab73ca832..f66b49d1916 100644
--- a/engines/mediastation/mediascript/operand.h
+++ b/engines/mediastation/mediascript/operand.h
@@ -59,6 +59,9 @@ public:
 	Asset *getAsset();
 	uint32 getAssetId();
 
+	void putCollection(Collection *collection);
+	Collection *getCollection();
+
 	Operand getLiteralValue() const;
 
 	bool operator==(const Operand &other) const;
@@ -85,6 +88,7 @@ private:
 		Variable *variable;
 		int i;
 		double d;
+		Collection *collection;
 	} _u;
 };
 
diff --git a/engines/mediastation/mediascript/scriptconstants.cpp b/engines/mediastation/mediascript/scriptconstants.cpp
index 55ec25bc4fa..1aaee142b51 100644
--- a/engines/mediastation/mediascript/scriptconstants.cpp
+++ b/engines/mediastation/mediascript/scriptconstants.cpp
@@ -211,6 +211,10 @@ const char *builtInMethodToStr(BuiltInMethod method) {
 		return "Sort";
 	case kDeleteAtMethod:
 		return "DeleteAt";
+	case kJumbleMethod: 
+		return "Jumble";
+	case kDeleteFirstMethod:
+		return "DeleteFirst";
 	case kOpenLensMethod:
 		return "OpenLens";
 	case kCloseLensMethod:
@@ -320,6 +324,8 @@ const char *operandTypeToStr(OperandType type) {
 		return "AssetId";
 	case kOperandTypeVariableDeclaration:
 		return "VariableDeclaration";
+	case kOperandTypeCollection:
+		return "Collection";
 	case kOperandTypeFunction:
 		return "Function";
 	default:
diff --git a/engines/mediastation/mediascript/scriptconstants.h b/engines/mediastation/mediascript/scriptconstants.h
index 2eb9f906c23..3e9d03547be 100644
--- a/engines/mediastation/mediascript/scriptconstants.h
+++ b/engines/mediastation/mediascript/scriptconstants.h
@@ -157,6 +157,8 @@ enum BuiltInMethod {
 	kSeekMethod = 256, // PARAMS: 1
 	kSortMethod = 266, // PARAMS: 0
 	kDeleteAtMethod = 258, // PARAMS: 1
+	kJumbleMethod = 255, // PARAMS: 0
+	kDeleteFirstMethod = 250, // PARAMS: 0
 
 	// PRINTER METHODS.
 	kOpenLensMethod = 346, // PARAMS: 0
@@ -244,6 +246,7 @@ enum OperandType {
 	kOperandTypeDollarSignVariable = 155,
 	kOperandTypeAssetId = 156,
 	kOperandTypeVariableDeclaration = 158,
+	kOperandTypeCollection = 159,
 	kOperandTypeFunction = 160
 };
 const char *operandTypeToStr(OperandType type);
diff --git a/engines/mediastation/mediascript/variable.cpp b/engines/mediastation/mediascript/variable.cpp
index afc375eb59f..bc9be29cc47 100644
--- a/engines/mediastation/mediascript/variable.cpp
+++ b/engines/mediastation/mediascript/variable.cpp
@@ -28,21 +28,49 @@
 
 namespace MediaStation {
 
+Operand Collection::callMethod(BuiltInMethod method, Common::Array<Operand> &args) {
+	switch (method) {
+	case kIsEmptyMethod: {
+		// This is a built-in method that checks if a collection is empty.
+		// We can just check the size of the collection.
+		Operand returnValue(kOperandTypeLiteral1);
+		returnValue.putInteger(empty());
+		return returnValue;
+	}
+
+	case kAppendMethod: {
+		for (Operand arg : args) {
+			push_back(arg);
+		}
+		return Operand();
+	}
+
+	case kDeleteFirstMethod: {
+		Operand returnValue = remove_at(0);
+		return returnValue;
+	}
+
+	default: {
+		error("Collection::callMethod(): Attempt to call unimplemented method %s (%d)", builtInMethodToStr(method), method);
+	}
+	}
+}
+
 Variable::Variable(Chunk &chunk, bool readId) {
 	if (readId) {
 		_id = Datum(chunk).u.i;
 	}
 	_type = static_cast<VariableType>(Datum(chunk).u.i);
-	debugC(5, kDebugLoading, "Variable::Variable(): id = 0x%x, type %s (%d) (@0x%llx)", 
+	debugC(1, kDebugLoading, "Variable::Variable(): id = %d, type %s (%d) (@0x%llx)", 
 		_id, variableTypeToStr(_type), static_cast<uint>(_type), static_cast<long long int>(chunk.pos()));
 	switch ((VariableType)_type) {
 	case kVariableTypeCollection: {
 		uint totalItems = Datum(chunk).u.i;
-		_value.collection = new Common::Array<Variable *>;
+		_value.collection = new Collection;
 		for (uint i = 0; i < totalItems; i++) {
 			debugC(7, kDebugLoading, "Variable::Variable(): %s: Value %d of %d", variableTypeToStr(_type), i, totalItems);
-			Variable *variableDeclaration = new Variable(chunk, readId = false);
-			_value.collection->push_back(variableDeclaration);
+			Variable variable = Variable(chunk, readId = false);
+			_value.collection->push_back(variable.getValue());
 		}
 		break;
 	}
@@ -97,9 +125,7 @@ Variable::Variable(Chunk &chunk, bool readId) {
 Variable::~Variable() {
 	switch (_type) {
 	case kVariableTypeCollection: {
-		for (Variable *variable : *(_value.collection)) {
-			delete variable;
-		}
+		_value.collection->clear();
 		delete _value.collection;
 		break;
 	}
@@ -122,9 +148,9 @@ Operand Variable::getValue() {
 	}
 
 	case kVariableTypeCollection: {
-		// TODO: Determine if any scripts actually try to do this.
-		error("Variable::getValue(): Returning a collection is not implemented");
-		break;
+		Operand returnValue(kOperandTypeCollection);
+		returnValue.putCollection(_value.collection);
+		return returnValue;
 	}
 
 	case kVariableTypeString: {
@@ -214,20 +240,4 @@ void Variable::putValue(Operand value) {
 	}
 }
 
-Operand Variable::callMethod(BuiltInMethod method, Common::Array<Operand> &args) {
-	switch (_type) {
-	case kVariableTypeCollection: {
-		// TODO: This is just a warning for now so we can get past the
-		// IBM/Crayola opening screen.
-		warning("Variable::callMethod(): Calling method on a collection not implemented yet");
-		return Operand();
-		break;
-	}
-
-	default: {
-		error("Variable::callMethod(): Calling method on unknown variable type %s (%d)", variableTypeToStr(_type), static_cast<uint>(_type));
-	}
-	}
-}
-
 } // End of namespace MediaStation
diff --git a/engines/mediastation/mediascript/variable.h b/engines/mediastation/mediascript/variable.h
index 2163970a548..691f011f70b 100644
--- a/engines/mediastation/mediascript/variable.h
+++ b/engines/mediastation/mediascript/variable.h
@@ -33,13 +33,18 @@ namespace MediaStation {
 
 class Operand;
 
+class Collection : public Common::Array<Operand> {
+public:
+	Operand callMethod(BuiltInMethod method, Common::Array<Operand> &args);
+};
+
 class Variable {
 public:
 	uint32 _id = 0;
 	VariableType _type = kVariableTypeEmpty;
 	union {
 		Common::String *string;
-		Common::Array<Variable *> *collection;
+		Collection *collection;
 		bool b;
 		int i;
 		double d;
@@ -51,7 +56,6 @@ public:
 
 	Operand getValue();
 	void putValue(Operand value);
-	Operand callMethod(BuiltInMethod method, Common::Array<Operand> &args);
 	~Variable();
 };
 


Commit: 19eb4a84a65b16e6d1f0d80561156330a52221da
    https://github.com/scummvm/scummvm/commit/19eb4a84a65b16e6d1f0d80561156330a52221da
Author: Nathanael Gentry (nathanael.gentrydb8 at gmail.com)
Date: 2025-01-26T14:29:06-05:00

Commit Message:
MEDIASTATION: Centralize time event hander processing

Changed paths:
    engines/mediastation/asset.cpp
    engines/mediastation/asset.h
    engines/mediastation/assets/movie.cpp
    engines/mediastation/assets/movie.h
    engines/mediastation/assets/timer.cpp


diff --git a/engines/mediastation/asset.cpp b/engines/mediastation/asset.cpp
index 9949df5a4a2..f28e39d94c2 100644
--- a/engines/mediastation/asset.cpp
+++ b/engines/mediastation/asset.cpp
@@ -45,6 +45,27 @@ int Asset::zIndex() const {
 	return _header->_zIndex;
 }
 
+void Asset::processTimeEventHandlers() {
+	if (!_isActive) {
+		warning("Asset::processTimeEventHandlers(): Attempted to process time event handlers while asset %d is not playing", _header->_id);
+		return;
+	}
+
+	// TODO: Replace with a queue.
+	uint currentTime = g_system->getMillis();
+	for (EventHandler *timeEvent : _header->_timeHandlers) {
+		double timeEventInFractionalSeconds = timeEvent->_argumentValue.u.f;
+		uint timeEventInMilliseconds = timeEventInFractionalSeconds * 1000;
+		bool timeEventAlreadyProcessed = timeEventInMilliseconds < _lastProcessedTime;
+		bool timeEventNeedsToBeProcessed = timeEventInMilliseconds <= currentTime - _startTime;
+		if (!timeEventAlreadyProcessed && timeEventNeedsToBeProcessed) {
+			debugC(5, kDebugScript, "Asset::processTimeEventHandlers(): Running On Time handler for time %d ms", timeEventInMilliseconds);
+			timeEvent->execute(_header->_id);
+		}
+	}
+	_lastProcessedTime = currentTime - _startTime;
+}
+
 void Asset::runEventHandlerIfExists(EventType eventType) {
 	EventHandler *eventHandler = _header->_eventHandlers.getValOrDefault(eventType);
 	if (eventHandler != nullptr) {
diff --git a/engines/mediastation/asset.h b/engines/mediastation/asset.h
index 0576152f540..f746618d666 100644
--- a/engines/mediastation/asset.h
+++ b/engines/mediastation/asset.h
@@ -24,6 +24,7 @@
 
 #include "common/keyboard.h"
 
+#include "mediastation/mediastation.h"
 #include "mediastation/subfile.h"
 #include "mediastation/chunk.h"
 #include "mediastation/mediascript/scriptconstants.h"
@@ -57,6 +58,7 @@ public:
 	virtual void readChunk(Chunk &chunk);
 	virtual void readSubfile(Subfile &subfile, Chunk &chunk);
 
+	void processTimeEventHandlers();
 	void runEventHandlerIfExists(EventType eventType);
 	void runKeyDownEventHandlerIfExists(Common::KeyState keyState);
 
diff --git a/engines/mediastation/assets/movie.cpp b/engines/mediastation/assets/movie.cpp
index d8f7729992e..96fc0e24760 100644
--- a/engines/mediastation/assets/movie.cpp
+++ b/engines/mediastation/assets/movie.cpp
@@ -244,26 +244,6 @@ void Movie::process() {
 	drawNextFrame();
 }
 
-void Movie::processTimeEventHandlers() {
-	if (!_isActive) {
-		warning("Movie::processTimeEventHandlers(): Attempted to process time event handlers while movie is not playing");
-		return;
-	}
-
-	uint currentTime = g_system->getMillis();
-	for (EventHandler *timeEvent : _header->_timeHandlers) {
-		double timeEventInFractionalSeconds = timeEvent->_argumentValue.u.f;
-		uint timeEventInMilliseconds = timeEventInFractionalSeconds * 1000;
-		bool timeEventAlreadyProcessed = timeEventInMilliseconds < _lastProcessedTime;
-		bool timeEventNeedsToBeProcessed = timeEventInMilliseconds <= currentTime - _startTime;
-		if (!timeEventAlreadyProcessed && timeEventNeedsToBeProcessed) {
-			debugC(5, kDebugScript, "Movie::processTimeEventHandlers(): Running On Time handler for movie time %d ms (real movie time: %d ms)", timeEventInMilliseconds, currentTime - _startTime);
-			timeEvent->execute(_header->_id);
-		}
-	}
-	_lastProcessedTime = currentTime - _startTime;
-}
-
 bool Movie::drawNextFrame() {
 	// TODO: We'll need to support persistent frames in movies too. Do movies
 	// have the same distinction between spatialShow and timePlay that sprites
diff --git a/engines/mediastation/assets/movie.h b/engines/mediastation/assets/movie.h
index ec57c473ea6..f81f40b44c2 100644
--- a/engines/mediastation/assets/movie.h
+++ b/engines/mediastation/assets/movie.h
@@ -115,7 +115,6 @@ private:
 
 	// Internal helper functions.
 	bool drawNextFrame();
-	void processTimeEventHandlers();
 };
 
 } // End of namespace MediaStation
diff --git a/engines/mediastation/assets/timer.cpp b/engines/mediastation/assets/timer.cpp
index dda6eb6b3f3..5af7507b856 100644
--- a/engines/mediastation/assets/timer.cpp
+++ b/engines/mediastation/assets/timer.cpp
@@ -87,27 +87,7 @@ void Timer::timeStop() {
 }
 
 void Timer::process() {
-	if (!_isActive) {
-		error("Timer::processTimeEventHandlers(): Attempted to process time event handlers while not playing");
-		return;
-	}
-
-	uint currentTime = g_system->getMillis();
-	//uint movieTime = currentTime - _startTime;
-	debugC(7, kDebugScript, "** Timer %d: ON TIME Event Handlers **", _header->_id);
-	for (EventHandler *timeEvent : _header->_timeHandlers) {
-		double timeEventInFractionalSeconds = timeEvent->_argumentValue.u.f;
-		uint timeEventInMilliseconds = timeEventInFractionalSeconds * 1000;
-		bool timeEventAlreadyProcessed = timeEventInMilliseconds < _lastProcessedTime;
-		bool timeEventNeedsToBeProcessed = timeEventInMilliseconds <= currentTime - _startTime;
-		if (!timeEventAlreadyProcessed && timeEventNeedsToBeProcessed) {
-			// TODO: What happens when we try re-run the timer when itʻs already
-			// running? Seems like this would cause re-entrancy issues.
-			timeEvent->execute(_header->_id);
-		}
-	}
-	debugC(7, kDebugScript, "** Timer %d: End ON TIME Event Handlers **", _header->_id);
-	_lastProcessedTime = currentTime - _startTime;
+	processTimeEventHandlers();
 }
 
 } // End of namespace MediaStation


Commit: ca602eabc25c9bca1d93e4c42ca2d0970a179dc3
    https://github.com/scummvm/scummvm/commit/ca602eabc25c9bca1d93e4c42ca2d0970a179dc3
Author: Nathanael Gentry (nathanael.gentrydb8 at gmail.com)
Date: 2025-01-26T14:29:06-05:00

Commit Message:
MEDIASTATION: Clean up Sound asset

Changed paths:
    engines/mediastation/assets/sound.cpp
    engines/mediastation/assets/sound.h


diff --git a/engines/mediastation/assets/sound.cpp b/engines/mediastation/assets/sound.cpp
index 43037a6e1d9..f7170b01b82 100644
--- a/engines/mediastation/assets/sound.cpp
+++ b/engines/mediastation/assets/sound.cpp
@@ -19,10 +19,12 @@
  *
  */
 
+#include "audio/decoders/raw.h"
 #include "audio/decoders/adpcm.h"
 
 #include "mediastation/debugchannels.h"
 #include "mediastation/assets/sound.h"
+#include "mediastation/mediastation.h"
 
 namespace MediaStation {
 
@@ -33,50 +35,67 @@ Sound::Sound(AssetHeader *header) : Asset(header) {
 }
 
 Sound::~Sound() {
-	delete _samples;
-	_samples = nullptr;
-	//for (Audio::SeekableAudioStream *stream : _streams) {
-	//    delete stream;
-	//}
+	for (Audio::SeekableAudioStream *stream : _streams) {
+		delete stream;
+	}
+	_streams.clear();
+}
+
+void Sound::process() {
+	processTimeEventHandlers();
+	if (!g_engine->_mixer->isSoundHandleActive(_handle)) {
+		_isActive = false;
+		_startTime = 0;
+		_lastProcessedTime = 0;
+		_handle = Audio::SoundHandle();
+
+		runEventHandlerIfExists(kSoundEndEvent);
+	}
 }
 
 Operand Sound::callMethod(BuiltInMethod methodId, Common::Array<Operand> &args) {
 	switch (methodId) {
-	default: {
-		error("Got unimplemented method ID %d", methodId);
+	case kTimePlayMethod: {
+		assert(args.empty());
+		timePlay();
+		return Operand();
 	}
+
+	case kTimeStopMethod: {
+		assert(args.empty());
+		timeStop();
+		return Operand();
 	}
-}
 
-void Sound::process() {
-	// TODO: Process more playing.
+	default: {
+		error("Sound::callMethod(): Got unimplemented method %s (%d)", builtInMethodToStr(methodId), methodId);
+	}
+	}
 }
 
 void Sound::readChunk(Chunk &chunk) {
-	// TODO: Can we read the chunk directly into the audio stream?
-	debugC(5, kDebugLoading, "Sound::readChunk(): (encoding = 0x%x) Reading audio chunk (@0x%llx)", static_cast<uint>(_encoding), static_cast<long long int>(chunk.pos()));
 	byte *buffer = (byte *)malloc(chunk._length);
 	chunk.read((void *)buffer, chunk._length);
-
-	switch (_encoding) {
-	case SoundEncoding::PCM_S16LE_MONO_22050: {
-		// Audio::SeekableAudioStream *stream = Audio::makeRawStream(buffer, chunk.length, Sound::RATE, Sound::FLAGS, DisposeAfterUse::NO);
-		//_streams.push_back(stream);
+	Audio::SeekableAudioStream *stream = nullptr;
+	switch (_header->_soundEncoding) {
+	case SoundEncoding::PCM_S16LE_MONO_22050:
+		stream = Audio::makeRawStream(buffer, chunk._length, 22050, Audio::FLAG_16BITS | Audio::FLAG_LITTLE_ENDIAN, DisposeAfterUse::NO);
 		break;
-	}
 
-	case SoundEncoding::IMA_ADPCM_S16LE_MONO_22050: {
-		// TODO: Support ADPCM decoding.
-		// Audio::SeekableAudioStream *stream = nullptr; // Audio::makeADPCMStream(buffer, chunk.length, DisposeAfterUse::NO, Audio::ADPCMType::kADPCMMSIma, Sound::RATE, 1, 4);
-		//_streams.push_back(stream);
+	case SoundEncoding::IMA_ADPCM_S16LE_MONO_22050:
+		// TODO: The interface here is different. We can't pass in the
+		// buffers directly. We have to make a stream first.
+		// stream = Audio::makeADPCMStream(buffer, chunk.length,
+		// DisposeAfterUse::NO, Audio::ADPCMType::kADPCMMSIma, 22050, 1,
+		// 4);
+		warning("Sound::readSubfile(): ADPCM decoding not implemented yet");
+		chunk.skip(chunk.bytesRemaining());
 		break;
-	}
 
-	default: {
-		error("Sound::readChunk(): Unknown audio encoding 0x%x", static_cast<uint>(_encoding));
-		break;
-	}
+	default:
+		error("Sound::readChunk(): Unknown audio encoding 0x%x", static_cast<uint>(_header->_soundEncoding));
 	}
+	_streams.push_back(stream);
 	debugC(5, kDebugLoading, "Sound::readChunk(): Finished reading audio chunk (@0x%llx)", static_cast<long long int>(chunk.pos()));
 }
 
@@ -96,4 +115,45 @@ void Sound::readSubfile(Subfile &subfile, Chunk &chunk) {
 	}
 }
 
+void Sound::timePlay() {
+	if (_isActive) {
+		warning("Sound::timePlay(): Attempt to play a sound that is already playing");
+		return;
+	}
+	_isActive = true;
+	g_engine->addPlayingAsset(this);
+
+	_startTime = g_system->getMillis();
+	_lastProcessedTime = 0;
+	_handle = Audio::SoundHandle();
+
+	runEventHandlerIfExists(kSoundBeginEvent);
+
+	if (!_streams.empty()) {
+		Audio::QueuingAudioStream *audio = Audio::makeQueuingAudioStream(22050, false);
+		for (Audio::SeekableAudioStream *stream : _streams) {
+			stream->rewind();
+			audio->queueAudioStream(stream, DisposeAfterUse::NO);
+		}
+		g_engine->_mixer->playStream(Audio::Mixer::kPlainSoundType, &_handle, audio, -1, Audio::Mixer::kMaxChannelVolume, DisposeAfterUse::YES);
+		audio->finish();
+	}
+}
+
+void Sound::timeStop() {
+	if (!_isActive) {
+		warning("Sound::timeStop(): Attempt to stop a sound that isn't playing");
+		return;
+	}
+
+	_isActive = false;
+	_startTime = 0;
+	_lastProcessedTime = 0;
+
+	g_engine->_mixer->stopHandle(_handle);
+	_handle = Audio::SoundHandle();
+
+	runEventHandlerIfExists(kSoundStoppedEvent);
 }
+
+} // End of namespace MediaStation
diff --git a/engines/mediastation/assets/sound.h b/engines/mediastation/assets/sound.h
index edbfa36bb4e..adba464e98c 100644
--- a/engines/mediastation/assets/sound.h
+++ b/engines/mediastation/assets/sound.h
@@ -19,12 +19,10 @@
  *
  */
 
-#ifndef MEDIASTATION_SOUND_H
-#define MEDIASTATION_SOUND_H
+#ifndef MEDIASTATION_ASSETS_SOUND_H
+#define MEDIASTATION_ASSETS_SOUND_H
 
-#include "audio/mixer.h"
 #include "audio/audiostream.h"
-#include "audio/decoders/raw.h"
 
 #include "mediastation/asset.h"
 #include "mediastation/chunk.h"
@@ -37,12 +35,7 @@ namespace MediaStation {
 
 class Sound : public Asset {
 public:
-	// For standalone Sound assets.
 	Sound(AssetHeader *header);
-
-	// For sounds that are part of a movie.
-	// TODO: Since these aren't Assets they should be handled elsewhere.
-	//Sound(AssetHeader::SoundEncoding encoding);
 	~Sound();
 
 	virtual Operand callMethod(BuiltInMethod methodId, Common::Array<Operand> &args) override;
@@ -51,14 +44,14 @@ public:
 	virtual void readChunk(Chunk& chunk) override;
 	virtual void readSubfile(Subfile &subFile, Chunk &chunk) override;
 
-	// All Media Station audio is signed 16-bit little-endian mono at 22050 Hz.
-	// Some defaults must be overridden in the flags.
-	static const uint RATE = 22050;
-	static const byte FLAGS = Audio::FLAG_16BITS | Audio::FLAG_LITTLE_ENDIAN;
-
 private:
 	SoundEncoding _encoding;
-	byte *_samples = nullptr;
+	Audio::SoundHandle _handle;
+	Common::Array<Audio::SeekableAudioStream *> _streams;
+
+	// Script method implementations
+	void timePlay();
+	void timeStop();
 };
 
 } // End of namespace MediaStation


Commit: 8e37b9a59695c15ddac5e22334be5f769f981f9e
    https://github.com/scummvm/scummvm/commit/8e37b9a59695c15ddac5e22334be5f769f981f9e
Author: Nathanael Gentry (nathanael.gentrydb8 at gmail.com)
Date: 2025-01-26T14:29:06-05:00

Commit Message:
MEDIASTATION: Implement hotspot enter/exit handling logic

Changed paths:
    engines/mediastation/assets/hotspot.cpp
    engines/mediastation/assets/hotspot.h
    engines/mediastation/mediastation.cpp
    engines/mediastation/mediastation.h


diff --git a/engines/mediastation/assets/hotspot.cpp b/engines/mediastation/assets/hotspot.cpp
index c06cf852cb2..7bc08218d22 100644
--- a/engines/mediastation/assets/hotspot.cpp
+++ b/engines/mediastation/assets/hotspot.cpp
@@ -20,6 +20,7 @@
  */
 
 #include "mediastation/assets/hotspot.h"
+#include "mediastation/mediastation.h"
 
 namespace MediaStation {
 
@@ -29,11 +30,50 @@ Hotspot::Hotspot(AssetHeader *header) : Asset(header) {
 	}
 }
 
+bool Hotspot::isInside(const Common::Point &pointToCheck) {
+	// No sense checking the polygon if we're not even in the bbox.
+	if (!_header->_boundingBox->contains(pointToCheck)) {
+		return false;
+	}
+
+	// We're in the bbox, but there might not be a polygon to check.
+	if (_header->_mouseActiveArea.empty()) {
+		return true;
+	}
+
+	// Polygon intersection code adapted from HADESCH engine, might need more
+	// refinement once more testing is possible.
+	Common::Point point = pointToCheck - Common::Point(_header->_boundingBox->left, _header->_boundingBox->top);
+	int rcross = 0; // Number of right-side overlaps
+
+	// Each edge is checked whether it cuts the outgoing stream from the point
+	Common::Array<Common::Point *> _polygon = _header->_mouseActiveArea;
+	for (unsigned i = 0; i < _polygon.size(); i++) {
+		const Common::Point &edgeStart = *_polygon[i];
+		const Common::Point &edgeEnd = *_polygon[(i + 1) % _polygon.size()];
+
+		// A vertex is a point? Then it lies on one edge of the polygon
+		if (point == edgeStart)
+			return true;
+
+		if ((edgeStart.y > point.y) != (edgeEnd.y > point.y)) {
+			int term1 = (edgeStart.x - point.x) * (edgeEnd.y - point.y) - (edgeEnd.x - point.x) * (edgeStart.y - point.y);
+			int term2 = (edgeEnd.y - point.y) - (edgeStart.y - edgeEnd.y);
+			if ((term1 > 0) == (term2 >= 0))
+				rcross++;
+		}
+	}
+
+	// The point is strictly inside the polygon if and only if the number of overlaps is odd
+	return ((rcross % 2) == 1);
+}
+
 Operand Hotspot::callMethod(BuiltInMethod methodId, Common::Array<Operand> &args) {
 	switch (methodId) {
 	case kMouseActivateMethod: {
 		assert(args.empty());
 		_isActive = true;
+		g_engine->addPlayingAsset(this);
 		return Operand();
 	}
 
@@ -44,7 +84,7 @@ Operand Hotspot::callMethod(BuiltInMethod methodId, Common::Array<Operand> &args
 	}
 
 	default: {
-		error("Got unimplemented method ID %d", methodId);
+		error("Hotspot::callMethod(): Got unimplemented method ID %d", methodId);
 	}
 	}
 }
diff --git a/engines/mediastation/assets/hotspot.h b/engines/mediastation/assets/hotspot.h
index 6818ffd000e..d72b0237555 100644
--- a/engines/mediastation/assets/hotspot.h
+++ b/engines/mediastation/assets/hotspot.h
@@ -34,6 +34,8 @@ public:
 	Hotspot(AssetHeader *header);
 	virtual ~Hotspot() override = default;
 
+	bool isInside(const Common::Point &pointToCheck);
+
 	virtual Operand callMethod(BuiltInMethod methodId, Common::Array<Operand> &args) override;
 };
 
diff --git a/engines/mediastation/mediastation.cpp b/engines/mediastation/mediastation.cpp
index 4f84cac81ba..966cb1b1756 100644
--- a/engines/mediastation/mediastation.cpp
+++ b/engines/mediastation/mediastation.cpp
@@ -28,6 +28,7 @@
 #include "mediastation/boot.h"
 #include "mediastation/context.h"
 #include "mediastation/asset.h"
+#include "mediastation/assets/hotspot.h"
 #include "mediastation/assets/movie.h"
 #include "mediastation/mediascript/scriptconstants.h"
 
@@ -186,9 +187,31 @@ void MediaStationEngine::processEvents() {
 			return;
 		}
 
+		case Common::EVENT_MOUSEMOVE: {
+			Asset *hotspot = findAssetToAcceptMouseEvents(e.mouse);
+			if (hotspot != nullptr) {
+				if (_currentHotspot == nullptr) {
+					_currentHotspot = hotspot;
+					debugC(5, kDebugEvents, "EVENT_MOUSEMOVE (%d, %d): Entered hotspot %d", e.mouse.x, e.mouse.y, hotspot->getHeader()->_id);
+					hotspot->runEventHandlerIfExists(kMouseEnteredEvent);
+				} else if (_currentHotspot == hotspot) {
+					// We are still in the same hotspot.
+				} else {
+					_currentHotspot->runEventHandlerIfExists(kMouseExitedEvent);
+					_currentHotspot = hotspot;
+					debugC(5, kDebugEvents, "EVENT_MOUSEMOVE (%d, %d): Exited hotspot %d", e.mouse.x, e.mouse.y, hotspot->getHeader()->_id);
+					hotspot->runEventHandlerIfExists(kMouseEnteredEvent);
+				}
+				debugC(5, kDebugEvents, "EVENT_MOUSEMOVE (%d, %d): Sent to hotspot %d", e.mouse.x, e.mouse.y, hotspot->getHeader()->_id);
+				hotspot->runEventHandlerIfExists(kMouseMovedEvent);
+			} else {
+				_currentHotspot = nullptr;
+			}
+			break;
+		}
+
 		case Common::EVENT_KEYDOWN: {
-			// TODO: Reading the current mouse position for hotspots might not
-			// be right, need to verify.
+			// Even though this is a keydown event, we need to look at the mouse position.
 			Common::Point mousePos = g_system->getEventManager()->getMousePos();
 			Asset *hotspot = findAssetToAcceptMouseEvents(mousePos);
 			if (hotspot != nullptr) {
@@ -381,19 +404,8 @@ Asset *MediaStationEngine::findAssetToAcceptMouseEvents(Common::Point point) {
 	int lowestZIndex = INT_MAX;
 
 	for (Asset *asset : _assetsPlaying) {
-		// TODO: Currently only hotspots are found, but other asset types can
-		// likely get mouse events too.
 		if (asset->type() == kAssetTypeHotspot) {
-			Common::Rect *boundingBox = asset->getHeader()->_boundingBox;
-			if (boundingBox == nullptr) {
-				error("Hotspot %d has no bounding box", asset->getHeader()->_id);
-			}
-
-			if (!asset->isActive()) {
-				continue;
-			}
-
-			if (boundingBox->contains(point)) {
+			if (asset->isActive() && static_cast<Hotspot *>(asset)->isInside(point)) {
 				if (asset->zIndex() < lowestZIndex) {
 					lowestZIndex = asset->zIndex();
 					intersectingAsset = asset;
diff --git a/engines/mediastation/mediastation.h b/engines/mediastation/mediastation.h
index 87a1955d0ce..c4a1ae2abc0 100644
--- a/engines/mediastation/mediastation.h
+++ b/engines/mediastation/mediastation.h
@@ -103,6 +103,7 @@ private:
 	Boot *_boot = nullptr;
 	Common::Array<Asset *> _assetsPlaying;
 	Common::HashMap<uint, Context *> _loadedContexts;
+	Asset *_currentHotspot = nullptr;
 
 	Context *loadContext(uint32 contextId);
 	void setPaletteFromHeader(AssetHeader *header);


Commit: 142460fcb2c7c63f522af6a54af6531cae62dff6
    https://github.com/scummvm/scummvm/commit/142460fcb2c7c63f522af6a54af6531cae62dff6
Author: Nathanael Gentry (nathanael.gentrydb8 at gmail.com)
Date: 2025-01-26T14:29:06-05:00

Commit Message:
MEDIASTATION: Stub out more script methods

Changed paths:
    engines/mediastation/assets/image.cpp
    engines/mediastation/assets/movie.cpp
    engines/mediastation/assets/path.cpp
    engines/mediastation/assets/sprite.cpp


diff --git a/engines/mediastation/assets/image.cpp b/engines/mediastation/assets/image.cpp
index edaa22a75c9..b0d16c4a272 100644
--- a/engines/mediastation/assets/image.cpp
+++ b/engines/mediastation/assets/image.cpp
@@ -21,6 +21,7 @@
 
 #include "mediastation/mediastation.h"
 #include "mediastation/assets/image.h"
+#include "mediastation/debugchannels.h"
 
 namespace MediaStation {
 
@@ -52,6 +53,13 @@ Operand Image::callMethod(BuiltInMethod methodId, Common::Array<Operand> &args)
 		break;
 	}
 
+	case kSetDissolveFactorMethod: {
+		assert(args.size() == 1);
+		warning("Image::callMethod(): setDissolveFactor not implemented yet");
+		return Operand();
+	}
+
+
 	default: {
 		error("Image::callMethod(): Got unimplemented method ID %d", methodId);
 	}
diff --git a/engines/mediastation/assets/movie.cpp b/engines/mediastation/assets/movie.cpp
index 96fc0e24760..2da51c9964c 100644
--- a/engines/mediastation/assets/movie.cpp
+++ b/engines/mediastation/assets/movie.cpp
@@ -186,6 +186,24 @@ Operand Movie::callMethod(BuiltInMethod methodId, Common::Array<Operand> &args)
 		return Operand();
 	}
 
+	case kSpatialShowMethod: {
+		assert(args.empty());
+		warning("Movie::callMethod(): spatialShow not implemented");
+		return Operand();
+	}
+
+	case kTimeStopMethod: {
+		assert(args.empty());
+		timeStop();
+		return Operand();
+	}
+
+	case kSpatialHideMethod: {
+		assert(args.empty());
+		warning("Movie::callMethod(): spatialHide not implemented");
+		return Operand();
+	}
+
 	default: {
 		error("Got unimplemented method ID %d", methodId);
 	}
diff --git a/engines/mediastation/assets/path.cpp b/engines/mediastation/assets/path.cpp
index e03f472a7ff..9a8dae48747 100644
--- a/engines/mediastation/assets/path.cpp
+++ b/engines/mediastation/assets/path.cpp
@@ -50,6 +50,12 @@ Operand Path::callMethod(BuiltInMethod methodId, Common::Array<Operand> &args) {
 		return returnValue;
 	}
 
+	case kSetDissolveFactorMethod: {
+		assert(args.size() == 1);
+		warning("Path::callMethod(): setDissolveFactor not implemented yet");
+		return Operand();
+	}
+
 	default: {
 		error("Got unimplemented method ID %d", methodId);
 	}
diff --git a/engines/mediastation/assets/sprite.cpp b/engines/mediastation/assets/sprite.cpp
index 201987b6dea..f3aae059dae 100644
--- a/engines/mediastation/assets/sprite.cpp
+++ b/engines/mediastation/assets/sprite.cpp
@@ -87,6 +87,18 @@ Operand Sprite::callMethod(BuiltInMethod methodId, Common::Array<Operand> &args)
 		return Operand();
 	}
 
+	case kSpatialHideMethod: {
+		assert(args.empty());
+		_isActive = false;
+		return Operand();
+	}
+
+	case kTimeStopMethod: {
+		assert(args.empty());
+		_isActive = false;
+		return Operand();
+	}
+
 	case kTimePlayMethod: {
 		assert(args.size() == 0);
 		timePlay();




More information about the Scummvm-git-logs mailing list