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

npjg noreply at scummvm.org
Fri Apr 11 18:55:08 UTC 2025


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

Summary:
69ec1c9f4b MEDIASTATION: Rename operand (value) types to be correct
4c2e5f242f MEDIASTATION: Factor expression evaluation into separate methods
ce179e0495 MEDIASTATION: Rename Operand to ScriptValue
b48dd3cff4 MEDIASTATION: Correctly implement script value type methods
04d375ea21 MEDIASTATION: Unify script value and event handler types
997c76fdfc MEDIASTATION: Add original script execution and return behavior
b7c9e1a75d MEDIASTATION: Factor opcodes into their own functions


Commit: 69ec1c9f4b51d2cbdc79de60690108a655dc4c5e
    https://github.com/scummvm/scummvm/commit/69ec1c9f4b51d2cbdc79de60690108a655dc4c5e
Author: Nathanael Gentry (nathanael.gentrydb8 at gmail.com)
Date: 2025-04-11T14:54:23-04:00

Commit Message:
MEDIASTATION: Rename operand (value) types to be correct

Changed paths:
    engines/mediastation/assets/hotspot.cpp
    engines/mediastation/assets/movie.cpp
    engines/mediastation/assets/path.cpp
    engines/mediastation/assets/sprite.cpp
    engines/mediastation/assets/timer.cpp
    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/mediastation.cpp


diff --git a/engines/mediastation/assets/hotspot.cpp b/engines/mediastation/assets/hotspot.cpp
index d0a2a0a169c..21d005bf6fa 100644
--- a/engines/mediastation/assets/hotspot.cpp
+++ b/engines/mediastation/assets/hotspot.cpp
@@ -87,19 +87,19 @@ Operand Hotspot::callMethod(BuiltInMethod methodId, Common::Array<Operand> &args
 
 	case kIsActiveMethod: {
 		assert(args.empty());
-		Operand returnValue(kOperandTypeLiteral1);
+		Operand returnValue(kOperandTypeBool);
 		returnValue.putInteger(static_cast<int>(_isActive));
 		return returnValue;
 	}
 
 	case kTriggerAbsXPositionMethod: {
-		Operand returnValue(kOperandTypeLiteral1);
+		Operand returnValue(kOperandTypeBool);
 		returnValue.putInteger(g_engine->_mousePos.x);
 		return returnValue;
 	}
 
 	case kTriggerAbsYPositionMethod: {
-		Operand returnValue(kOperandTypeLiteral1);
+		Operand returnValue(kOperandTypeBool);
 		returnValue.putInteger(g_engine->_mousePos.y);
 		return returnValue;
 	}
diff --git a/engines/mediastation/assets/movie.cpp b/engines/mediastation/assets/movie.cpp
index e75f9dd5b39..c188a5f4228 100644
--- a/engines/mediastation/assets/movie.cpp
+++ b/engines/mediastation/assets/movie.cpp
@@ -214,7 +214,7 @@ Operand Movie::callMethod(BuiltInMethod methodId, Common::Array<Operand> &args)
 
 	case kIsVisibleMethod: {
 		assert(args.empty());
-		Operand returnValue(kOperandTypeLiteral1);
+		Operand returnValue(kOperandTypeBool);
 		returnValue.putInteger(_isShowing);
 		return returnValue;
 	}
@@ -233,14 +233,14 @@ Operand Movie::callMethod(BuiltInMethod methodId, Common::Array<Operand> &args)
 
 	case kIsPlayingMethod: {
 		assert(args.empty());
-		Operand returnValue(kOperandTypeLiteral1);
+		Operand returnValue(kOperandTypeBool);
 		returnValue.putInteger(static_cast<uint>(_isPlaying));
 		return returnValue;
 	}
 
 	case kXPositionMethod: {
 		assert(args.empty());
-		Operand returnValue(kOperandTypeLiteral1);
+		Operand returnValue(kOperandTypeBool);
 		returnValue.putInteger(_header->_boundingBox->left);
 		return returnValue;
 
@@ -248,7 +248,7 @@ Operand Movie::callMethod(BuiltInMethod methodId, Common::Array<Operand> &args)
 
 	case kYPositionMethod: {
 		assert(args.empty());
-		Operand returnValue(kOperandTypeLiteral1);
+		Operand returnValue(kOperandTypeBool);
 		returnValue.putInteger(_header->_boundingBox->top);
 		return returnValue;
 	}
diff --git a/engines/mediastation/assets/path.cpp b/engines/mediastation/assets/path.cpp
index c6744ceab6e..70657ed3b9c 100644
--- a/engines/mediastation/assets/path.cpp
+++ b/engines/mediastation/assets/path.cpp
@@ -45,7 +45,7 @@ Operand Path::callMethod(BuiltInMethod methodId, Common::Array<Operand> &args) {
 
 	case kPercentCompleteMethod: {
 		assert(args.size() == 0);
-		Operand returnValue(kOperandTypeFloat1);
+		Operand returnValue(kOperandTypeTime);
 		returnValue.putDouble(percentComplete());
 		return returnValue;
 	}
@@ -58,7 +58,7 @@ Operand Path::callMethod(BuiltInMethod methodId, Common::Array<Operand> &args) {
 
 	case kIsPlayingMethod: {
 		assert(args.empty());
-		Operand returnValue(kOperandTypeLiteral1);
+		Operand returnValue(kOperandTypeBool);
 		returnValue.putInteger(_isActive);
 		return returnValue;
 	}
diff --git a/engines/mediastation/assets/sprite.cpp b/engines/mediastation/assets/sprite.cpp
index c3af44070a9..6c1dbc6d3bc 100644
--- a/engines/mediastation/assets/sprite.cpp
+++ b/engines/mediastation/assets/sprite.cpp
@@ -135,7 +135,7 @@ Operand Sprite::callMethod(BuiltInMethod methodId, Common::Array<Operand> &args)
 	}
 
 	case kIsPlayingMethod: {
-		Operand returnValue(kOperandTypeLiteral1);
+		Operand returnValue(kOperandTypeBool);
 		returnValue.putInteger(static_cast<int>(_isPlaying));
 		return returnValue;
 	}
diff --git a/engines/mediastation/assets/timer.cpp b/engines/mediastation/assets/timer.cpp
index bd5b53afd05..097cc992274 100644
--- a/engines/mediastation/assets/timer.cpp
+++ b/engines/mediastation/assets/timer.cpp
@@ -42,7 +42,7 @@ Operand Timer::callMethod(BuiltInMethod methodId, Common::Array<Operand> &args)
 
 	case kIsPlayingMethod: {
 		assert(args.size() == 0);
-		Operand returnValue(kOperandTypeLiteral1);
+		Operand returnValue(kOperandTypeBool);
 		returnValue.putInteger(static_cast<int>(_isActive));
 		return returnValue;
 	}
diff --git a/engines/mediastation/mediascript/codechunk.cpp b/engines/mediastation/mediascript/codechunk.cpp
index 810aa3f8c38..36757c989e3 100644
--- a/engines/mediastation/mediascript/codechunk.cpp
+++ b/engines/mediastation/mediascript/codechunk.cpp
@@ -129,7 +129,7 @@ Operand CodeChunk::executeNextStatement() {
 			debugCN(5, kDebugScript, "    rhs: ");
 			Operand value2 = executeNextStatement();
 
-			Operand returnValue(kOperandTypeLiteral1);
+			Operand returnValue(kOperandTypeBool);
 			bool logicalOr = (value1 || value2);
 			returnValue.putInteger(static_cast<uint>(logicalOr));
 			return returnValue;
@@ -139,7 +139,7 @@ Operand CodeChunk::executeNextStatement() {
 			debugCN(5, kDebugScript, "\n    value: ");
 			Operand value = executeNextStatement();
 
-			Operand returnValue(kOperandTypeLiteral1);
+			Operand returnValue(kOperandTypeBool);
 			bool logicalNot = !(static_cast<bool>(value.getInteger()));
 			returnValue.putInteger(static_cast<uint>(logicalNot));
 			return returnValue;
@@ -151,7 +151,7 @@ Operand CodeChunk::executeNextStatement() {
 			debugCN(5, kDebugScript, "    rhs: ");
 			Operand value2 = executeNextStatement();
 
-			Operand returnValue(kOperandTypeLiteral1);
+			Operand returnValue(kOperandTypeBool);
 			bool logicalAnd = (value1 && value2);
 			returnValue.putInteger(static_cast<uint>(logicalAnd));
 			return returnValue;
@@ -181,8 +181,7 @@ Operand CodeChunk::executeNextStatement() {
 			debugCN(5, kDebugScript, "    rhs: ");
 			Operand value2 = executeNextStatement();
 
-			// TODO: Confirm this is the correct value type?
-			Operand returnValue(kOperandTypeLiteral1);
+			Operand returnValue(kOperandTypeBool);
 			bool equal = (value1 == value2);
 			returnValue.putInteger(static_cast<uint>(equal));
 			return returnValue;
@@ -194,8 +193,7 @@ Operand CodeChunk::executeNextStatement() {
 			debugCN(5, kDebugScript, "    rhs: ");
 			Operand value2 = executeNextStatement();
 
-			// TODO: Confirm this is the correct value type?
-			Operand returnValue(kOperandTypeLiteral1);
+			Operand returnValue(kOperandTypeBool);
 			bool notEqual = !(value1 == value2);
 			returnValue.putInteger(static_cast<uint>(notEqual));
 			return returnValue;
@@ -207,8 +205,7 @@ Operand CodeChunk::executeNextStatement() {
 			debugCN(5, kDebugScript, "    rhs: ");
 			Operand value2 = executeNextStatement();
 
-			// TODO: Confirm this is the correct value type?
-			Operand returnValue(kOperandTypeLiteral1);
+			Operand returnValue(kOperandTypeBool);
 			bool lessThan = (value1 < value2);
 			returnValue.putInteger(static_cast<uint>(lessThan));
 			return returnValue;
@@ -220,8 +217,7 @@ Operand CodeChunk::executeNextStatement() {
 			debugCN(5, kDebugScript, "    rhs: ");
 			Operand value2 = executeNextStatement();
 
-			// TODO: Confirm this is the correct value type?
-			Operand returnValue(kOperandTypeLiteral1);
+			Operand returnValue(kOperandTypeBool);
 			bool greaterThan = (value1 > value2);
 			returnValue.putInteger(static_cast<uint>(greaterThan));
 			return returnValue;
@@ -233,8 +229,7 @@ Operand CodeChunk::executeNextStatement() {
 			debugCN(5, kDebugScript, "    rhs: ");
 			Operand value2 = executeNextStatement();
 
-			// TODO: Confirm this is the correct value type?
-			Operand returnValue(kOperandTypeLiteral1);
+			Operand returnValue(kOperandTypeBool);
 			bool lessThanOrEqualTo = (value1 < value2) || (value1 == value2);
 			returnValue.putInteger(static_cast<uint>(lessThanOrEqualTo));
 			return returnValue;
@@ -246,8 +241,7 @@ Operand CodeChunk::executeNextStatement() {
 			debugCN(5, kDebugScript, "    rhs: ");
 			Operand value2 = executeNextStatement();
 
-			// TODO: Confirm this is the correct value type?
-			Operand returnValue(kOperandTypeLiteral1);
+			Operand returnValue(kOperandTypeBool);
 			bool greaterThanOrEqualTo = (value1 > value2) || (value1 == value2);
 			returnValue.putInteger(static_cast<uint>(greaterThanOrEqualTo));
 			return returnValue;
@@ -344,8 +338,8 @@ Operand CodeChunk::executeNextStatement() {
 			return operand;
 		}
 
-		case kOperandTypeLiteral1:
-		case kOperandTypeLiteral2:
+		case kOperandTypeBool:
+		case kOperandTypeInt:
 		case kOperandTypeDollarSignVariable: {
 			int literal = Datum(*_bytecode).u.i;
 			debugC(5, kDebugScript, "%d ", literal);
@@ -353,22 +347,22 @@ Operand CodeChunk::executeNextStatement() {
 			return operand;
 		}
 
-		case kOperandTypeFloat1:
-		case kOperandTypeFloat2: {
+		case kOperandTypeFloat:
+		case kOperandTypeTime: {
 			double d = Datum(*_bytecode).u.f;
 			debugC(5, kDebugScript, "%f ", d);
 			operand.putDouble(d);
 			return operand;
 		}
 
-		case kOperandTypeMethod: {
+		case kOperandTypeMethodId: {
 			BuiltInMethod methodId = static_cast<BuiltInMethod>(Datum(*_bytecode).u.i);
 			debugC(5, kDebugScript, "%s ", builtInMethodToStr(methodId));
 			operand.putMethodId(methodId);
 			return operand;
 		}
 
-		case kOperandTypeFunction: {
+		case kOperandTypeFunctionId: {
 			uint functionId = Datum(*_bytecode).u.i;
 			debugC(5, kDebugScript, "%d ", functionId);
 			operand.putFunctionId(functionId);
@@ -434,7 +428,7 @@ Operand CodeChunk::callFunction(uint functionId, uint parameterCount) {
 Operand CodeChunk::getVariable(uint32 id, VariableScope scope) {
 	switch (scope) {
 	case kVariableScopeGlobal: {
-		Operand returnValue(kOperandTypeVariableDeclaration);
+		Operand returnValue(kOperandTypeVariable);
 		Variable *variable = g_engine->_variables.getVal(id);
 		returnValue.putVariable(variable);
 		return returnValue;
diff --git a/engines/mediastation/mediascript/operand.cpp b/engines/mediastation/mediascript/operand.cpp
index f0a10727b32..3518462aedc 100644
--- a/engines/mediastation/mediascript/operand.cpp
+++ b/engines/mediastation/mediascript/operand.cpp
@@ -27,14 +27,14 @@ namespace MediaStation {
 
 void Operand::putInteger(int i) {
 	switch (_type) {
-	case kOperandTypeLiteral1:
-	case kOperandTypeLiteral2:
+	case kOperandTypeBool:
+	case kOperandTypeInt:
 	case kOperandTypeDollarSignVariable: {
 		_u.i = i;
 		break;
 	}
 
-	case kOperandTypeVariableDeclaration: {
+	case kOperandTypeVariable: {
 		_u.variable->_value.i = i;
 		break;
 	}
@@ -47,17 +47,17 @@ void Operand::putInteger(int i) {
 
 int Operand::getInteger() {
 	switch (_type) {
-	case kOperandTypeLiteral1:
-	case kOperandTypeLiteral2:
+	case kOperandTypeBool:
+	case kOperandTypeInt:
 	case kOperandTypeDollarSignVariable: {
 		return _u.i;
 	}
 
-	case kOperandTypeFloat1: {
+	case kOperandTypeTime: {
 		return static_cast<int>(_u.d);
 	}
 
-	case kOperandTypeVariableDeclaration: {
+	case kOperandTypeVariable: {
 		return _u.variable->_value.i;
 	}
 
@@ -69,13 +69,13 @@ int Operand::getInteger() {
 
 void Operand::putDouble(double d) {
 	switch (_type) {
-	case kOperandTypeFloat1:
-	case kOperandTypeFloat2: {
+	case kOperandTypeTime:
+	case kOperandTypeFloat: {
 		_u.d = d;
 		break;
 	}
 
-	case kOperandTypeVariableDeclaration: {
+	case kOperandTypeVariable: {
 		// TODO: Add assertion.
 		_u.variable->_value.d = d;
 		break;
@@ -89,17 +89,17 @@ void Operand::putDouble(double d) {
 
 double Operand::getDouble() {
 	switch (_type) {
-	case kOperandTypeFloat1:
-	case kOperandTypeFloat2: {
+	case kOperandTypeTime:
+	case kOperandTypeFloat: {
 		return _u.d;
 	}
 
-	case kOperandTypeLiteral1:
-	case kOperandTypeLiteral2: {
+	case kOperandTypeBool:
+	case kOperandTypeInt: {
 		return static_cast<double>(_u.i);
 	}
 
-	case kOperandTypeVariableDeclaration: {
+	case kOperandTypeVariable: {
 		// TODO: Add assertion that this is the proper type.
 		return _u.variable->_value.d;
 	}
@@ -117,7 +117,7 @@ void Operand::putString(Common::String *string) {
 		break;
 	}
 
-	case kOperandTypeVariableDeclaration: {
+	case kOperandTypeVariable: {
 		assert(_u.variable->_type == kVariableTypeString);
 		_u.variable->_value.string = string;
 		break;
@@ -135,7 +135,7 @@ Common::String *Operand::getString() {
 		return _u.string;
 	}
 
-	case kOperandTypeVariableDeclaration: {
+	case kOperandTypeVariable: {
 		assert(_u.variable->_type == kVariableTypeString);
 		return _u.variable->_value.string;
 	}
@@ -148,7 +148,7 @@ Common::String *Operand::getString() {
 
 void Operand::putVariable(Variable *variable) {
 	switch (_type) {
-	case kOperandTypeVariableDeclaration: {
+	case kOperandTypeVariable: {
 		_u.variable = variable;
 		break;
 	}
@@ -161,7 +161,7 @@ void Operand::putVariable(Variable *variable) {
 
 Variable *Operand::getVariable() {
 	switch (_type) {
-	case kOperandTypeVariableDeclaration: {
+	case kOperandTypeVariable: {
 		return _u.variable;
 	}
 
@@ -173,7 +173,7 @@ Variable *Operand::getVariable() {
 
 void Operand::putFunctionId(uint functionId) {
 	switch (_type) {
-	case kOperandTypeFunction: {
+	case kOperandTypeFunctionId: {
 		_u.functionId = functionId;
 		break;
 	}
@@ -186,11 +186,11 @@ void Operand::putFunctionId(uint functionId) {
 
 uint Operand::getFunctionId() {
 	switch (_type) {
-	case kOperandTypeFunction: {
+	case kOperandTypeFunctionId: {
 		return _u.functionId;
 	}
 
-	case kOperandTypeVariableDeclaration: {
+	case kOperandTypeVariable: {
 		assert(_u.variable->_type == kVariableTypeFunction);
 		return _u.variable->_value.functionId;
 	}
@@ -203,7 +203,7 @@ uint Operand::getFunctionId() {
 
 void Operand::putMethodId(BuiltInMethod methodId) {
 	switch (_type) {
-	case kOperandTypeMethod: {
+	case kOperandTypeMethodId: {
 		_u.methodId = methodId;
 		break;
 	}
@@ -216,7 +216,7 @@ void Operand::putMethodId(BuiltInMethod methodId) {
 
 BuiltInMethod Operand::getMethodId() {
 	switch (_type) {
-	case kOperandTypeMethod: {
+	case kOperandTypeMethodId: {
 		return _u.methodId;
 	}
 
@@ -233,7 +233,7 @@ void Operand::putAsset(uint32 assetId) {
 		break;
 	}
 
-	case kOperandTypeVariableDeclaration: {
+	case kOperandTypeVariable: {
 		assert(_u.variable->_type == kVariableTypeAssetId);
 		_u.variable->_value.assetId = assetId;
 		break;
@@ -255,7 +255,7 @@ Asset *Operand::getAsset() {
 		}
 	}
 
-	case kOperandTypeVariableDeclaration: {
+	case kOperandTypeVariable: {
 		assert(_u.variable->_type == kVariableTypeAssetId);
 		return g_engine->getAssetById(_u.variable->_value.assetId);
 	}
@@ -272,7 +272,7 @@ uint32 Operand::getAssetId() {
 		return _u.assetId;
 	}
 
-	case kOperandTypeVariableDeclaration: {
+	case kOperandTypeVariable: {
 		assert(_u.variable->_type == kVariableTypeAssetId);
 		return _u.variable->_value.assetId;
 	}
@@ -290,7 +290,7 @@ void Operand::putCollection(Common::SharedPtr<Collection> collection) {
 		break;
 	}
 
-	case kOperandTypeVariableDeclaration: {
+	case kOperandTypeVariable: {
 		assert(_u.variable->_type == kVariableTypeCollection);
 		_u.variable->_c = collection;
 		break;
@@ -308,7 +308,7 @@ Common::SharedPtr<Collection> Operand::getCollection() {
 		return _collection;
 	}
 
-	case kOperandTypeVariableDeclaration: {
+	case kOperandTypeVariable: {
 		assert(_u.variable->_type == kVariableTypeCollection);
 		return _u.variable->_c;
 	}
@@ -322,7 +322,7 @@ Common::SharedPtr<Collection> Operand::getCollection() {
 Operand Operand::getLiteralValue() const {
 	// This function dereferences any variable to get the actual
 	// "direct" value (a literal asset ID or otherwise).
-	if (_type == kOperandTypeVariableDeclaration) {
+	if (_type == kOperandTypeVariable) {
 		return _u.variable->getValue();
 	} else {
 		return *this;
@@ -340,12 +340,12 @@ bool Operand::operator==(const Operand &other) const {
 		return lhsValue == rhsValue;
 	} else {
 		switch (lhs.getType()) {
-		case kOperandTypeLiteral1:
-		case kOperandTypeLiteral2:
+		case kOperandTypeBool:
+		case kOperandTypeInt:
 			return lhs.getInteger() == rhs.getInteger();
 
 		case kOperandTypeAssetId:
-			if (rhs.getType() == kOperandTypeLiteral2) {
+			if (rhs.getType() == kOperandTypeInt) {
 				// This might happen if, for example, a given asset wasn't found
 				// in a collection and the script sets the return value to -1.
 				return static_cast<int>(lhs.getAssetId()) == rhs.getInteger();
@@ -408,8 +408,8 @@ bool Operand::operator||(const Operand &other) const {
 	// 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:
+	case kOperandTypeBool:
+	case kOperandTypeInt:
 		return lhs.getInteger() || rhs.getInteger();
 
 	default:
@@ -423,8 +423,8 @@ bool Operand::operator!() const {
 	// If the types being compared end up being incompatible, the respective get
 	// method will raise the error.
 	switch (literalValue.getType()) {
-	case kOperandTypeLiteral1:
-	case kOperandTypeLiteral2:
+	case kOperandTypeBool:
+	case kOperandTypeInt:
 		return !literalValue.getInteger();
 
 	default:
@@ -439,8 +439,8 @@ bool Operand::operator&&(const Operand &other) const {
 	// If the types being compared end up being incompatible, the respective get
 	// method will raise the error.
 	switch (lhs.getType()) {
-	case kOperandTypeLiteral1:
-	case kOperandTypeLiteral2:
+	case kOperandTypeBool:
+	case kOperandTypeInt:
 		return lhs.getInteger() && rhs.getInteger();
 
 	default:
@@ -545,8 +545,8 @@ Operand Operand::operator%(const Operand &other) const {
 	// 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:
+	case kOperandTypeBool:
+	case kOperandTypeInt:
 		if (rhs.getInteger() == 0) {
 			error("Operand::operator%%(): Attempted mod by zero");
 		}
@@ -565,13 +565,13 @@ Operand Operand::operator-() const {
 	// 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:
+	case kOperandTypeBool:
+	case kOperandTypeInt:
 		returnValue.putInteger(-literalValue.getInteger());
 		return returnValue;
 
-	case kOperandTypeFloat1:
-	case kOperandTypeFloat2:
+	case kOperandTypeTime:
+	case kOperandTypeFloat:
 		returnValue.putDouble(-literalValue.getDouble());
 		return returnValue;
 
diff --git a/engines/mediastation/mediascript/operand.h b/engines/mediastation/mediascript/operand.h
index 3adba4951bc..2059b25dc70 100644
--- a/engines/mediastation/mediascript/operand.h
+++ b/engines/mediastation/mediascript/operand.h
@@ -84,8 +84,8 @@ public:
 	Operand operator-() const;
 
 private:
-	bool isInteger() { return getType() == kOperandTypeLiteral1 || getType() == kOperandTypeLiteral2; };
-	bool isDouble() { return getType() == kOperandTypeFloat1 || getType() == kOperandTypeFloat2; };
+	bool isInteger() { return getType() == kOperandTypeBool || getType() == kOperandTypeInt; };
+	bool isDouble() { return getType() == kOperandTypeFloat || getType() == kOperandTypeTime; };
 	bool isNumber() { return isInteger() || isDouble(); };
 
 	OperandType _type = kOperandTypeEmpty;
diff --git a/engines/mediastation/mediascript/scriptconstants.cpp b/engines/mediastation/mediascript/scriptconstants.cpp
index 4a11bc9d408..70f128a1b52 100644
--- a/engines/mediastation/mediascript/scriptconstants.cpp
+++ b/engines/mediastation/mediascript/scriptconstants.cpp
@@ -318,28 +318,28 @@ const char *operandTypeToStr(OperandType type) {
 	switch (type) {
 	case kOperandTypeEmpty:
 		return "Empty";
-	case kOperandTypeLiteral1:
-		return "Literal1";
-	case kOperandTypeLiteral2:
-		return "Literal2";
-	case kOperandTypeFloat1:
-		return "Float1";
-	case kOperandTypeFloat2:
-		return "Float2";
+	case kOperandTypeBool:
+		return "Bool";
+	case kOperandTypeFloat:
+		return "Float";
+	case kOperandTypeInt:
+		return "Int";
 	case kOperandTypeString:
 		return "String";
 	case kOperandTypeDollarSignVariable:
 		return "DollarSignVariable";
 	case kOperandTypeAssetId:
 		return "AssetId";
-	case kOperandTypeVariableDeclaration:
-		return "VariableDeclaration";
+	case kOperandTypeTime:
+		return "Time";
+	case kOperandTypeVariable:
+		return "Variable";
+	case kOperandTypeFunctionId:
+		return "FunctionId";
+	case kOperandTypeMethodId:
+		return "MethodId";
 	case kOperandTypeCollection:
 		return "Collection";
-	case kOperandTypeFunction:
-		return "Function";
-	case kOperandTypeMethod:
-		return "Method";
 	default:
 		return "UNKNOWN";
 	}
diff --git a/engines/mediastation/mediascript/scriptconstants.h b/engines/mediastation/mediascript/scriptconstants.h
index be33460b6a0..e6653e5cbd8 100644
--- a/engines/mediastation/mediascript/scriptconstants.h
+++ b/engines/mediastation/mediascript/scriptconstants.h
@@ -236,23 +236,17 @@ enum EventHandlerArgumentType {
 const char *eventHandlerArgumentTypeToStr(EventHandlerArgumentType type);
 
 enum OperandType {
-	// This is an invalid type used for initialization only.
 	kOperandTypeEmpty = 0,
-
-	// TODO: Figure out the difference between these two.
-	kOperandTypeLiteral1 = 151,
-	kOperandTypeLiteral2 = 153,
-	// TODO: Figure out the difference between these two.
-	kOperandTypeFloat1 = 152,
-	kOperandTypeFloat2 = 157,
+	kOperandTypeBool = 151,
+	kOperandTypeFloat = 152,
+	kOperandTypeInt = 153,
 	kOperandTypeString = 154,
-	// TODO: This only seems to be used in effectTransition,
-	// as in effectTransition ( $FadeToPalette )
 	kOperandTypeDollarSignVariable = 155,
 	kOperandTypeAssetId = 156,
-	kOperandTypeVariableDeclaration = 158,
-	kOperandTypeFunction = 159,
-	kOperandTypeMethod = 160,
+	kOperandTypeTime = 157,
+	kOperandTypeVariable = 158,
+	kOperandTypeFunctionId = 159,
+	kOperandTypeMethodId = 160,
 	kOperandTypeCollection = 161
 };
 const char *operandTypeToStr(OperandType type);
diff --git a/engines/mediastation/mediascript/variable.cpp b/engines/mediastation/mediascript/variable.cpp
index 8cf8c6c36d0..90aebda8e5b 100644
--- a/engines/mediastation/mediascript/variable.cpp
+++ b/engines/mediastation/mediascript/variable.cpp
@@ -31,7 +31,7 @@ namespace MediaStation {
 Operand Collection::callMethod(BuiltInMethod method, Common::Array<Operand> &args) {
 	switch (method) {
 	case kIsEmptyMethod: {
-		Operand returnValue(kOperandTypeLiteral1);
+		Operand returnValue(kOperandTypeBool);
 		returnValue.putInteger(static_cast<uint>(empty()));
 		return returnValue;
 	}
@@ -63,7 +63,7 @@ Operand Collection::callMethod(BuiltInMethod method, Common::Array<Operand> &arg
 	}
 
 	case kCountMethod: {
-		Operand returnValue = Operand(kOperandTypeLiteral1);
+		Operand returnValue = Operand(kOperandTypeBool);
 		returnValue.putInteger(size());
 		return returnValue;
 	}
@@ -95,7 +95,7 @@ Operand Collection::callMethod(BuiltInMethod method, Common::Array<Operand> &arg
 		}
 
 		// The item wasn't found.
-		Operand returnValue(kOperandTypeLiteral1);
+		Operand returnValue(kOperandTypeBool);
 		returnValue.putInteger(-1);
 		return returnValue;
 	}
@@ -222,7 +222,7 @@ Operand Variable::getValue() {
 	case kVariableTypeBoolean: {
 		// TODO: Is this value type correct?
 		// Shouldn't matter too much, though, since it's still an integer type.
-		Operand returnValue(kOperandTypeLiteral1);
+		Operand returnValue(kOperandTypeBool);
 		returnValue.putInteger(_value.i);
 		return returnValue;
 	}
@@ -230,7 +230,7 @@ Operand Variable::getValue() {
 	case kVariableTypeInt: {
 		// TODO: Is this value type correct?
 		// Shouldn't matter too much, though, since it's still an integer type.
-		Operand returnValue(kOperandTypeLiteral1);
+		Operand returnValue(kOperandTypeBool);
 		returnValue.putInteger(_value.i);
 		return returnValue;
 	}
@@ -238,7 +238,7 @@ Operand Variable::getValue() {
 	case kVariableTypeFloat: {
 		// TODO: Is this value type correct?
 		// Shouldn't matter too much, though, since it's still a floating-point type.
-		Operand returnValue(kOperandTypeFloat1);
+		Operand returnValue(kOperandTypeTime);
 		returnValue.putDouble(_value.d);
 		return returnValue;
 	}
@@ -256,16 +256,16 @@ void Variable::putValue(Operand value) {
 		error("Variable::putValue(): Assigning an empty operand to a variable not supported");
 	}
 
-	case kOperandTypeLiteral1:
-	case kOperandTypeLiteral2:
+	case kOperandTypeBool:
+	case kOperandTypeInt:
 	case kOperandTypeDollarSignVariable: {
 		_type = kVariableTypeInt;
 		_value.i = value.getInteger();
 		break;
 	}
 
-	case kOperandTypeFloat1:
-	case kOperandTypeFloat2: {
+	case kOperandTypeTime:
+	case kOperandTypeFloat: {
 		_type = kVariableTypeFloat;
 		_value.d = value.getDouble();
 		break;
@@ -283,12 +283,12 @@ void Variable::putValue(Operand value) {
 		break;
 	}
 
-	case kOperandTypeVariableDeclaration: {
+	case kOperandTypeVariable: {
 		putValue(value.getLiteralValue());
 		break;
 	}
 
-	case kOperandTypeFunction: {
+	case kOperandTypeFunctionId: {
 		_type = kVariableTypeFunction;
 		_value.functionId = value.getFunctionId();
 		break;
diff --git a/engines/mediastation/mediastation.cpp b/engines/mediastation/mediastation.cpp
index 1a826eb8572..b6116fc9195 100644
--- a/engines/mediastation/mediastation.cpp
+++ b/engines/mediastation/mediastation.cpp
@@ -524,7 +524,7 @@ Operand MediaStationEngine::callBuiltInFunction(BuiltInFunction function, Common
 
 	case kUnk1Function: {
 		warning("MediaStationEngine::callBuiltInFunction(): Function 10 not implemented");
-		Operand returnValue = Operand(kOperandTypeLiteral1);
+		Operand returnValue = Operand(kOperandTypeBool);
 		returnValue.putInteger(1);
 		return returnValue;
 	}


Commit: 4c2e5f242ff4ce5b08727faeb17ce0b9ae1304e9
    https://github.com/scummvm/scummvm/commit/4c2e5f242ff4ce5b08727faeb17ce0b9ae1304e9
Author: Nathanael Gentry (nathanael.gentrydb8 at gmail.com)
Date: 2025-04-11T14:54:23-04:00

Commit Message:
MEDIASTATION: Factor expression evaluation into separate methods

Changed paths:
    engines/mediastation/mediascript/codechunk.cpp
    engines/mediastation/mediascript/codechunk.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 36757c989e3..a82596c006c 100644
--- a/engines/mediastation/mediascript/codechunk.cpp
+++ b/engines/mediastation/mediascript/codechunk.cpp
@@ -39,7 +39,7 @@ Operand CodeChunk::execute(Common::Array<Operand> *args, Common::Array<Operand>
 	_args = args;
 	Operand returnValue;
 	while (_bytecode->pos() < _bytecode->size()) {
-		Operand instructionResult = executeNextStatement();
+		Operand instructionResult = evaluateExpression();
 		if (instructionResult.getType() != kOperandTypeEmpty) {
 			returnValue = instructionResult;
 		}
@@ -60,353 +60,394 @@ Operand CodeChunk::execute(Common::Array<Operand> *args, Common::Array<Operand>
 	return returnValue;
 }
 
-Operand CodeChunk::executeNextStatement() {
+Operand CodeChunk::evaluateExpression() {
 	if (_bytecode->eos()) {
-		error("CodeChunk::executeNextStatement(): Attempt to read past end of bytecode chunk");
+		error("CodeChunk::evaluateExpression(): Attempt to read past end of bytecode chunk");
 	}
 
-	InstructionType instructionType = static_cast<InstructionType>(Datum(*_bytecode).u.i);
-	debugCN(5, kDebugScript, "(%s) ", instructionTypeToStr(instructionType));
+	ExpressionType instructionType = static_cast<ExpressionType>(Datum(*_bytecode).u.i);
+	debugCN(5, kDebugScript, "(%s) ", expressionTypeToStr(instructionType));
+
+	Operand returnValue;
 	switch (instructionType) {
-	case kInstructionTypeEmpty: {
+	case kExpressionTypeEmpty: {
 		return Operand();
 	}
 
-	case kInstructionTypeFunctionCall: {
-		Opcode opcode = static_cast<Opcode>(Datum(*_bytecode).u.i);
-		debugCN(5, kDebugScript, "%s ", opcodeToStr(opcode));
-		switch (opcode) {
-		case kOpcodeAssignVariable: {
-			uint32 id = Datum(*_bytecode).u.i;
-			VariableScope scope = static_cast<VariableScope>(Datum(*_bytecode).u.i);
-			debugC(5, kDebugScript, "%d (%s) ", id, variableScopeToStr(scope));
-			debugCN(5, kDebugScript, "  Value: ");
-			Operand newValue = executeNextStatement();
+	case kExpressionTypeOperation:
+		returnValue = evaluateOperation();
+		break;
 
-			putVariable(id, scope, newValue);
-			return Operand();
-		}
+	case kExpressionTypeValue:
+		returnValue = evaluateValue();
+		break;
 
-		case kOpcodeCallFunction: {
-			uint functionId = Datum(*_bytecode).u.i;
-			uint32 parameterCount = Datum(*_bytecode).u.i;
-			debugC(5, kDebugScript, "%d (%d params)", functionId, parameterCount);
-			return callFunction(functionId, parameterCount);
-		}
+	case kExpressionTypeVariable:
+		returnValue = evaluateVariable();
+		break;
 
-		case kOpcodeCallMethod: {
-			// In Media Station, all methods seem be built-in - there don't
-			// seem to be custom objects or methods individual titles can
-			// define. Functions, however, CAN be title-defined.
-			// But here, we're only looking for built-in methods.
-			BuiltInMethod methodId = static_cast<BuiltInMethod>(Datum(*_bytecode).u.i);
-			uint32 parameterCount = Datum(*_bytecode).u.i;
-			debugC(5, kDebugScript, "%s (%d params)", builtInMethodToStr(methodId), parameterCount);
-			debugCN(5, kDebugScript, "  Self: ");
-			Operand selfObject = executeNextStatement();
-			Common::Array<Operand> args;
-			for (uint i = 0; i < parameterCount; i++) {
-				debugCN(5, kDebugScript, "  Param %d: ", i);
-				Operand arg = executeNextStatement();
-				args.push_back(arg);
-			}
-			Operand returnValue = callBuiltInMethod(methodId, selfObject, args);
-			return returnValue;
-		}
+	default:
+		error("CodeChunk::getNextStatement(): Got unimplemented instruction type %s (%d)", expressionTypeToStr(instructionType), static_cast<uint>(instructionType));
+	}
 
-		case kOpcodeDeclareVariables: {
-			uint32 localVariableCount = Datum(*_bytecode).u.i;
-			debugC(5, kDebugScript, "%d", localVariableCount);
-			assert(_locals == nullptr);
-			_locals = new Common::Array<Operand>(localVariableCount);
-			_weOwnLocals = true;
-			return Operand();
-		}
+	return returnValue;
+}
 
-		case kOpcodeOr: {
-			debugCN(5, kDebugScript, "\n    lhs: ");
-			Operand value1 = executeNextStatement();
-			debugCN(5, kDebugScript, "    rhs: ");
-			Operand value2 = executeNextStatement();
+Operand CodeChunk::evaluateOperation() {
+	Opcode opcode = static_cast<Opcode>(Datum(*_bytecode).u.i);
+	debugCN(5, kDebugScript, "%s ", opcodeToStr(opcode));
+	switch (opcode) {
+	case kOpcodeAssignVariable: {
+		uint32 id = Datum(*_bytecode).u.i;
+		VariableScope scope = static_cast<VariableScope>(Datum(*_bytecode).u.i);
+		debugC(5, kDebugScript, "%d (%s) ", id, variableScopeToStr(scope));
+		debugCN(5, kDebugScript, "  Value: ");
+		Operand newValue = evaluateExpression();
 
-			Operand returnValue(kOperandTypeBool);
-			bool logicalOr = (value1 || value2);
-			returnValue.putInteger(static_cast<uint>(logicalOr));
-			return returnValue;
-		}
+		putVariable(id, scope, newValue);
+		return Operand();
+	}
 
-		case kOpcodeNot: {
-			debugCN(5, kDebugScript, "\n    value: ");
-			Operand value = executeNextStatement();
+	case kOpcodeCallFunction: {
+		uint functionId = Datum(*_bytecode).u.i;
+		uint32 parameterCount = Datum(*_bytecode).u.i;
+		debugC(5, kDebugScript, "%d (%d params)", functionId, parameterCount);
+		return callFunction(functionId, parameterCount);
+	}
 
-			Operand returnValue(kOperandTypeBool);
-			bool logicalNot = !(static_cast<bool>(value.getInteger()));
-			returnValue.putInteger(static_cast<uint>(logicalNot));
-			return returnValue;
-		}
+	case kOpcodeCallMethod: {
+		// In Media Station, all methods seem be built-in - there don't
+		// seem to be custom objects or methods individual titles can
+		// define. Functions, however, CAN be title-defined.
+		// But here, we're only looking for built-in methods.
+		BuiltInMethod methodId = static_cast<BuiltInMethod>(Datum(*_bytecode).u.i);
+		uint32 parameterCount = Datum(*_bytecode).u.i;
+		debugC(5, kDebugScript, "%s (%d params)", builtInMethodToStr(methodId), parameterCount);
+		debugCN(5, kDebugScript, "  Self: ");
+		Operand selfObject = evaluateExpression();
+		Common::Array<Operand> args;
+		for (uint i = 0; i < parameterCount; i++) {
+			debugCN(5, kDebugScript, "  Param %d: ", i);
+			Operand arg = evaluateExpression();
+			args.push_back(arg);
+		}
+		Operand returnValue = callBuiltInMethod(methodId, selfObject, args);
+		return returnValue;
+	}
 
-		case kOpcodeAnd: {
-			debugCN(5, kDebugScript, "\n    value: ");
-			Operand value1 = executeNextStatement();
-			debugCN(5, kDebugScript, "    rhs: ");
-			Operand value2 = executeNextStatement();
+	case kOpcodeDeclareVariables: {
+		uint32 localVariableCount = Datum(*_bytecode).u.i;
+		debugC(5, kDebugScript, "%d", localVariableCount);
+		assert(_locals == nullptr);
+		_locals = new Common::Array<Operand>(localVariableCount);
+		_weOwnLocals = true;
+		return Operand();
+	}
 
-			Operand returnValue(kOperandTypeBool);
-			bool logicalAnd = (value1 && value2);
-			returnValue.putInteger(static_cast<uint>(logicalAnd));
-			return returnValue;
-		}
+	case kOpcodeOr: {
+		debugCN(5, kDebugScript, "\n    lhs: ");
+		Operand value1 = evaluateExpression();
+		debugCN(5, kDebugScript, "    rhs: ");
+		Operand value2 = evaluateExpression();
 
-		case kOpcodeIfElse: {
-			debugCN(5, kDebugScript, "\n    condition: ");
-			Operand condition = executeNextStatement();
-
-			CodeChunk ifBlock(*_bytecode);
-			CodeChunk elseBlock(*_bytecode);
-			// Doesn't seem like there is a real bool type for values,
-			// ao just get an integer.
-			if (condition.getInteger()) {
-				ifBlock.execute(_args, _locals);
-			} else {
-				elseBlock.execute(_args, _locals);
-			}
+		Operand returnValue(kOperandTypeBool);
+		bool logicalOr = (value1 || value2);
+		returnValue.putInteger(static_cast<uint>(logicalOr));
+		return returnValue;
+	}
 
-			// If blocks themselves shouldn't return anything.
-			return Operand();
-		}
+	case kOpcodeNot: {
+		debugCN(5, kDebugScript, "\n    value: ");
+		Operand value = evaluateExpression();
 
-		case kOpcodeEquals: {
-			debugCN(5, kDebugScript, "\n    lhs: ");
-			Operand value1 = executeNextStatement();
-			debugCN(5, kDebugScript, "    rhs: ");
-			Operand value2 = executeNextStatement();
+		Operand returnValue(kOperandTypeBool);
+		bool logicalNot = !(static_cast<bool>(value.getInteger()));
+		returnValue.putInteger(static_cast<uint>(logicalNot));
+		return returnValue;
+	}
 
-			Operand returnValue(kOperandTypeBool);
-			bool equal = (value1 == value2);
-			returnValue.putInteger(static_cast<uint>(equal));
-			return returnValue;
-		}
+	case kOpcodeAnd: {
+		debugCN(5, kDebugScript, "\n    value: ");
+		Operand value1 = evaluateExpression();
+		debugCN(5, kDebugScript, "    rhs: ");
+		Operand value2 = evaluateExpression();
 
-		case kOpcodeNotEquals: {
-			debugCN(5, kDebugScript, "\n    lhs: ");
-			Operand value1 = executeNextStatement();
-			debugCN(5, kDebugScript, "    rhs: ");
-			Operand value2 = executeNextStatement();
+		Operand returnValue(kOperandTypeBool);
+		bool logicalAnd = (value1 && value2);
+		returnValue.putInteger(static_cast<uint>(logicalAnd));
+		return returnValue;
+	}
 
-			Operand returnValue(kOperandTypeBool);
-			bool notEqual = !(value1 == value2);
-			returnValue.putInteger(static_cast<uint>(notEqual));
-			return returnValue;
+	case kOpcodeIfElse: {
+		debugCN(5, kDebugScript, "\n    condition: ");
+		Operand condition = evaluateExpression();
+
+		CodeChunk ifBlock(*_bytecode);
+		CodeChunk elseBlock(*_bytecode);
+		// Doesn't seem like there is a real bool type for values,
+		// ao just get an integer.
+		if (condition.getInteger()) {
+			ifBlock.execute(_args, _locals);
+		} else {
+			elseBlock.execute(_args, _locals);
 		}
 
-		case kOpcodeLessThan: {
-			debugCN(5, kDebugScript, "\n    lhs: ");
-			Operand value1 = executeNextStatement();
-			debugCN(5, kDebugScript, "    rhs: ");
-			Operand value2 = executeNextStatement();
+		// If blocks themselves shouldn't return anything.
+		return Operand();
+	}
 
-			Operand returnValue(kOperandTypeBool);
-			bool lessThan = (value1 < value2);
-			returnValue.putInteger(static_cast<uint>(lessThan));
-			return returnValue;
-		}
+	case kOpcodeEquals: {
+		debugCN(5, kDebugScript, "\n    lhs: ");
+		Operand value1 = evaluateExpression();
+		debugCN(5, kDebugScript, "    rhs: ");
+		Operand value2 = evaluateExpression();
 
-		case kOpcodeGreaterThan: {
-			debugCN(5, kDebugScript, "\n    lhs: ");
-			Operand value1 = executeNextStatement();
-			debugCN(5, kDebugScript, "    rhs: ");
-			Operand value2 = executeNextStatement();
+		Operand returnValue(kOperandTypeBool);
+		bool equal = (value1 == value2);
+		returnValue.putInteger(static_cast<uint>(equal));
+		return returnValue;
+	}
 
-			Operand returnValue(kOperandTypeBool);
-			bool greaterThan = (value1 > value2);
-			returnValue.putInteger(static_cast<uint>(greaterThan));
-			return returnValue;
-		}
+	case kOpcodeNotEquals: {
+		debugCN(5, kDebugScript, "\n    lhs: ");
+		Operand value1 = evaluateExpression();
+		debugCN(5, kDebugScript, "    rhs: ");
+		Operand value2 = evaluateExpression();
 
-		case kOpcodeLessThanOrEqualTo: {
-			debugCN(5, kDebugScript, "\n    lhs: ");
-			Operand value1 = executeNextStatement();
-			debugCN(5, kDebugScript, "    rhs: ");
-			Operand value2 = executeNextStatement();
+		Operand returnValue(kOperandTypeBool);
+		bool notEqual = !(value1 == value2);
+		returnValue.putInteger(static_cast<uint>(notEqual));
+		return returnValue;
+	}
 
-			Operand returnValue(kOperandTypeBool);
-			bool lessThanOrEqualTo = (value1 < value2) || (value1 == value2);
-			returnValue.putInteger(static_cast<uint>(lessThanOrEqualTo));
-			return returnValue;
-		}
+	case kOpcodeLessThan: {
+		debugCN(5, kDebugScript, "\n    lhs: ");
+		Operand value1 = evaluateExpression();
+		debugCN(5, kDebugScript, "    rhs: ");
+		Operand value2 = evaluateExpression();
 
-		case kOpcodeGreaterThanOrEqualTo: {
-			debugCN(5, kDebugScript, "\n    lhs: ");
-			Operand value1 = executeNextStatement();
-			debugCN(5, kDebugScript, "    rhs: ");
-			Operand value2 = executeNextStatement();
+		Operand returnValue(kOperandTypeBool);
+		bool lessThan = (value1 < value2);
+		returnValue.putInteger(static_cast<uint>(lessThan));
+		return returnValue;
+	}
 
-			Operand returnValue(kOperandTypeBool);
-			bool greaterThanOrEqualTo = (value1 > value2) || (value1 == value2);
-			returnValue.putInteger(static_cast<uint>(greaterThanOrEqualTo));
-			return returnValue;
-		}
+	case kOpcodeGreaterThan: {
+		debugCN(5, kDebugScript, "\n    lhs: ");
+		Operand value1 = evaluateExpression();
+		debugCN(5, kDebugScript, "    rhs: ");
+		Operand value2 = evaluateExpression();
+
+		Operand returnValue(kOperandTypeBool);
+		bool greaterThan = (value1 > value2);
+		returnValue.putInteger(static_cast<uint>(greaterThan));
+		return returnValue;
+	}
 
-		case kOpcodeAdd: {
-			debugCN(5, kDebugScript, "\n    lhs: ");
-			Operand value1 = executeNextStatement();
-			debugCN(5, kDebugScript, "    rhs: ");
-			Operand value2 = executeNextStatement();
+	case kOpcodeLessThanOrEqualTo: {
+		debugCN(5, kDebugScript, "\n    lhs: ");
+		Operand value1 = evaluateExpression();
+		debugCN(5, kDebugScript, "    rhs: ");
+		Operand value2 = evaluateExpression();
 
-			Operand returnValue = value1 + value2;
-			return returnValue;
-		}
+		Operand returnValue(kOperandTypeBool);
+		bool lessThanOrEqualTo = (value1 < value2) || (value1 == value2);
+		returnValue.putInteger(static_cast<uint>(lessThanOrEqualTo));
+		return returnValue;
+	}
 
-		case kOpcodeSubtract: {
-			debugCN(5, kDebugScript, "\n    lhs: ");
-			Operand value1 = executeNextStatement();
-			debugCN(5, kDebugScript, "    rhs: ");
-			Operand value2 = executeNextStatement();
+	case kOpcodeGreaterThanOrEqualTo: {
+		debugCN(5, kDebugScript, "\n    lhs: ");
+		Operand value1 = evaluateExpression();
+		debugCN(5, kDebugScript, "    rhs: ");
+		Operand value2 = evaluateExpression();
 
-			Operand returnValue = value1 - value2;
-			return returnValue;
-		}
+		Operand returnValue(kOperandTypeBool);
+		bool greaterThanOrEqualTo = (value1 > value2) || (value1 == value2);
+		returnValue.putInteger(static_cast<uint>(greaterThanOrEqualTo));
+		return returnValue;
+	}
 
-		case kOpcodeMultiply: {
-			debugCN(5, kDebugScript, "\n    lhs: ");
-			Operand value1 = executeNextStatement();
-			debugCN(5, kDebugScript, "    rhs: ");
-			Operand value2 = executeNextStatement();
+	case kOpcodeAdd: {
+		debugCN(5, kDebugScript, "\n    lhs: ");
+		Operand value1 = evaluateExpression();
+		debugCN(5, kDebugScript, "    rhs: ");
+		Operand value2 = evaluateExpression();
 
-			Operand returnValue = value1 * value2;
-			return returnValue;
-		}
+		Operand returnValue = value1 + value2;
+		return returnValue;
+	}
 
-		case kOpcodeDivide: {
-			debugCN(5, kDebugScript, "\n    lhs: ");
-			Operand value1 = executeNextStatement();
-			debugCN(5, kDebugScript, "    rhs: ");
-			Operand value2 = executeNextStatement();
+	case kOpcodeSubtract: {
+		debugCN(5, kDebugScript, "\n    lhs: ");
+		Operand value1 = evaluateExpression();
+		debugCN(5, kDebugScript, "    rhs: ");
+		Operand value2 = evaluateExpression();
 
-			Operand returnValue = value1 / value2;
-			return returnValue;
-		}
+		Operand returnValue = value1 - value2;
+		return returnValue;
+	}
 
-		case kOpcodeModulo: {
-			debugCN(5, kDebugScript, "\n    lhs: ");
-			Operand value1 = executeNextStatement();
-			debugCN(5, kDebugScript, "    rhs: ");
-			Operand value2 = executeNextStatement();
+	case kOpcodeMultiply: {
+		debugCN(5, kDebugScript, "\n    lhs: ");
+		Operand value1 = evaluateExpression();
+		debugCN(5, kDebugScript, "    rhs: ");
+		Operand value2 = evaluateExpression();
 
-			Operand returnValue = value1 % value2;
-			return returnValue;
-		}
+		Operand returnValue = value1 * value2;
+		return returnValue;
+	}
 
-		case kOpcodeNegate: {
-			Operand value = executeNextStatement();
-			debugCN(5, kDebugScript, "    value: ");
+	case kOpcodeDivide: {
+		debugCN(5, kDebugScript, "\n    lhs: ");
+		Operand value1 = evaluateExpression();
+		debugCN(5, kDebugScript, "    rhs: ");
+		Operand value2 = evaluateExpression();
 
-			return -value;
-		}
+		Operand returnValue = value1 / value2;
+		return returnValue;
+	}
 
-		case kOpcodeReturn: {
-			debugCN(5, kDebugScript, "    return: ");
-			Operand value = executeNextStatement();
+	case kOpcodeModulo: {
+		debugCN(5, kDebugScript, "\n    lhs: ");
+		Operand value1 = evaluateExpression();
+		debugCN(5, kDebugScript, "    rhs: ");
+		Operand value2 = evaluateExpression();
 
-			return value;
-		}
+		Operand returnValue = value1 % value2;
+		return returnValue;
+	}
 
-		case kOpcodeCallFunctionInVariable: {
-			uint parameterCount = Datum(*_bytecode).u.i;
-			Operand variable = executeNextStatement();
-			uint functionId = variable.getFunctionId();
-			debugC(5, kDebugScript, "Variable %d [function %d] (%d params)", variable.getVariable()->_id, functionId, parameterCount);
+	case kOpcodeNegate: {
+		Operand value = evaluateExpression();
+		debugCN(5, kDebugScript, "    value: ");
 
-			return callFunction(functionId, parameterCount);
-		}
+		return -value;
+	}
 
-		default:
-			error("CodeChunk::getNextStatement(): Got unimplemented opcode %s (%d)", opcodeToStr(opcode), static_cast<uint>(opcode));
-		}
-		break;
+	case kOpcodeReturn: {
+		debugCN(5, kDebugScript, "    return: ");
+		Operand value = evaluateExpression();
+
+		return value;
 	}
 
-	case kInstructionTypeOperand: {
-		OperandType operandType = static_cast<OperandType>(Datum(*_bytecode).u.i);
-		debugCN(5, kDebugScript, "%s ", operandTypeToStr(operandType));
-		Operand operand(operandType);
-		switch (operandType) {
-		case kOperandTypeAssetId: {
-			uint32 assetId = Datum(*_bytecode).u.i;
-			debugC(5, kDebugScript, "%d ", assetId);
-			operand.putAsset(assetId);
-			return operand;
-		}
+	case kOpcodeCallFunctionInVariable: {
+		uint parameterCount = Datum(*_bytecode).u.i;
+		Operand variable = evaluateExpression();
+		uint functionId = variable.getFunctionId();
+		debugC(5, kDebugScript, "Variable %d [function %d] (%d params)", variable.getVariable()->_id, functionId, parameterCount);
 
-		case kOperandTypeBool:
-		case kOperandTypeInt:
-		case kOperandTypeDollarSignVariable: {
-			int literal = Datum(*_bytecode).u.i;
-			debugC(5, kDebugScript, "%d ", literal);
-			operand.putInteger(literal);
-			return operand;
-		}
+		return callFunction(functionId, parameterCount);
+	}
 
-		case kOperandTypeFloat:
-		case kOperandTypeTime: {
-			double d = Datum(*_bytecode).u.f;
-			debugC(5, kDebugScript, "%f ", d);
-			operand.putDouble(d);
-			return operand;
-		}
+	default:
+		error("Got unimplemented opcode %s (%d)", opcodeToStr(opcode), static_cast<uint>(opcode));
+	}
+}
 
-		case kOperandTypeMethodId: {
-			BuiltInMethod methodId = static_cast<BuiltInMethod>(Datum(*_bytecode).u.i);
-			debugC(5, kDebugScript, "%s ", builtInMethodToStr(methodId));
-			operand.putMethodId(methodId);
-			return operand;
-		}
+Operand CodeChunk::evaluateValue() {
+	OperandType operandType = static_cast<OperandType>(Datum(*_bytecode).u.i);
+	debugCN(5, kDebugScript, "%s ", operandTypeToStr(operandType));
 
-		case kOperandTypeFunctionId: {
-			uint functionId = Datum(*_bytecode).u.i;
-			debugC(5, kDebugScript, "%d ", functionId);
-			operand.putFunctionId(functionId);
-			return operand;
+	Operand operand(operandType);
+	switch (operandType) {
+	case kOperandTypeBool: {
+		int b = Datum(*_bytecode).u.i;
+		if (b != 0 && b != 1) {
+			error("Got invalid boolean value %d", b);
 		}
+		debugC(5, kDebugScript, "%d ", b);
+		operand.putInteger(b == 1 ? true : false);
+		return operand;
+	}
 
-		case kOperandTypeString: {
-			// This is indeed a raw string, not a string wrapped in a datum!
-			// TODO: This copies the string. Can we read it directly from the chunk?
-			int size = Datum(*_bytecode, kDatumTypeUint16_1).u.i;
-			char *buffer = new char[size + 1];
-			_bytecode->read(buffer, size);
-			buffer[size] = '\0';
-			Common::String *string = new Common::String(buffer);
-			debugC(5, kDebugScript, "%s ", string->c_str());
-			operand.putString(string);
-			delete[] buffer;
-			return operand;
-		}
+	case kOperandTypeFloat: {
+		double f = Datum(*_bytecode).u.f;
+		debugC(5, kDebugScript, "%f ", f);
+		operand.putDouble(f);
+		return operand;
+	}
 
-		default:
-			error("CodeChunk::getNextStatement(): Got unimplemented operand type %s (%d)", operandTypeToStr(operandType), static_cast<uint>(operandType));
-		}
-		break;
+	case kOperandTypeInt: {
+		int i = Datum(*_bytecode).u.i;
+		debugC(5, kDebugScript, "%d ", i);
+		operand.putInteger(i);
+		return operand;
 	}
 
-	case kInstructionTypeVariableRef: {
-		uint32 id = Datum(*_bytecode).u.i;
-		VariableScope scope = static_cast<VariableScope>(Datum(*_bytecode).u.i);
-		debugC(5, kDebugScript, "Variable %d (%s)", id, variableScopeToStr(scope));
-		Operand variable = getVariable(id, scope);
-		return variable;
+	case kOperandTypeString: {
+		// This is indeed a raw string, not a string wrapped in a datum!
+		// TODO: This copies the string. Can we read it directly from the chunk?
+		int size = Datum(*_bytecode, kDatumTypeUint16_1).u.i;
+		char *buffer = new char[size + 1];
+		_bytecode->read(buffer, size);
+		buffer[size] = '\0';
+		Common::String *string = new Common::String(buffer);
+		debugC(5, kDebugScript, "%s ", string->c_str());
+		operand.putString(string);
+		delete[] buffer;
+		return operand;
+	}
+
+	case kOperandTypeDollarSignVariable: {
+		uint literal = Datum(*_bytecode).u.i;
+		debugC(5, kDebugScript, "%d ", literal);
+		operand.putInteger(literal);
+		return operand;
+	}
+
+	case kOperandTypeAssetId: {
+		uint assetId = Datum(*_bytecode).u.i;
+		debugC(5, kDebugScript, "%d ", assetId);
+		operand.putAsset(assetId);
+		return operand;
+	}
+
+	case kOperandTypeTime: {
+		double d = Datum(*_bytecode).u.f;
+		debugC(5, kDebugScript, "%f ", d);
+		operand.putDouble(d);
+		return operand;
+	}
+
+	case kOperandTypeVariable: {
+		// TODO: Implement this as we go through the re-architecting.
+		error("kOperandTypeVariable not implemented yet");
+	}
+
+	case kOperandTypeFunctionId: {
+		uint functionId = Datum(*_bytecode).u.i;
+		debugC(5, kDebugScript, "%d ", functionId);
+		operand.putFunctionId(functionId);
+		return operand;
+	}
+
+	case kOperandTypeMethodId: {
+		BuiltInMethod methodId = static_cast<BuiltInMethod>(Datum(*_bytecode).u.i);
+		debugC(5, kDebugScript, "%s ", builtInMethodToStr(methodId));
+		operand.putMethodId(methodId);
+		return operand;
 	}
 
 	default:
-		error("CodeChunk::getNextStatement(): Got unimplemented instruction type %s (%d)", instructionTypeToStr(instructionType), static_cast<uint>(instructionType));
+		error("CodeChunk::getNextStatement(): Got unknown operand type %s (%d)", operandTypeToStr(operandType), static_cast<uint>(operandType));
 	}
 }
 
+Operand CodeChunk::evaluateVariable() {
+	uint32 id = Datum(*_bytecode).u.i;
+	VariableScope scope = static_cast<VariableScope>(Datum(*_bytecode).u.i);
+	debugC(5, kDebugScript, "Variable %d (%s)", id, variableScopeToStr(scope));
+	Operand variable = getVariable(id, scope);
+	return variable;
+}
+
 Operand CodeChunk::callFunction(uint functionId, uint parameterCount) {
 	Common::Array<Operand> args;
 	for (uint i = 0; i < parameterCount; i++) {
 		debugCN(5, kDebugScript, "  Param %d: ", i);
-		Operand arg = executeNextStatement();
+		Operand arg = evaluateExpression();
 		args.push_back(arg);
 	}
 
diff --git a/engines/mediastation/mediascript/codechunk.h b/engines/mediastation/mediascript/codechunk.h
index c2cd09f000e..567bbc8e0e8 100644
--- a/engines/mediastation/mediascript/codechunk.h
+++ b/engines/mediastation/mediascript/codechunk.h
@@ -42,7 +42,11 @@ public:
 	static Operand callBuiltInMethod(BuiltInMethod method, Operand &self, Common::Array<Operand> &args);
 
 private:
-	Operand executeNextStatement();
+	Operand evaluateExpression();
+	Operand evaluateOperation();
+	Operand evaluateValue();
+	Operand evaluateVariable();
+
 	Operand callFunction(uint functionId, uint parameterCount);
 	Operand getVariable(uint32 id, VariableScope scope);
 	void putVariable(uint32 id, VariableScope scope, Operand &value);
diff --git a/engines/mediastation/mediascript/scriptconstants.cpp b/engines/mediastation/mediascript/scriptconstants.cpp
index 70f128a1b52..faaa4e3690a 100644
--- a/engines/mediastation/mediascript/scriptconstants.cpp
+++ b/engines/mediastation/mediascript/scriptconstants.cpp
@@ -23,16 +23,16 @@
 
 namespace MediaStation {
 
-const char *instructionTypeToStr(InstructionType type) {
+const char *expressionTypeToStr(ExpressionType type) {
 	switch (type) {
-	case kInstructionTypeEmpty:
+	case kExpressionTypeEmpty:
 		return "Empty";
-	case kInstructionTypeFunctionCall:
-		return "FunctionCall";
-	case kInstructionTypeOperand:
-		return "Operand";
-	case kInstructionTypeVariableRef:
-		return "VariableReference";
+	case kExpressionTypeVariable:
+		return "Variable";
+	case kExpressionTypeValue:
+		return "Value";
+	case kExpressionTypeOperation:
+		return "Operation";
 	default:
 		return "UNKNOWN";
 	}
diff --git a/engines/mediastation/mediascript/scriptconstants.h b/engines/mediastation/mediascript/scriptconstants.h
index e6653e5cbd8..6867ff28ed1 100644
--- a/engines/mediastation/mediascript/scriptconstants.h
+++ b/engines/mediastation/mediascript/scriptconstants.h
@@ -24,13 +24,13 @@
 
 namespace MediaStation {
 
-enum InstructionType {
-	kInstructionTypeEmpty = 0x0000,
-	kInstructionTypeFunctionCall = 0x0067,
-	kInstructionTypeOperand = 0x0066,
-	kInstructionTypeVariableRef = 0x0065
+enum ExpressionType {
+	kExpressionTypeEmpty = 0x0000,
+	kExpressionTypeVariable = 0x0065,
+	kExpressionTypeValue = 0x0066,
+	kExpressionTypeOperation = 0x0067,
 };
-const char *instructionTypeToStr(InstructionType type);
+const char *expressionTypeToStr(ExpressionType type);
 
 enum Opcode {
 	kOpcodeIfElse = 202,
@@ -68,6 +68,7 @@ const char *opcodeToStr(Opcode opcode);
 enum VariableScope {
 	kVariableScopeLocal = 1,
 	kVariableScopeParameter = 2,
+	kVariableScopeIndirectParameter = 3,
 	kVariableScopeGlobal = 4
 };
 const char *variableScopeToStr(VariableScope scope);
@@ -252,9 +253,7 @@ enum OperandType {
 const char *operandTypeToStr(OperandType type);
 
 enum VariableType {
-	// This is an invalid type used for initialization only.
 	kVariableTypeEmpty = 0x0000,
-
 	kVariableTypeFunction = 0x0008,
 	kVariableTypeCollection = 0x0007,
 	kVariableTypeString = 0x0006,


Commit: ce179e04952e0367d35e808f627dea27d5580e9f
    https://github.com/scummvm/scummvm/commit/ce179e04952e0367d35e808f627dea27d5580e9f
Author: Nathanael Gentry (nathanael.gentrydb8 at gmail.com)
Date: 2025-04-11T14:54:23-04:00

Commit Message:
MEDIASTATION: Rename Operand to ScriptValue

Changed paths:
  A engines/mediastation/mediascript/scriptvalue.cpp
  A engines/mediastation/mediascript/scriptvalue.h
  R engines/mediastation/mediascript/operand.cpp
  R engines/mediastation/mediascript/operand.h
    engines/mediastation/asset.h
    engines/mediastation/assets/canvas.cpp
    engines/mediastation/assets/canvas.h
    engines/mediastation/assets/font.cpp
    engines/mediastation/assets/font.h
    engines/mediastation/assets/hotspot.cpp
    engines/mediastation/assets/hotspot.h
    engines/mediastation/assets/image.cpp
    engines/mediastation/assets/image.h
    engines/mediastation/assets/movie.cpp
    engines/mediastation/assets/movie.h
    engines/mediastation/assets/palette.cpp
    engines/mediastation/assets/palette.h
    engines/mediastation/assets/path.cpp
    engines/mediastation/assets/path.h
    engines/mediastation/assets/screen.cpp
    engines/mediastation/assets/screen.h
    engines/mediastation/assets/sound.cpp
    engines/mediastation/assets/sound.h
    engines/mediastation/assets/sprite.cpp
    engines/mediastation/assets/sprite.h
    engines/mediastation/assets/stage.h
    engines/mediastation/assets/text.cpp
    engines/mediastation/assets/text.h
    engines/mediastation/assets/timer.cpp
    engines/mediastation/assets/timer.h
    engines/mediastation/context.cpp
    engines/mediastation/mediascript/codechunk.cpp
    engines/mediastation/mediascript/codechunk.h
    engines/mediastation/mediascript/eventhandler.cpp
    engines/mediastation/mediascript/eventhandler.h
    engines/mediastation/mediascript/function.cpp
    engines/mediastation/mediascript/function.h
    engines/mediastation/mediascript/scriptconstants.cpp
    engines/mediastation/mediascript/scriptconstants.h
    engines/mediastation/mediascript/variable.cpp
    engines/mediastation/mediascript/variable.h
    engines/mediastation/mediastation.cpp
    engines/mediastation/mediastation.h
    engines/mediastation/module.mk
    engines/mediastation/transitions.cpp


diff --git a/engines/mediastation/asset.h b/engines/mediastation/asset.h
index 81380b3031a..1aa756333ec 100644
--- a/engines/mediastation/asset.h
+++ b/engines/mediastation/asset.h
@@ -27,7 +27,7 @@
 #include "mediastation/mediastation.h"
 #include "mediastation/datafile.h"
 #include "mediastation/mediascript/scriptconstants.h"
-#include "mediastation/mediascript/operand.h"
+#include "mediastation/mediascript/scriptvalue.h"
 #include "mediastation/assetheader.h"
 
 namespace MediaStation {
@@ -50,7 +50,7 @@ public:
 	}
 
 	// Runs built-in bytecode methods.
-	virtual Operand callMethod(BuiltInMethod methodId, Common::Array<Operand> &args) = 0;
+	virtual ScriptValue callMethod(BuiltInMethod methodId, Common::Array<ScriptValue> &args) = 0;
 	// Called to have the asset do any processing, like drawing new frames,
 	// handling time-based event handlers, and such. Some assets don't have any
 	// processing to do.
diff --git a/engines/mediastation/assets/canvas.cpp b/engines/mediastation/assets/canvas.cpp
index 1856be6f665..6579842dbac 100644
--- a/engines/mediastation/assets/canvas.cpp
+++ b/engines/mediastation/assets/canvas.cpp
@@ -23,7 +23,7 @@
 
 namespace MediaStation {
 
-Operand Canvas::callMethod(BuiltInMethod methodId, Common::Array<Operand> &args) {
+ScriptValue Canvas::callMethod(BuiltInMethod methodId, Common::Array<ScriptValue> &args) {
 	switch (methodId) {
 	case kClearToPaletteMethod: {
 		error("Canvas::callMethod(): BuiltInFunction::clearToPalette is not implemented yet");
diff --git a/engines/mediastation/assets/canvas.h b/engines/mediastation/assets/canvas.h
index 17f9e3e65bb..ca2214ddf82 100644
--- a/engines/mediastation/assets/canvas.h
+++ b/engines/mediastation/assets/canvas.h
@@ -24,7 +24,7 @@
 
 #include "mediastation/asset.h"
 #include "mediastation/assetheader.h"
-#include "mediastation/mediascript/operand.h"
+#include "mediastation/mediascript/scriptvalue.h"
 #include "mediastation/mediascript/scriptconstants.h"
 
 namespace MediaStation {
@@ -33,7 +33,7 @@ class Canvas : public Asset {
 public:
 	Canvas(AssetHeader *header) : Asset(header) {};
 
-	virtual Operand callMethod(BuiltInMethod methodId, Common::Array<Operand> &args) override;
+	virtual ScriptValue callMethod(BuiltInMethod methodId, Common::Array<ScriptValue> &args) override;
 };
 
 } // End of namespace MediaStation
diff --git a/engines/mediastation/assets/font.cpp b/engines/mediastation/assets/font.cpp
index e14caf6f8aa..a9f71e1c0be 100644
--- a/engines/mediastation/assets/font.cpp
+++ b/engines/mediastation/assets/font.cpp
@@ -37,7 +37,7 @@ Font::~Font() {
 	_glyphs.clear();
 }
 
-Operand Font::callMethod(BuiltInMethod methodId, Common::Array<Operand> &args) {
+ScriptValue Font::callMethod(BuiltInMethod methodId, Common::Array<ScriptValue> &args) {
 	error("Font::callMethod(): Font does not have any callable methods");
 }
 
diff --git a/engines/mediastation/assets/font.h b/engines/mediastation/assets/font.h
index be258dd1ac9..3ecab34e8b6 100644
--- a/engines/mediastation/assets/font.h
+++ b/engines/mediastation/assets/font.h
@@ -26,7 +26,7 @@
 #include "mediastation/assetheader.h"
 #include "mediastation/bitmap.h"
 #include "mediastation/datafile.h"
-#include "mediastation/mediascript/operand.h"
+#include "mediastation/mediascript/scriptvalue.h"
 #include "mediastation/mediascript/scriptconstants.h"
 
 namespace MediaStation {
@@ -46,7 +46,7 @@ public:
 	Font(AssetHeader *header) : Asset(header) {};
 	~Font();
 
-	virtual Operand callMethod(BuiltInMethod methodId, Common::Array<Operand> &args) override;
+	virtual ScriptValue callMethod(BuiltInMethod methodId, Common::Array<ScriptValue> &args) override;
 
 	virtual void readChunk(Chunk &chunk) override;
 
diff --git a/engines/mediastation/assets/hotspot.cpp b/engines/mediastation/assets/hotspot.cpp
index 21d005bf6fa..058eb2a95ff 100644
--- a/engines/mediastation/assets/hotspot.cpp
+++ b/engines/mediastation/assets/hotspot.cpp
@@ -68,39 +68,39 @@ bool Hotspot::isInside(const Common::Point &pointToCheck) {
 	return ((rcross % 2) == 1);
 }
 
-Operand Hotspot::callMethod(BuiltInMethod methodId, Common::Array<Operand> &args) {
+ScriptValue Hotspot::callMethod(BuiltInMethod methodId, Common::Array<ScriptValue> &args) {
 	switch (methodId) {
 	case kMouseActivateMethod: {
 		assert(args.empty());
 		_isActive = true;
 		g_engine->addPlayingAsset(this);
 		g_engine->_needsHotspotRefresh = true;
-		return Operand();
+		return ScriptValue();
 	}
 
 	case kMouseDeactivateMethod: {
 		assert(args.empty());
 		_isActive = false;
 		g_engine->_needsHotspotRefresh = true;
-		return Operand();
+		return ScriptValue();
 	}
 
 	case kIsActiveMethod: {
 		assert(args.empty());
-		Operand returnValue(kOperandTypeBool);
-		returnValue.putInteger(static_cast<int>(_isActive));
+		ScriptValue returnValue(kOperandTypeBool);
+		returnValue.setToParamToken(static_cast<int>(_isActive));
 		return returnValue;
 	}
 
 	case kTriggerAbsXPositionMethod: {
-		Operand returnValue(kOperandTypeBool);
-		returnValue.putInteger(g_engine->_mousePos.x);
+		ScriptValue returnValue(kOperandTypeBool);
+		returnValue.setToParamToken(g_engine->_mousePos.x);
 		return returnValue;
 	}
 
 	case kTriggerAbsYPositionMethod: {
-		Operand returnValue(kOperandTypeBool);
-		returnValue.putInteger(g_engine->_mousePos.y);
+		ScriptValue returnValue(kOperandTypeBool);
+		returnValue.setToParamToken(g_engine->_mousePos.y);
 		return returnValue;
 	}
 
diff --git a/engines/mediastation/assets/hotspot.h b/engines/mediastation/assets/hotspot.h
index ae5a5728596..fb1aa73e2f3 100644
--- a/engines/mediastation/assets/hotspot.h
+++ b/engines/mediastation/assets/hotspot.h
@@ -24,7 +24,7 @@
 
 #include "mediastation/asset.h"
 #include "mediastation/assetheader.h"
-#include "mediastation/mediascript/operand.h"
+#include "mediastation/mediascript/scriptvalue.h"
 #include "mediastation/mediascript/scriptconstants.h"
 
 namespace MediaStation {
@@ -35,7 +35,7 @@ public:
 
 	bool isInside(const Common::Point &pointToCheck);
 
-	virtual Operand callMethod(BuiltInMethod methodId, Common::Array<Operand> &args) override;
+	virtual ScriptValue callMethod(BuiltInMethod methodId, Common::Array<ScriptValue> &args) override;
 };
 
 } // End of namespace MediaStation
diff --git a/engines/mediastation/assets/image.cpp b/engines/mediastation/assets/image.cpp
index 869f73c9dd8..eabc5fb479b 100644
--- a/engines/mediastation/assets/image.cpp
+++ b/engines/mediastation/assets/image.cpp
@@ -40,24 +40,24 @@ Image::~Image() {
 	_bitmap = nullptr;
 }
 
-Operand Image::callMethod(BuiltInMethod methodId, Common::Array<Operand> &args) {
+ScriptValue Image::callMethod(BuiltInMethod methodId, Common::Array<ScriptValue> &args) {
 	switch (methodId) {
 	case kSpatialShowMethod: {
 		assert(args.empty());
 		spatialShow();
-		return Operand();
+		return ScriptValue();
 	}
 
 	case kSpatialHideMethod: {
 		assert(args.empty());
 		spatialHide();
-		return Operand();
+		return ScriptValue();
 	}
 
 	case kSetDissolveFactorMethod: {
 		assert(args.size() == 1);
 		warning("Image::callMethod(): setDissolveFactor not implemented yet");
-		return Operand();
+		return ScriptValue();
 	}
 
 	case kSpatialMoveToMethod: {
@@ -68,8 +68,8 @@ Operand Image::callMethod(BuiltInMethod methodId, Common::Array<Operand> &args)
 		g_engine->_dirtyRects.push_back(bbox);
 
 		// Update location and mark new location dirty.
-		int newXAdjust = args[0].getInteger();
-		int newYAdjust = args[1].getInteger();
+		int newXAdjust = args[0].asParamToken();
+		int newYAdjust = args[1].asParamToken();
 		if (_xAdjust != newXAdjust || _yAdjust != newYAdjust) {
 			_xAdjust = newXAdjust;
 			_yAdjust = newYAdjust;
@@ -78,7 +78,7 @@ Operand Image::callMethod(BuiltInMethod methodId, Common::Array<Operand> &args)
 			g_engine->_dirtyRects.push_back(bbox);
 		}
 
-		return Operand();
+		return ScriptValue();
 	}
 
 	default:
diff --git a/engines/mediastation/assets/image.h b/engines/mediastation/assets/image.h
index 4f4d502c785..14850c6e930 100644
--- a/engines/mediastation/assets/image.h
+++ b/engines/mediastation/assets/image.h
@@ -26,7 +26,7 @@
 #include "mediastation/datafile.h"
 #include "mediastation/bitmap.h"
 #include "mediastation/assetheader.h"
-#include "mediastation/mediascript/operand.h"
+#include "mediastation/mediascript/scriptvalue.h"
 #include "mediastation/mediascript/scriptconstants.h"
 
 namespace MediaStation {
@@ -42,7 +42,7 @@ public:
 
 	virtual void redraw(Common::Rect &rect) override;
 
-	virtual Operand callMethod(BuiltInMethod methodId, Common::Array<Operand> &args) override;
+	virtual ScriptValue callMethod(BuiltInMethod methodId, Common::Array<ScriptValue> &args) override;
 
 private:
 	Bitmap *_bitmap = nullptr;
diff --git a/engines/mediastation/assets/movie.cpp b/engines/mediastation/assets/movie.cpp
index c188a5f4228..65deb7392ce 100644
--- a/engines/mediastation/assets/movie.cpp
+++ b/engines/mediastation/assets/movie.cpp
@@ -186,77 +186,77 @@ Movie::~Movie() {
 	_footers.clear();
 }
 
-Operand Movie::callMethod(BuiltInMethod methodId, Common::Array<Operand> &args) {
+ScriptValue Movie::callMethod(BuiltInMethod methodId, Common::Array<ScriptValue> &args) {
 	switch (methodId) {
 	case kTimePlayMethod: {
 		assert(args.empty());
 		timePlay();
-		return Operand();
+		return ScriptValue();
 	}
 
 	case kSpatialShowMethod: {
 		assert(args.empty());
 		spatialShow();
-		return Operand();
+		return ScriptValue();
 	}
 
 	case kTimeStopMethod: {
 		assert(args.empty());
 		timeStop();
-		return Operand();
+		return ScriptValue();
 	}
 
 	case kSpatialHideMethod: {
 		assert(args.empty());
 		spatialHide();
-		return Operand();
+		return ScriptValue();
 	}
 
 	case kIsVisibleMethod: {
 		assert(args.empty());
-		Operand returnValue(kOperandTypeBool);
-		returnValue.putInteger(_isShowing);
+		ScriptValue returnValue(kOperandTypeBool);
+		returnValue.setToParamToken(_isShowing);
 		return returnValue;
 	}
 
 	case kSpatialCenterMoveToMethod: {
 		assert(args.size() == 2);
-		spatialCenterMoveTo(args[0].getInteger(), args[1].getInteger());
-		return Operand();
+		spatialCenterMoveTo(args[0].asParamToken(), args[1].asParamToken());
+		return ScriptValue();
 	}
 
 	case kSpatialMoveToMethod: {
 		assert(args.size() == 2);
-		spatialMoveTo(args[0].getInteger(), args[1].getInteger());
-		return Operand();
+		spatialMoveTo(args[0].asParamToken(), args[1].asParamToken());
+		return ScriptValue();
 	}
 
 	case kIsPlayingMethod: {
 		assert(args.empty());
-		Operand returnValue(kOperandTypeBool);
-		returnValue.putInteger(static_cast<uint>(_isPlaying));
+		ScriptValue returnValue(kOperandTypeBool);
+		returnValue.setToParamToken(static_cast<uint>(_isPlaying));
 		return returnValue;
 	}
 
 	case kXPositionMethod: {
 		assert(args.empty());
-		Operand returnValue(kOperandTypeBool);
-		returnValue.putInteger(_header->_boundingBox->left);
+		ScriptValue returnValue(kOperandTypeBool);
+		returnValue.setToParamToken(_header->_boundingBox->left);
 		return returnValue;
 
 	}
 
 	case kYPositionMethod: {
 		assert(args.empty());
-		Operand returnValue(kOperandTypeBool);
-		returnValue.putInteger(_header->_boundingBox->top);
+		ScriptValue returnValue(kOperandTypeBool);
+		returnValue.setToParamToken(_header->_boundingBox->top);
 		return returnValue;
 	}
 
 	case kSetDissolveFactorMethod: {
 		assert(args.size() == 1);
 		warning("Movie::callMethod(): setDissolveFactor not implemented yet");
-		return Operand();
+		return ScriptValue();
 	}
 
 	default:
diff --git a/engines/mediastation/assets/movie.h b/engines/mediastation/assets/movie.h
index 5b1841449b7..514f4f57f74 100644
--- a/engines/mediastation/assets/movie.h
+++ b/engines/mediastation/assets/movie.h
@@ -97,7 +97,7 @@ public:
 	virtual void readChunk(Chunk &chunk) override;
 	virtual void readSubfile(Subfile &subfile, Chunk &chunk) override;
 
-	virtual Operand callMethod(BuiltInMethod methodId, Common::Array<Operand> &args) override;
+	virtual ScriptValue callMethod(BuiltInMethod methodId, Common::Array<ScriptValue> &args) override;
 	virtual void process() override;
 
 	virtual void redraw(Common::Rect &rect) override;
diff --git a/engines/mediastation/assets/palette.cpp b/engines/mediastation/assets/palette.cpp
index f9fa8ee09cc..c3d09005700 100644
--- a/engines/mediastation/assets/palette.cpp
+++ b/engines/mediastation/assets/palette.cpp
@@ -25,7 +25,7 @@
 
 namespace MediaStation {
 
-Operand Palette::callMethod(BuiltInMethod methodId, Common::Array<Operand> &args) {
+ScriptValue Palette::callMethod(BuiltInMethod methodId, Common::Array<ScriptValue> &args) {
 	switch (methodId) {
 	default:
 		error("Palette::callMethod(): Got unimplemented method ID %s (%d)", builtInMethodToStr(methodId), static_cast<uint>(methodId));
diff --git a/engines/mediastation/assets/palette.h b/engines/mediastation/assets/palette.h
index 478a34a26a6..d960792aaa1 100644
--- a/engines/mediastation/assets/palette.h
+++ b/engines/mediastation/assets/palette.h
@@ -24,7 +24,7 @@
 
 #include "mediastation/assetheader.h"
 #include "mediastation/asset.h"
-#include "mediastation/mediascript/operand.h"
+#include "mediastation/mediascript/scriptvalue.h"
 #include "mediastation/mediascript/scriptconstants.h"
 
 namespace MediaStation {
@@ -33,7 +33,7 @@ class Palette : public Asset {
 public:
 	Palette(AssetHeader *header) : Asset(header) {};
 
-	virtual Operand callMethod(BuiltInMethod methodId, Common::Array<Operand> &args) override;
+	virtual ScriptValue callMethod(BuiltInMethod methodId, Common::Array<ScriptValue> &args) override;
 };
 
 } // End of namespace MediaStation
diff --git a/engines/mediastation/assets/path.cpp b/engines/mediastation/assets/path.cpp
index 70657ed3b9c..c153547f310 100644
--- a/engines/mediastation/assets/path.cpp
+++ b/engines/mediastation/assets/path.cpp
@@ -28,38 +28,38 @@ Path::~Path() {
 	_percentComplete = 0;
 }
 
-Operand Path::callMethod(BuiltInMethod methodId, Common::Array<Operand> &args) {
+ScriptValue Path::callMethod(BuiltInMethod methodId, Common::Array<ScriptValue> &args) {
 	switch (methodId) {
 	case kTimePlayMethod: {
 		assert(args.size() == 0);
 		timePlay();
-		return Operand();
+		return ScriptValue();
 	}
 
 	case kSetDurationMethod: {
 		assert(args.size() == 1);
-		uint durationInMilliseconds = static_cast<uint>(args[0].getDouble() * 1000);
+		uint durationInMilliseconds = static_cast<uint>(args[0].asFloat() * 1000);
 		setDuration(durationInMilliseconds);
-		return Operand();
+		return ScriptValue();
 	}
 
 	case kPercentCompleteMethod: {
 		assert(args.size() == 0);
-		Operand returnValue(kOperandTypeTime);
-		returnValue.putDouble(percentComplete());
+		ScriptValue returnValue(kOperandTypeTime);
+		returnValue.setToFloat(percentComplete());
 		return returnValue;
 	}
 
 	case kSetDissolveFactorMethod: {
 		assert(args.size() == 1);
 		warning("Path::callMethod(): setDissolveFactor not implemented yet");
-		return Operand();
+		return ScriptValue();
 	}
 
 	case kIsPlayingMethod: {
 		assert(args.empty());
-		Operand returnValue(kOperandTypeBool);
-		returnValue.putInteger(_isActive);
+		ScriptValue returnValue(kOperandTypeBool);
+		returnValue.setToParamToken(_isActive);
 		return returnValue;
 	}
 
diff --git a/engines/mediastation/assets/path.h b/engines/mediastation/assets/path.h
index 939dfdf1e41..60239bc7ef5 100644
--- a/engines/mediastation/assets/path.h
+++ b/engines/mediastation/assets/path.h
@@ -24,7 +24,7 @@
 
 #include "mediastation/assetheader.h"
 #include "mediastation/asset.h"
-#include "mediastation/mediascript/operand.h"
+#include "mediastation/mediascript/scriptvalue.h"
 #include "mediastation/mediascript/scriptconstants.h"
 
 namespace MediaStation {
@@ -36,7 +36,7 @@ public:
 
 	virtual void process() override;
 
-	virtual Operand callMethod(BuiltInMethod methodId, Common::Array<Operand> &args) override;
+	virtual ScriptValue callMethod(BuiltInMethod methodId, Common::Array<ScriptValue> &args) override;
 
 private:
 	double _percentComplete = 0.0;
diff --git a/engines/mediastation/assets/screen.cpp b/engines/mediastation/assets/screen.cpp
index 40fe2f6ee4e..5c05cba4251 100644
--- a/engines/mediastation/assets/screen.cpp
+++ b/engines/mediastation/assets/screen.cpp
@@ -24,7 +24,7 @@
 
 namespace MediaStation {
 
-Operand Screen::callMethod(BuiltInMethod methodId, Common::Array<Operand> &args) {
+ScriptValue Screen::callMethod(BuiltInMethod methodId, Common::Array<ScriptValue> &args) {
 	switch (methodId) {
 	default:
 		error("Screen::callMethod(): Got unimplemented method ID %s (%d)", builtInMethodToStr(methodId), static_cast<uint>(methodId));
diff --git a/engines/mediastation/assets/screen.h b/engines/mediastation/assets/screen.h
index 463e68553e2..7a8c16f1732 100644
--- a/engines/mediastation/assets/screen.h
+++ b/engines/mediastation/assets/screen.h
@@ -24,7 +24,7 @@
 
 #include "mediastation/assetheader.h"
 #include "mediastation/asset.h"
-#include "mediastation/mediascript/operand.h"
+#include "mediastation/mediascript/scriptvalue.h"
 #include "mediastation/mediascript/scriptconstants.h"
 
 namespace MediaStation {
@@ -36,7 +36,7 @@ class Screen : public Asset {
 public:
 	Screen(AssetHeader *header) : Asset(header) {};
 
-	virtual Operand callMethod(BuiltInMethod methodId, Common::Array<Operand> &args) override;
+	virtual ScriptValue callMethod(BuiltInMethod methodId, Common::Array<ScriptValue> &args) override;
 };
 
 } // End of namespace MediaStation
diff --git a/engines/mediastation/assets/sound.cpp b/engines/mediastation/assets/sound.cpp
index 0d6b9edb1ec..78fed5f8416 100644
--- a/engines/mediastation/assets/sound.cpp
+++ b/engines/mediastation/assets/sound.cpp
@@ -55,18 +55,18 @@ void Sound::process() {
 	}
 }
 
-Operand Sound::callMethod(BuiltInMethod methodId, Common::Array<Operand> &args) {
+ScriptValue Sound::callMethod(BuiltInMethod methodId, Common::Array<ScriptValue> &args) {
 	switch (methodId) {
 	case kTimePlayMethod: {
 		assert(args.empty());
 		timePlay();
-		return Operand();
+		return ScriptValue();
 	}
 
 	case kTimeStopMethod: {
 		assert(args.empty());
 		timeStop();
-		return Operand();
+		return ScriptValue();
 	}
 
 	default:
diff --git a/engines/mediastation/assets/sound.h b/engines/mediastation/assets/sound.h
index 6330128b594..41435f730e4 100644
--- a/engines/mediastation/assets/sound.h
+++ b/engines/mediastation/assets/sound.h
@@ -27,7 +27,7 @@
 #include "mediastation/asset.h"
 #include "mediastation/datafile.h"
 #include "mediastation/assetheader.h"
-#include "mediastation/mediascript/operand.h"
+#include "mediastation/mediascript/scriptvalue.h"
 #include "mediastation/mediascript/scriptconstants.h"
 
 namespace MediaStation {
@@ -37,7 +37,7 @@ public:
 	Sound(AssetHeader *header);
 	~Sound();
 
-	virtual Operand callMethod(BuiltInMethod methodId, Common::Array<Operand> &args) override;
+	virtual ScriptValue callMethod(BuiltInMethod methodId, Common::Array<ScriptValue> &args) override;
 	virtual void process() override;
 
 	virtual void readChunk(Chunk& chunk) override;
diff --git a/engines/mediastation/assets/sprite.cpp b/engines/mediastation/assets/sprite.cpp
index 6c1dbc6d3bc..d0091a4da66 100644
--- a/engines/mediastation/assets/sprite.cpp
+++ b/engines/mediastation/assets/sprite.cpp
@@ -85,58 +85,58 @@ Sprite::~Sprite() {
 	_frames.clear();
 }
 
-Operand Sprite::callMethod(BuiltInMethod methodId, Common::Array<Operand> &args) {
+ScriptValue Sprite::callMethod(BuiltInMethod methodId, Common::Array<ScriptValue> &args) {
 	switch (methodId) {
 	case kSpatialShowMethod: {
 		assert(args.empty());
 		spatialShow();
-		return Operand();
+		return ScriptValue();
 	}
 
 	case kSpatialHideMethod: {
 		assert(args.empty());
 		spatialHide();
-		return Operand();
+		return ScriptValue();
 	}
 
 	case kTimePlayMethod: {
 		assert(args.empty());
 		timePlay();
-		return Operand();
+		return ScriptValue();
 	}
 
 	case kTimeStopMethod: {
 		assert(args.empty());
 		timeStop();
-		return Operand();
+		return ScriptValue();
 	}
 
 	case kMovieResetMethod: {
 		assert(args.empty());
 		movieReset();
-		return Operand();
+		return ScriptValue();
 	}
 
 	case kSetCurrentClipMethod: {
 		assert(args.size() <= 1);
-		if (args.size() == 1 && args[0].getInteger() != 0) {
-			error("Sprite::callMethod(): (%d) setClip() called with unhandled arg: %d", _header->_id, args[0].getInteger());
+		if (args.size() == 1 && args[0].asParamToken() != 0) {
+			error("Sprite::callMethod(): (%d) setClip() called with unhandled arg: %d", _header->_id, args[0].asParamToken());
 		}
 		setCurrentClip();
-		return Operand();
+		return ScriptValue();
 	}
 
 	case kSetSpriteFrameByIdMethod: {
 		assert(args.size() == 1);
-		uint32 externalFrameId = args[0].getInteger();
+		uint32 externalFrameId = args[0].asParamToken();
 		uint32 internalFrameId = _header->_spriteFrameMapping.getVal(externalFrameId);
 		showFrame(_frames[internalFrameId]);
-		return Operand();
+		return ScriptValue();
 	}
 
 	case kIsPlayingMethod: {
-		Operand returnValue(kOperandTypeBool);
-		returnValue.putInteger(static_cast<int>(_isPlaying));
+		ScriptValue returnValue(kOperandTypeBool);
+		returnValue.setToParamToken(static_cast<int>(_isPlaying));
 		return returnValue;
 	}
 
@@ -149,8 +149,8 @@ Operand Sprite::callMethod(BuiltInMethod methodId, Common::Array<Operand> &args)
 		}
 
 		// Update the location and mark the new location dirty.
-		int newXAdjust = args[0].getInteger();
-		int newYAdjust = args[1].getInteger();
+		int newXAdjust = args[0].asParamToken();
+		int newYAdjust = args[1].asParamToken();
 		if (_xAdjust != newXAdjust || _yAdjust != newYAdjust) {
 			debugC(5, kDebugGraphics, "Sprite::callMethod(): (%d) Moving sprite to (%d, %d)", _header->_id, newXAdjust, newYAdjust);
 			_xAdjust = newXAdjust;
@@ -160,7 +160,7 @@ Operand Sprite::callMethod(BuiltInMethod methodId, Common::Array<Operand> &args)
 			}
 		}
 
-		return Operand();
+		return ScriptValue();
 	}
 
 	default:
diff --git a/engines/mediastation/assets/sprite.h b/engines/mediastation/assets/sprite.h
index 1999ae819f5..2715ac12140 100644
--- a/engines/mediastation/assets/sprite.h
+++ b/engines/mediastation/assets/sprite.h
@@ -29,7 +29,7 @@
 #include "mediastation/assetheader.h"
 #include "mediastation/datafile.h"
 #include "mediastation/bitmap.h"
-#include "mediastation/mediascript/operand.h"
+#include "mediastation/mediascript/scriptvalue.h"
 #include "mediastation/mediascript/scriptconstants.h"
 
 namespace MediaStation {
@@ -67,7 +67,7 @@ public:
 	Sprite(AssetHeader *header);
 	~Sprite();
 
-	virtual Operand callMethod(BuiltInMethod methodId, Common::Array<Operand> &args) override;
+	virtual ScriptValue callMethod(BuiltInMethod methodId, Common::Array<ScriptValue> &args) override;
 	virtual void process() override;
 	virtual void redraw(Common::Rect &rect) override;
 
diff --git a/engines/mediastation/assets/stage.h b/engines/mediastation/assets/stage.h
index bbbdd89ae02..e6768a07719 100644
--- a/engines/mediastation/assets/stage.h
+++ b/engines/mediastation/assets/stage.h
@@ -30,7 +30,7 @@ class Camera : public Asset {
 public:
 	Camera(AssetHeader *header) : Asset(header) {};
 
-	virtual Operand callMethod(BuiltInMethod methodId, Common::Array<Operand> &args) override { error("CallMethod not implemented"); };
+	virtual ScriptValue callMethod(BuiltInMethod methodId, Common::Array<ScriptValue> &args) override { error("CallMethod not implemented"); };
 	virtual void process() override {};
 };
 
@@ -38,7 +38,7 @@ class Stage : public Asset {
 public:
 	Stage(AssetHeader *header) : Asset(header) {};
 
-	virtual Operand callMethod(BuiltInMethod methodId, Common::Array<Operand> &args) override { error("CallMethod not implemented"); };
+	virtual ScriptValue callMethod(BuiltInMethod methodId, Common::Array<ScriptValue> &args) override { error("CallMethod not implemented"); };
 	virtual void process() override {};
 
 private:
diff --git a/engines/mediastation/assets/text.cpp b/engines/mediastation/assets/text.cpp
index 40f4d68240e..a835bdadc24 100644
--- a/engines/mediastation/assets/text.cpp
+++ b/engines/mediastation/assets/text.cpp
@@ -23,7 +23,7 @@
 
 namespace MediaStation {
 
-Operand Text::callMethod(BuiltInMethod methodId, Common::Array<Operand> &args) {
+ScriptValue Text::callMethod(BuiltInMethod methodId, Common::Array<ScriptValue> &args) {
 	switch (methodId) {
 	case kTextMethod: {
 		assert(args.empty());
@@ -39,14 +39,14 @@ Operand Text::callMethod(BuiltInMethod methodId, Common::Array<Operand> &args) {
 		assert(args.empty());
 		_isActive = true;
 		warning("Text::callMethod(): spatialShow method not implemented yet");
-		return Operand();
+		return ScriptValue();
 	}
 
 	case kSpatialHideMethod: {
 		assert(args.empty());
 		_isActive = false;
 		warning("Text::callMethod(): spatialHide method not implemented yet");
-		return Operand();
+		return ScriptValue();
 	}
 
 	default:
diff --git a/engines/mediastation/assets/text.h b/engines/mediastation/assets/text.h
index 3eabe279fc1..49e4065483c 100644
--- a/engines/mediastation/assets/text.h
+++ b/engines/mediastation/assets/text.h
@@ -26,7 +26,7 @@
 
 #include "mediastation/asset.h"
 #include "mediastation/assetheader.h"
-#include "mediastation/mediascript/operand.h"
+#include "mediastation/mediascript/scriptvalue.h"
 #include "mediastation/mediascript/scriptconstants.h"
 
 namespace MediaStation {
@@ -35,7 +35,7 @@ class Text : public Asset {
 public:
 	Text(AssetHeader *header) : Asset(header) {};
 
-	virtual Operand callMethod(BuiltInMethod methodId, Common::Array<Operand> &args) override;
+	virtual ScriptValue callMethod(BuiltInMethod methodId, Common::Array<ScriptValue> &args) override;
 
 private:
 	// Method implementations.
diff --git a/engines/mediastation/assets/timer.cpp b/engines/mediastation/assets/timer.cpp
index 097cc992274..41e28680dc4 100644
--- a/engines/mediastation/assets/timer.cpp
+++ b/engines/mediastation/assets/timer.cpp
@@ -26,24 +26,24 @@
 
 namespace MediaStation {
 
-Operand Timer::callMethod(BuiltInMethod methodId, Common::Array<Operand> &args) {
+ScriptValue Timer::callMethod(BuiltInMethod methodId, Common::Array<ScriptValue> &args) {
 	switch (methodId) {
 	case kTimePlayMethod: {
 		assert(args.size() == 0);
 		timePlay();
-		return Operand();
+		return ScriptValue();
 	}
 
 	case kTimeStopMethod: {
 		assert(args.size() == 0);
 		timeStop();
-		return Operand();
+		return ScriptValue();
 	}
 
 	case kIsPlayingMethod: {
 		assert(args.size() == 0);
-		Operand returnValue(kOperandTypeBool);
-		returnValue.putInteger(static_cast<int>(_isActive));
+		ScriptValue returnValue(kOperandTypeBool);
+		returnValue.setToParamToken(static_cast<int>(_isActive));
 		return returnValue;
 	}
 
diff --git a/engines/mediastation/assets/timer.h b/engines/mediastation/assets/timer.h
index b9b7ee84491..ecf037becf8 100644
--- a/engines/mediastation/assets/timer.h
+++ b/engines/mediastation/assets/timer.h
@@ -24,7 +24,7 @@
 
 #include "mediastation/asset.h"
 #include "mediastation/assetheader.h"
-#include "mediastation/mediascript/operand.h"
+#include "mediastation/mediascript/scriptvalue.h"
 #include "mediastation/mediascript/scriptconstants.h"
 
 namespace MediaStation {
@@ -33,7 +33,7 @@ class Timer : public Asset {
 public:
 	Timer(AssetHeader *header) : Asset(header) {};
 
-	virtual Operand callMethod(BuiltInMethod methodId, Common::Array<Operand> &args) override;
+	virtual ScriptValue callMethod(BuiltInMethod methodId, Common::Array<ScriptValue> &args) override;
 	virtual void process() override;
 
 private:
diff --git a/engines/mediastation/context.cpp b/engines/mediastation/context.cpp
index 98633884844..b3ae09fa4c0 100644
--- a/engines/mediastation/context.cpp
+++ b/engines/mediastation/context.cpp
@@ -185,11 +185,11 @@ void Context::readParametersSection(Chunk &chunk) {
 			if (g_engine->_variables.contains(variable->_id)) {
 				// Don't overwrite the variable if it already exists. This can happen if we have
 				// unloaded a screen but are returning to it later.
-				debugC(5, kDebugScript, "ContextParameters::ContextParameters(): Skipping re-creation of existing global variable %d (type: %s)", variable->_id, variableTypeToStr(variable->_type));
+				debugC(5, kDebugScript, "ContextParameters::ContextParameters(): Skipping re-creation of existing global variable %d (type: %s)", variable->_id, scriptValueTypeToStr(variable->_type));
 				delete variable;
 			} else {
 				g_engine->_variables.setVal(variable->_id, variable);
-				debugC(5, kDebugScript, "ContextParameters::ContextParameters(): Created global variable %d (type: %s)", variable->_id, variableTypeToStr(variable->_type));
+				debugC(5, kDebugScript, "ContextParameters::ContextParameters(): Created global variable %d (type: %s)", variable->_id, scriptValueTypeToStr(variable->_type));
 			}
 			break;
 		}
diff --git a/engines/mediastation/mediascript/codechunk.cpp b/engines/mediastation/mediascript/codechunk.cpp
index a82596c006c..4cf9da62598 100644
--- a/engines/mediastation/mediascript/codechunk.cpp
+++ b/engines/mediastation/mediascript/codechunk.cpp
@@ -34,12 +34,12 @@ CodeChunk::CodeChunk(Common::SeekableReadStream &chunk) {
 	_bytecode = chunk.readStream(lengthInBytes);
 }
 
-Operand CodeChunk::execute(Common::Array<Operand> *args, Common::Array<Operand> *locals) {
+ScriptValue CodeChunk::execute(Common::Array<ScriptValue> *args, Common::Array<ScriptValue> *locals) {
 	_locals = locals;
 	_args = args;
-	Operand returnValue;
+	ScriptValue returnValue;
 	while (_bytecode->pos() < _bytecode->size()) {
-		Operand instructionResult = evaluateExpression();
+		ScriptValue instructionResult = evaluateExpression();
 		if (instructionResult.getType() != kOperandTypeEmpty) {
 			returnValue = instructionResult;
 		}
@@ -60,7 +60,7 @@ Operand CodeChunk::execute(Common::Array<Operand> *args, Common::Array<Operand>
 	return returnValue;
 }
 
-Operand CodeChunk::evaluateExpression() {
+ScriptValue CodeChunk::evaluateExpression() {
 	if (_bytecode->eos()) {
 		error("CodeChunk::evaluateExpression(): Attempt to read past end of bytecode chunk");
 	}
@@ -68,10 +68,10 @@ Operand CodeChunk::evaluateExpression() {
 	ExpressionType instructionType = static_cast<ExpressionType>(Datum(*_bytecode).u.i);
 	debugCN(5, kDebugScript, "(%s) ", expressionTypeToStr(instructionType));
 
-	Operand returnValue;
+	ScriptValue returnValue;
 	switch (instructionType) {
 	case kExpressionTypeEmpty: {
-		return Operand();
+		return ScriptValue();
 	}
 
 	case kExpressionTypeOperation:
@@ -93,7 +93,7 @@ Operand CodeChunk::evaluateExpression() {
 	return returnValue;
 }
 
-Operand CodeChunk::evaluateOperation() {
+ScriptValue CodeChunk::evaluateOperation() {
 	Opcode opcode = static_cast<Opcode>(Datum(*_bytecode).u.i);
 	debugCN(5, kDebugScript, "%s ", opcodeToStr(opcode));
 	switch (opcode) {
@@ -102,10 +102,10 @@ Operand CodeChunk::evaluateOperation() {
 		VariableScope scope = static_cast<VariableScope>(Datum(*_bytecode).u.i);
 		debugC(5, kDebugScript, "%d (%s) ", id, variableScopeToStr(scope));
 		debugCN(5, kDebugScript, "  Value: ");
-		Operand newValue = evaluateExpression();
+		ScriptValue newValue = evaluateExpression();
 
 		putVariable(id, scope, newValue);
-		return Operand();
+		return ScriptValue();
 	}
 
 	case kOpcodeCallFunction: {
@@ -124,14 +124,14 @@ Operand CodeChunk::evaluateOperation() {
 		uint32 parameterCount = Datum(*_bytecode).u.i;
 		debugC(5, kDebugScript, "%s (%d params)", builtInMethodToStr(methodId), parameterCount);
 		debugCN(5, kDebugScript, "  Self: ");
-		Operand selfObject = evaluateExpression();
-		Common::Array<Operand> args;
+		ScriptValue selfObject = evaluateExpression();
+		Common::Array<ScriptValue> args;
 		for (uint i = 0; i < parameterCount; i++) {
 			debugCN(5, kDebugScript, "  Param %d: ", i);
-			Operand arg = evaluateExpression();
+			ScriptValue arg = evaluateExpression();
 			args.push_back(arg);
 		}
-		Operand returnValue = callBuiltInMethod(methodId, selfObject, args);
+		ScriptValue returnValue = callBuiltInMethod(methodId, selfObject, args);
 		return returnValue;
 	}
 
@@ -139,187 +139,187 @@ Operand CodeChunk::evaluateOperation() {
 		uint32 localVariableCount = Datum(*_bytecode).u.i;
 		debugC(5, kDebugScript, "%d", localVariableCount);
 		assert(_locals == nullptr);
-		_locals = new Common::Array<Operand>(localVariableCount);
+		_locals = new Common::Array<ScriptValue>(localVariableCount);
 		_weOwnLocals = true;
-		return Operand();
+		return ScriptValue();
 	}
 
 	case kOpcodeOr: {
 		debugCN(5, kDebugScript, "\n    lhs: ");
-		Operand value1 = evaluateExpression();
+		ScriptValue value1 = evaluateExpression();
 		debugCN(5, kDebugScript, "    rhs: ");
-		Operand value2 = evaluateExpression();
+		ScriptValue value2 = evaluateExpression();
 
-		Operand returnValue(kOperandTypeBool);
+		ScriptValue returnValue(kOperandTypeBool);
 		bool logicalOr = (value1 || value2);
-		returnValue.putInteger(static_cast<uint>(logicalOr));
+		returnValue.setToParamToken(static_cast<uint>(logicalOr));
 		return returnValue;
 	}
 
 	case kOpcodeNot: {
 		debugCN(5, kDebugScript, "\n    value: ");
-		Operand value = evaluateExpression();
+		ScriptValue value = evaluateExpression();
 
-		Operand returnValue(kOperandTypeBool);
-		bool logicalNot = !(static_cast<bool>(value.getInteger()));
-		returnValue.putInteger(static_cast<uint>(logicalNot));
+		ScriptValue returnValue(kOperandTypeBool);
+		bool logicalNot = !(static_cast<bool>(value.asParamToken()));
+		returnValue.setToParamToken(static_cast<uint>(logicalNot));
 		return returnValue;
 	}
 
 	case kOpcodeAnd: {
 		debugCN(5, kDebugScript, "\n    value: ");
-		Operand value1 = evaluateExpression();
+		ScriptValue value1 = evaluateExpression();
 		debugCN(5, kDebugScript, "    rhs: ");
-		Operand value2 = evaluateExpression();
+		ScriptValue value2 = evaluateExpression();
 
-		Operand returnValue(kOperandTypeBool);
+		ScriptValue returnValue(kOperandTypeBool);
 		bool logicalAnd = (value1 && value2);
-		returnValue.putInteger(static_cast<uint>(logicalAnd));
+		returnValue.setToParamToken(static_cast<uint>(logicalAnd));
 		return returnValue;
 	}
 
 	case kOpcodeIfElse: {
 		debugCN(5, kDebugScript, "\n    condition: ");
-		Operand condition = evaluateExpression();
+		ScriptValue condition = evaluateExpression();
 
 		CodeChunk ifBlock(*_bytecode);
 		CodeChunk elseBlock(*_bytecode);
 		// Doesn't seem like there is a real bool type for values,
 		// ao just get an integer.
-		if (condition.getInteger()) {
+		if (condition.asParamToken()) {
 			ifBlock.execute(_args, _locals);
 		} else {
 			elseBlock.execute(_args, _locals);
 		}
 
 		// If blocks themselves shouldn't return anything.
-		return Operand();
+		return ScriptValue();
 	}
 
 	case kOpcodeEquals: {
 		debugCN(5, kDebugScript, "\n    lhs: ");
-		Operand value1 = evaluateExpression();
+		ScriptValue value1 = evaluateExpression();
 		debugCN(5, kDebugScript, "    rhs: ");
-		Operand value2 = evaluateExpression();
+		ScriptValue value2 = evaluateExpression();
 
-		Operand returnValue(kOperandTypeBool);
+		ScriptValue returnValue(kOperandTypeBool);
 		bool equal = (value1 == value2);
-		returnValue.putInteger(static_cast<uint>(equal));
+		returnValue.setToParamToken(static_cast<uint>(equal));
 		return returnValue;
 	}
 
 	case kOpcodeNotEquals: {
 		debugCN(5, kDebugScript, "\n    lhs: ");
-		Operand value1 = evaluateExpression();
+		ScriptValue value1 = evaluateExpression();
 		debugCN(5, kDebugScript, "    rhs: ");
-		Operand value2 = evaluateExpression();
+		ScriptValue value2 = evaluateExpression();
 
-		Operand returnValue(kOperandTypeBool);
+		ScriptValue returnValue(kOperandTypeBool);
 		bool notEqual = !(value1 == value2);
-		returnValue.putInteger(static_cast<uint>(notEqual));
+		returnValue.setToParamToken(static_cast<uint>(notEqual));
 		return returnValue;
 	}
 
 	case kOpcodeLessThan: {
 		debugCN(5, kDebugScript, "\n    lhs: ");
-		Operand value1 = evaluateExpression();
+		ScriptValue value1 = evaluateExpression();
 		debugCN(5, kDebugScript, "    rhs: ");
-		Operand value2 = evaluateExpression();
+		ScriptValue value2 = evaluateExpression();
 
-		Operand returnValue(kOperandTypeBool);
+		ScriptValue returnValue(kOperandTypeBool);
 		bool lessThan = (value1 < value2);
-		returnValue.putInteger(static_cast<uint>(lessThan));
+		returnValue.setToParamToken(static_cast<uint>(lessThan));
 		return returnValue;
 	}
 
 	case kOpcodeGreaterThan: {
 		debugCN(5, kDebugScript, "\n    lhs: ");
-		Operand value1 = evaluateExpression();
+		ScriptValue value1 = evaluateExpression();
 		debugCN(5, kDebugScript, "    rhs: ");
-		Operand value2 = evaluateExpression();
+		ScriptValue value2 = evaluateExpression();
 
-		Operand returnValue(kOperandTypeBool);
+		ScriptValue returnValue(kOperandTypeBool);
 		bool greaterThan = (value1 > value2);
-		returnValue.putInteger(static_cast<uint>(greaterThan));
+		returnValue.setToParamToken(static_cast<uint>(greaterThan));
 		return returnValue;
 	}
 
 	case kOpcodeLessThanOrEqualTo: {
 		debugCN(5, kDebugScript, "\n    lhs: ");
-		Operand value1 = evaluateExpression();
+		ScriptValue value1 = evaluateExpression();
 		debugCN(5, kDebugScript, "    rhs: ");
-		Operand value2 = evaluateExpression();
+		ScriptValue value2 = evaluateExpression();
 
-		Operand returnValue(kOperandTypeBool);
+		ScriptValue returnValue(kOperandTypeBool);
 		bool lessThanOrEqualTo = (value1 < value2) || (value1 == value2);
-		returnValue.putInteger(static_cast<uint>(lessThanOrEqualTo));
+		returnValue.setToParamToken(static_cast<uint>(lessThanOrEqualTo));
 		return returnValue;
 	}
 
 	case kOpcodeGreaterThanOrEqualTo: {
 		debugCN(5, kDebugScript, "\n    lhs: ");
-		Operand value1 = evaluateExpression();
+		ScriptValue value1 = evaluateExpression();
 		debugCN(5, kDebugScript, "    rhs: ");
-		Operand value2 = evaluateExpression();
+		ScriptValue value2 = evaluateExpression();
 
-		Operand returnValue(kOperandTypeBool);
+		ScriptValue returnValue(kOperandTypeBool);
 		bool greaterThanOrEqualTo = (value1 > value2) || (value1 == value2);
-		returnValue.putInteger(static_cast<uint>(greaterThanOrEqualTo));
+		returnValue.setToParamToken(static_cast<uint>(greaterThanOrEqualTo));
 		return returnValue;
 	}
 
 	case kOpcodeAdd: {
 		debugCN(5, kDebugScript, "\n    lhs: ");
-		Operand value1 = evaluateExpression();
+		ScriptValue value1 = evaluateExpression();
 		debugCN(5, kDebugScript, "    rhs: ");
-		Operand value2 = evaluateExpression();
+		ScriptValue value2 = evaluateExpression();
 
-		Operand returnValue = value1 + value2;
+		ScriptValue returnValue = value1 + value2;
 		return returnValue;
 	}
 
 	case kOpcodeSubtract: {
 		debugCN(5, kDebugScript, "\n    lhs: ");
-		Operand value1 = evaluateExpression();
+		ScriptValue value1 = evaluateExpression();
 		debugCN(5, kDebugScript, "    rhs: ");
-		Operand value2 = evaluateExpression();
+		ScriptValue value2 = evaluateExpression();
 
-		Operand returnValue = value1 - value2;
+		ScriptValue returnValue = value1 - value2;
 		return returnValue;
 	}
 
 	case kOpcodeMultiply: {
 		debugCN(5, kDebugScript, "\n    lhs: ");
-		Operand value1 = evaluateExpression();
+		ScriptValue value1 = evaluateExpression();
 		debugCN(5, kDebugScript, "    rhs: ");
-		Operand value2 = evaluateExpression();
+		ScriptValue value2 = evaluateExpression();
 
-		Operand returnValue = value1 * value2;
+		ScriptValue returnValue = value1 * value2;
 		return returnValue;
 	}
 
 	case kOpcodeDivide: {
 		debugCN(5, kDebugScript, "\n    lhs: ");
-		Operand value1 = evaluateExpression();
+		ScriptValue value1 = evaluateExpression();
 		debugCN(5, kDebugScript, "    rhs: ");
-		Operand value2 = evaluateExpression();
+		ScriptValue value2 = evaluateExpression();
 
-		Operand returnValue = value1 / value2;
+		ScriptValue returnValue = value1 / value2;
 		return returnValue;
 	}
 
 	case kOpcodeModulo: {
 		debugCN(5, kDebugScript, "\n    lhs: ");
-		Operand value1 = evaluateExpression();
+		ScriptValue value1 = evaluateExpression();
 		debugCN(5, kDebugScript, "    rhs: ");
-		Operand value2 = evaluateExpression();
+		ScriptValue value2 = evaluateExpression();
 
-		Operand returnValue = value1 % value2;
+		ScriptValue returnValue = value1 % value2;
 		return returnValue;
 	}
 
 	case kOpcodeNegate: {
-		Operand value = evaluateExpression();
+		ScriptValue value = evaluateExpression();
 		debugCN(5, kDebugScript, "    value: ");
 
 		return -value;
@@ -327,15 +327,15 @@ Operand CodeChunk::evaluateOperation() {
 
 	case kOpcodeReturn: {
 		debugCN(5, kDebugScript, "    return: ");
-		Operand value = evaluateExpression();
+		ScriptValue value = evaluateExpression();
 
 		return value;
 	}
 
 	case kOpcodeCallFunctionInVariable: {
 		uint parameterCount = Datum(*_bytecode).u.i;
-		Operand variable = evaluateExpression();
-		uint functionId = variable.getFunctionId();
+		ScriptValue variable = evaluateExpression();
+		uint functionId = variable.asFunctionId();
 		debugC(5, kDebugScript, "Variable %d [function %d] (%d params)", variable.getVariable()->_id, functionId, parameterCount);
 
 		return callFunction(functionId, parameterCount);
@@ -346,11 +346,11 @@ Operand CodeChunk::evaluateOperation() {
 	}
 }
 
-Operand CodeChunk::evaluateValue() {
+ScriptValue CodeChunk::evaluateValue() {
 	OperandType operandType = static_cast<OperandType>(Datum(*_bytecode).u.i);
 	debugCN(5, kDebugScript, "%s ", operandTypeToStr(operandType));
 
-	Operand operand(operandType);
+	ScriptValue returnValue(operandType);
 	switch (operandType) {
 	case kOperandTypeBool: {
 		int b = Datum(*_bytecode).u.i;
@@ -358,22 +358,22 @@ Operand CodeChunk::evaluateValue() {
 			error("Got invalid boolean value %d", b);
 		}
 		debugC(5, kDebugScript, "%d ", b);
-		operand.putInteger(b == 1 ? true : false);
-		return operand;
+		returnValue.setToParamToken(b == 1 ? true : false);
+		return returnValue;
 	}
 
 	case kOperandTypeFloat: {
 		double f = Datum(*_bytecode).u.f;
 		debugC(5, kDebugScript, "%f ", f);
-		operand.putDouble(f);
-		return operand;
+		returnValue.setToFloat(f);
+		return returnValue;
 	}
 
 	case kOperandTypeInt: {
 		int i = Datum(*_bytecode).u.i;
 		debugC(5, kDebugScript, "%d ", i);
-		operand.putInteger(i);
-		return operand;
+		returnValue.setToParamToken(i);
+		return returnValue;
 	}
 
 	case kOperandTypeString: {
@@ -385,30 +385,30 @@ Operand CodeChunk::evaluateValue() {
 		buffer[size] = '\0';
 		Common::String *string = new Common::String(buffer);
 		debugC(5, kDebugScript, "%s ", string->c_str());
-		operand.putString(string);
+		returnValue.setToString(string);
 		delete[] buffer;
-		return operand;
+		return returnValue;
 	}
 
-	case kOperandTypeDollarSignVariable: {
+	case kOperandTypeParamToken: {
 		uint literal = Datum(*_bytecode).u.i;
 		debugC(5, kDebugScript, "%d ", literal);
-		operand.putInteger(literal);
-		return operand;
+		returnValue.setToParamToken(literal);
+		return returnValue;
 	}
 
 	case kOperandTypeAssetId: {
 		uint assetId = Datum(*_bytecode).u.i;
 		debugC(5, kDebugScript, "%d ", assetId);
-		operand.putAsset(assetId);
-		return operand;
+		returnValue.setToAssetId(assetId);
+		return returnValue;
 	}
 
 	case kOperandTypeTime: {
 		double d = Datum(*_bytecode).u.f;
 		debugC(5, kDebugScript, "%f ", d);
-		operand.putDouble(d);
-		return operand;
+		returnValue.setToFloat(d);
+		return returnValue;
 	}
 
 	case kOperandTypeVariable: {
@@ -419,39 +419,39 @@ Operand CodeChunk::evaluateValue() {
 	case kOperandTypeFunctionId: {
 		uint functionId = Datum(*_bytecode).u.i;
 		debugC(5, kDebugScript, "%d ", functionId);
-		operand.putFunctionId(functionId);
-		return operand;
+		returnValue.setToFunctionId(functionId);
+		return returnValue;
 	}
 
 	case kOperandTypeMethodId: {
 		BuiltInMethod methodId = static_cast<BuiltInMethod>(Datum(*_bytecode).u.i);
 		debugC(5, kDebugScript, "%s ", builtInMethodToStr(methodId));
-		operand.putMethodId(methodId);
-		return operand;
+		returnValue.setToMethodId(methodId);
+		return returnValue;
 	}
 
 	default:
-		error("CodeChunk::getNextStatement(): Got unknown operand type %s (%d)", operandTypeToStr(operandType), static_cast<uint>(operandType));
+		error("Got unknown ScriptValue type %s (%d)", operandTypeToStr(operandType), static_cast<uint>(operandType));
 	}
 }
 
-Operand CodeChunk::evaluateVariable() {
+ScriptValue CodeChunk::evaluateVariable() {
 	uint32 id = Datum(*_bytecode).u.i;
 	VariableScope scope = static_cast<VariableScope>(Datum(*_bytecode).u.i);
 	debugC(5, kDebugScript, "Variable %d (%s)", id, variableScopeToStr(scope));
-	Operand variable = getVariable(id, scope);
+	ScriptValue variable = getVariable(id, scope);
 	return variable;
 }
 
-Operand CodeChunk::callFunction(uint functionId, uint parameterCount) {
-	Common::Array<Operand> args;
+ScriptValue CodeChunk::callFunction(uint functionId, uint parameterCount) {
+	Common::Array<ScriptValue> args;
 	for (uint i = 0; i < parameterCount; i++) {
 		debugCN(5, kDebugScript, "  Param %d: ", i);
-		Operand arg = evaluateExpression();
+		ScriptValue arg = evaluateExpression();
 		args.push_back(arg);
 	}
 
-	Operand returnValue;
+	ScriptValue returnValue;
 	Function *function = g_engine->getFunctionById(functionId);
 	if (function != nullptr) {
 		// This is a title-defined function.
@@ -466,10 +466,10 @@ Operand CodeChunk::callFunction(uint functionId, uint parameterCount) {
 	return returnValue;
 }
 
-Operand CodeChunk::getVariable(uint32 id, VariableScope scope) {
+ScriptValue CodeChunk::getVariable(uint32 id, VariableScope scope) {
 	switch (scope) {
 	case kVariableScopeGlobal: {
-		Operand returnValue(kOperandTypeVariable);
+		ScriptValue returnValue(kOperandTypeVariable);
 		Variable *variable = g_engine->_variables.getVal(id);
 		returnValue.putVariable(variable);
 		return returnValue;
@@ -493,7 +493,7 @@ Operand CodeChunk::getVariable(uint32 id, VariableScope scope) {
 	}
 }
 
-void CodeChunk::putVariable(uint32 id, VariableScope scope, Operand &value) {
+void CodeChunk::putVariable(uint32 id, VariableScope scope, ScriptValue &value) {
 	switch (scope) {
 	case kVariableScopeGlobal: {
 		Variable *variable = g_engine->_variables.getVal(id);
@@ -520,43 +520,43 @@ void CodeChunk::putVariable(uint32 id, VariableScope scope, Operand &value) {
 	}
 }
 
-Operand CodeChunk::callBuiltInMethod(BuiltInMethod method, Operand &self, Common::Array<Operand> &args) {
-	Operand literalSelf = self.getLiteralValue();
+ScriptValue CodeChunk::callBuiltInMethod(BuiltInMethod method, ScriptValue &self, Common::Array<ScriptValue> &args) {
+	ScriptValue literalSelf = self.getLiteralValue();
 	OperandType literalType = literalSelf.getType();
 	switch (literalType) {
 	case kOperandTypeAssetId: {
-		if (self.getAssetId() == 1) {
+		if (self.asAssetId() == 1) {
 			// This is a "document" method that we need to handle specially.
 			// The document (@doc) accepts engine-level methods like changing the
 			// active screen.
 			// HACK: This is so we don't have to implement a separate document class
 			// just to house these methods. Rather, we just call in the engine.
-			Operand returnValue = g_engine->callMethod(method, args);
+			ScriptValue returnValue = g_engine->callMethod(method, args);
 			return returnValue;
-		} else if (self.getAssetId() == 0) {
+		} else if (self.asAssetId() == 0) {
 			// It seems to be valid to call a method on a null asset ID, in
 			// which case nothing happens. Still issue warning for traceability.
 			warning("CodeChunk::callBuiltInMethod(): Attempt to call method on a null asset ID");
-			return Operand();
+			return ScriptValue();
 		} else {
 			// This is a regular asset that we can process directly.
 			Asset *selfAsset = self.getAsset();
 			if (selfAsset == nullptr) {
-				error("CodeChunk::callBuiltInMethod(): Attempt to call method on asset ID %d, which isn't loaded", self.getAssetId());
+				error("CodeChunk::callBuiltInMethod(): Attempt to call method on asset ID %d, which isn't loaded", self.asAssetId());
 			}
-			Operand returnValue = selfAsset->callMethod(method, args);
+			ScriptValue returnValue = selfAsset->callMethod(method, args);
 			return returnValue;
 		}
 	}
 
 	case kOperandTypeCollection: {
-		Common::SharedPtr<Collection> collection = literalSelf.getCollection();
-		Operand returnValue = collection->callMethod(method, args);
+		Common::SharedPtr<Collection> collection = literalSelf.asCollection();
+		ScriptValue returnValue = collection->callMethod(method, args);
 		return returnValue;
 	}
 
 	default:
-		error("CodeChunk::callBuiltInMethod(): Attempt to call method on unimplemented operand type %s (%d)",
+		error("CodeChunk::callBuiltInMethod(): Attempt to call method on unimplemented ScriptValue type %s (%d)",
 			operandTypeToStr(literalType), static_cast<uint>(literalType));
 	}
 }
diff --git a/engines/mediastation/mediascript/codechunk.h b/engines/mediastation/mediascript/codechunk.h
index 567bbc8e0e8..23606e7e642 100644
--- a/engines/mediastation/mediascript/codechunk.h
+++ b/engines/mediastation/mediascript/codechunk.h
@@ -27,7 +27,7 @@
 
 #include "mediastation/datafile.h"
 #include "mediastation/mediascript/variable.h"
-#include "mediastation/mediascript/operand.h"
+#include "mediastation/mediascript/scriptvalue.h"
 #include "mediastation/mediascript/scriptconstants.h"
 
 namespace MediaStation {
@@ -37,23 +37,23 @@ public:
 	CodeChunk(Common::SeekableReadStream &chunk);
 	~CodeChunk();
 
-	Operand execute(Common::Array<Operand> *args = nullptr, Common::Array<Operand> *locals = nullptr);
+	ScriptValue execute(Common::Array<ScriptValue> *args = nullptr, Common::Array<ScriptValue> *locals = nullptr);
 
-	static Operand callBuiltInMethod(BuiltInMethod method, Operand &self, Common::Array<Operand> &args);
+	static ScriptValue callBuiltInMethod(BuiltInMethod method, ScriptValue &self, Common::Array<ScriptValue> &args);
 
 private:
-	Operand evaluateExpression();
-	Operand evaluateOperation();
-	Operand evaluateValue();
-	Operand evaluateVariable();
+	ScriptValue evaluateExpression();
+	ScriptValue evaluateOperation();
+	ScriptValue evaluateValue();
+	ScriptValue evaluateVariable();
 
-	Operand callFunction(uint functionId, uint parameterCount);
-	Operand getVariable(uint32 id, VariableScope scope);
-	void putVariable(uint32 id, VariableScope scope, Operand &value);
+	ScriptValue callFunction(uint functionId, uint parameterCount);
+	ScriptValue getVariable(uint32 id, VariableScope scope);
+	void putVariable(uint32 id, VariableScope scope, ScriptValue &value);
 
 	bool _weOwnLocals = false;
-	Common::Array<Operand> *_locals = nullptr;
-	Common::Array<Operand> *_args = nullptr;
+	Common::Array<ScriptValue> *_locals = nullptr;
+	Common::Array<ScriptValue> *_args = nullptr;
 	Common::SeekableReadStream *_bytecode = nullptr;
 };
 
diff --git a/engines/mediastation/mediascript/eventhandler.cpp b/engines/mediastation/mediascript/eventhandler.cpp
index afb6692df0f..735c3d47ef2 100644
--- a/engines/mediastation/mediascript/eventhandler.cpp
+++ b/engines/mediastation/mediascript/eventhandler.cpp
@@ -42,14 +42,14 @@ EventHandler::EventHandler(Chunk &chunk) {
 	_code = new CodeChunk(chunk);
 }
 
-Operand EventHandler::execute(uint assetId) {
+ScriptValue EventHandler::execute(uint assetId) {
 	// TODO: The assetId is only passed in for debug visibility, there should be
 	// a better way to handle that.
 	debugC(5, kDebugScript, "\n********** EVENT HANDLER %s **********", getDebugHeader(assetId).c_str());
 
 	// The only argument that can be provided to an
 	// event handler is the _argumentValue.
-	Operand returnValue = _code->execute();
+	ScriptValue returnValue = _code->execute();
 
 	debugC(5, kDebugScript, "********** END EVENT HANDLER %s **********", getDebugHeader(assetId).c_str());
 	return returnValue;
diff --git a/engines/mediastation/mediascript/eventhandler.h b/engines/mediastation/mediascript/eventhandler.h
index 288ea417d34..6ab0ed18b79 100644
--- a/engines/mediastation/mediascript/eventhandler.h
+++ b/engines/mediastation/mediascript/eventhandler.h
@@ -36,7 +36,7 @@ public:
 	EventHandler(Chunk &chunk);
 	~EventHandler();
 
-	Operand execute(uint assetId);
+	ScriptValue execute(uint assetId);
 	EventType _type;
 	EventHandlerArgumentType _argumentType;
 	Datum _argumentValue;
diff --git a/engines/mediastation/mediascript/function.cpp b/engines/mediastation/mediascript/function.cpp
index 1d77892c0a8..babb8b7319a 100644
--- a/engines/mediastation/mediascript/function.cpp
+++ b/engines/mediastation/mediascript/function.cpp
@@ -41,9 +41,9 @@ Function::~Function() {
 	_code = nullptr;
 }
 
-Operand Function::execute(Common::Array<Operand> &args) {
+ScriptValue Function::execute(Common::Array<ScriptValue> &args) {
 	debugC(5, kDebugScript, "\n********** FUNCTION %d **********", _id);
-	Operand returnValue = _code->execute(&args);
+	ScriptValue returnValue = _code->execute(&args);
 	debugC(5, kDebugScript, "********** END FUNCTION **********");
 	return returnValue;
 }
diff --git a/engines/mediastation/mediascript/function.h b/engines/mediastation/mediascript/function.h
index 436f6968d2f..b567095133f 100644
--- a/engines/mediastation/mediascript/function.h
+++ b/engines/mediastation/mediascript/function.h
@@ -34,7 +34,7 @@ public:
 	Function(Chunk &chunk);
 	~Function();
 
-	Operand execute(Common::Array<Operand> &args);
+	ScriptValue execute(Common::Array<ScriptValue> &args);
 
 	uint _fileId;
 	uint _id;
diff --git a/engines/mediastation/mediascript/operand.cpp b/engines/mediastation/mediascript/operand.cpp
deleted file mode 100644
index 3518462aedc..00000000000
--- a/engines/mediastation/mediascript/operand.cpp
+++ /dev/null
@@ -1,583 +0,0 @@
-/* ScummVM - Graphic Adventure Engine
- *
- * ScummVM is the legal property of its developers, whose names
- * are too numerous to list here. Please refer to the COPYRIGHT
- * file distributed with this source distribution.
- *
- * This program is free software: you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation, either version 3 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program.  If not, see <http://www.gnu.org/licenses/>.
- *
- */
-
-#include "mediastation/mediastation.h"
-#include "mediastation/mediascript/operand.h"
-#include "mediastation/mediascript/function.h"
-
-namespace MediaStation {
-
-void Operand::putInteger(int i) {
-	switch (_type) {
-	case kOperandTypeBool:
-	case kOperandTypeInt:
-	case kOperandTypeDollarSignVariable: {
-		_u.i = i;
-		break;
-	}
-
-	case kOperandTypeVariable: {
-		_u.variable->_value.i = i;
-		break;
-	}
-
-	default:
-		error("Operand::putInteger(): Attempt to put integer into operand type %s (%d)",
-			operandTypeToStr(_type), static_cast<uint>(_type));
-	}
-}
-
-int Operand::getInteger() {
-	switch (_type) {
-	case kOperandTypeBool:
-	case kOperandTypeInt:
-	case kOperandTypeDollarSignVariable: {
-		return _u.i;
-	}
-
-	case kOperandTypeTime: {
-		return static_cast<int>(_u.d);
-	}
-
-	case kOperandTypeVariable: {
-		return _u.variable->_value.i;
-	}
-
-	default:
-		error("Operand::getInteger(): Attempt to get integer from operand type %s (%d)",
-			operandTypeToStr(_type), static_cast<uint>(_type));
-	}
-}
-
-void Operand::putDouble(double d) {
-	switch (_type) {
-	case kOperandTypeTime:
-	case kOperandTypeFloat: {
-		_u.d = d;
-		break;
-	}
-
-	case kOperandTypeVariable: {
-		// TODO: Add assertion.
-		_u.variable->_value.d = d;
-		break;
-	}
-
-	default:
-		error("Operand::putDouble(): Attempt to put double into operand type %s (%d)",
-			operandTypeToStr(_type), static_cast<uint>(_type));
-	}
-}
-
-double Operand::getDouble() {
-	switch (_type) {
-	case kOperandTypeTime:
-	case kOperandTypeFloat: {
-		return _u.d;
-	}
-
-	case kOperandTypeBool:
-	case kOperandTypeInt: {
-		return static_cast<double>(_u.i);
-	}
-
-	case kOperandTypeVariable: {
-		// TODO: Add assertion that this is the proper type.
-		return _u.variable->_value.d;
-	}
-
-	default:
-		error("Operand::getDouble(): Attempt to get double from operand type %s (%d)",
-			operandTypeToStr(_type), static_cast<uint>(_type));
-	}
-}
-
-void Operand::putString(Common::String *string) {
-	switch (_type) {
-	case kOperandTypeString: {
-		_u.string = string;
-		break;
-	}
-
-	case kOperandTypeVariable: {
-		assert(_u.variable->_type == kVariableTypeString);
-		_u.variable->_value.string = string;
-		break;
-	}
-
-	default:
-		error("Operand::putString(): Attempt to put string into operand type %s (%d)",
-			operandTypeToStr(_type), static_cast<uint>(_type));
-	}
-}
-
-Common::String *Operand::getString() {
-	switch (_type) {
-	case kOperandTypeString: {
-		return _u.string;
-	}
-
-	case kOperandTypeVariable: {
-		assert(_u.variable->_type == kVariableTypeString);
-		return _u.variable->_value.string;
-	}
-
-	default:
-		error("Operand::getString(): Attempt to get string from operand type %s (%d)",
-			operandTypeToStr(_type), static_cast<uint>(_type));
-	}
-}
-
-void Operand::putVariable(Variable *variable) {
-	switch (_type) {
-	case kOperandTypeVariable: {
-		_u.variable = variable;
-		break;
-	}
-
-	default:
-		error("Operand::putVariable(): Attempt to put variable into operand type %s (%d)",
-			operandTypeToStr(_type), static_cast<uint>(_type));
-	}
-}
-
-Variable *Operand::getVariable() {
-	switch (_type) {
-	case kOperandTypeVariable: {
-		return _u.variable;
-	}
-
-	default:
-		error("Operand::getVariable(): Attempt to get variable from operand type %s (%d)",
-			operandTypeToStr(_type), static_cast<uint>(_type));
-	}
-}
-
-void Operand::putFunctionId(uint functionId) {
-	switch (_type) {
-	case kOperandTypeFunctionId: {
-		_u.functionId = functionId;
-		break;
-	}
-
-	default:
-		error("Operand::putFunctionId(): Attempt to put function ID into operand type %s (%d)",
-			operandTypeToStr(_type), static_cast<uint>(_type));
-	}
-}
-
-uint Operand::getFunctionId() {
-	switch (_type) {
-	case kOperandTypeFunctionId: {
-		return _u.functionId;
-	}
-
-	case kOperandTypeVariable: {
-		assert(_u.variable->_type == kVariableTypeFunction);
-		return _u.variable->_value.functionId;
-	}
-
-	default:
-		error("Operand::getFunction(): Attempt to get function ID from operand type %s (%d)",
-			operandTypeToStr(_type), static_cast<uint>(_type));
-	}
-}
-
-void Operand::putMethodId(BuiltInMethod methodId) {
-	switch (_type) {
-	case kOperandTypeMethodId: {
-		_u.methodId = methodId;
-		break;
-	}
-
-	default:
-		error("Operand::putFunctionId(): Attempt to put method ID into operand type %s (%d)",
-			operandTypeToStr(_type), static_cast<uint>(_type));
-	}
-}
-
-BuiltInMethod Operand::getMethodId() {
-	switch (_type) {
-	case kOperandTypeMethodId: {
-		return _u.methodId;
-	}
-
-	default:
-		error("Operand::getFunction(): Attempt to get method ID from operand type %s (%d)",
-			operandTypeToStr(_type), static_cast<uint>(_type));
-	}
-}
-
-void Operand::putAsset(uint32 assetId) {
-	switch (_type) {
-	case kOperandTypeAssetId: {
-		_u.assetId = assetId;
-		break;
-	}
-
-	case kOperandTypeVariable: {
-		assert(_u.variable->_type == kVariableTypeAssetId);
-		_u.variable->_value.assetId = assetId;
-		break;
-	}
-
-	default:
-		error("Operand::putAsset(): Attempt to put asset ID into operand type %s (%d)",
-			operandTypeToStr(_type), static_cast<uint>(_type));
-	}
-}
-
-Asset *Operand::getAsset() {
-	switch (_type) {
-	case kOperandTypeAssetId: {
-		if (_u.assetId == 0) {
-			return nullptr;
-		} else {
-			return g_engine->getAssetById(_u.assetId);
-		}
-	}
-
-	case kOperandTypeVariable: {
-		assert(_u.variable->_type == kVariableTypeAssetId);
-		return g_engine->getAssetById(_u.variable->_value.assetId);
-	}
-
-	default:
-		error("Operand::getAsset(): Attempt to get asset from operand type %s (%d)",
-			operandTypeToStr(_type), static_cast<uint>(_type));
-	}
-}
-
-uint32 Operand::getAssetId() {
-	switch (_type) {
-	case kOperandTypeAssetId: {
-		return _u.assetId;
-	}
-
-	case kOperandTypeVariable: {
-		assert(_u.variable->_type == kVariableTypeAssetId);
-		return _u.variable->_value.assetId;
-	}
-
-	default:
-		error("Operand::getAssetId(): Attempt to get asset ID from operand type %s (%d)",
-			operandTypeToStr(_type), static_cast<uint>(_type));
-	}
-}
-
-void Operand::putCollection(Common::SharedPtr<Collection> collection) {
-	switch (_type) {
-	case kOperandTypeCollection: {
-		_collection = collection;
-		break;
-	}
-
-	case kOperandTypeVariable: {
-		assert(_u.variable->_type == kVariableTypeCollection);
-		_u.variable->_c = collection;
-		break;
-	}
-
-	default:
-		error("Operand::putCollection(): Attempt to put collection into operand type %s (%d)",
-			operandTypeToStr(_type), static_cast<uint>(_type));
-	}
-}
-
-Common::SharedPtr<Collection> Operand::getCollection() {
-	switch (_type) {
-	case kOperandTypeCollection: {
-		return _collection;
-	}
-
-	case kOperandTypeVariable: {
-		assert(_u.variable->_type == kVariableTypeCollection);
-		return _u.variable->_c;
-	}
-
-	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).
-	if (_type == kOperandTypeVariable) {
-		return _u.variable->getValue();
-	} else {
-		return *this;
-	}
-}
-
-bool Operand::operator==(const Operand &other) const {
-	Operand lhs = getLiteralValue();
-	Operand rhs = other.getLiteralValue();
-
-	if (lhs.isDouble() || rhs.isDouble()) {
-		// If either operand is a double, perform double comparison.
-		double lhsValue = lhs.isDouble() ? lhs.getDouble() : static_cast<double>(lhs.getInteger());
-		double rhsValue = rhs.isDouble() ? rhs.getDouble() : static_cast<double>(rhs.getInteger());
-		return lhsValue == rhsValue;
-	} else {
-		switch (lhs.getType()) {
-		case kOperandTypeBool:
-		case kOperandTypeInt:
-			return lhs.getInteger() == rhs.getInteger();
-
-		case kOperandTypeAssetId:
-			if (rhs.getType() == kOperandTypeInt) {
-				// This might happen if, for example, a given asset wasn't found
-				// in a collection and the script sets the return value to -1.
-				return static_cast<int>(lhs.getAssetId()) == rhs.getInteger();
-			} else {
-				// If the types are incompatiable, rhs will raise the error.
-				return lhs.getAssetId() == rhs.getAssetId();
-			}
-
-		case kOperandTypeString:
-			return *lhs.getString() == *rhs.getString();
-
-		default:
-			error("Operand::operator==(): Unimplemented operand types %s and %s", operandTypeToStr(lhs.getType()), operandTypeToStr(rhs.getType()));
-		}
-	}
-}
-
-bool Operand::operator<(const Operand &other) const {
-	Operand lhs = getLiteralValue();
-	Operand rhs = other.getLiteralValue();
-
-	if (!lhs.isNumber() || !rhs.isNumber()) {
-		error("Operand::operator<(): Unimplemented operand types %s and %s", operandTypeToStr(lhs.getType()), operandTypeToStr(rhs.getType()));
-	}
-
-	if (lhs.isDouble() || rhs.isDouble()) {
-		// If either operand is a double, perform double comparison.
-		double lhsValue = lhs.isDouble() ? lhs.getDouble() : static_cast<double>(lhs.getInteger());
-		double rhsValue = rhs.isDouble() ? rhs.getDouble() : static_cast<double>(rhs.getInteger());
-		return lhsValue < rhsValue;
-	} else {
-		// Otherwise, perform integer comparison.
-		return lhs.getInteger() < rhs.getInteger();
-	}
-}
-
-bool Operand::operator>(const Operand &other) const {
-	Operand lhs = getLiteralValue();
-	Operand rhs = other.getLiteralValue();
-
-	if (!lhs.isNumber() || !rhs.isNumber()) {
-		error("Operand::operator>(): Unimplemented operand types %s and %s", operandTypeToStr(lhs.getType()), operandTypeToStr(rhs.getType()));
-	}
-
-	if (lhs.isDouble() || rhs.isDouble()) {
-		// If either operand is a double, perform double comparison.
-		double lhsValue = lhs.isDouble() ? lhs.getDouble() : static_cast<double>(lhs.getInteger());
-		double rhsValue = rhs.isDouble() ? rhs.getDouble() : static_cast<double>(rhs.getInteger());
-		return lhsValue > rhsValue;
-	} else {
-		// Otherwise, perform integer comparison.
-		return lhs.getInteger() > rhs.getInteger();
-	}
-}
-
-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 kOperandTypeBool:
-	case kOperandTypeInt:
-		return lhs.getInteger() || rhs.getInteger();
-
-	default:
-		error("Operand::operator||(): Unimplemented operand types %s and %s", operandTypeToStr(lhs.getType()), operandTypeToStr(rhs.getType()));
-	}
-}
-
-bool Operand::operator!() const {
-	Operand literalValue = getLiteralValue();
-
-	// If the types being compared end up being incompatible, the respective get
-	// method will raise the error.
-	switch (literalValue.getType()) {
-	case kOperandTypeBool:
-	case kOperandTypeInt:
-		return !literalValue.getInteger();
-
-	default:
-		error("Operand::operator!(): Unimplemented operand type %s", operandTypeToStr(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 will raise the error.
-	switch (lhs.getType()) {
-	case kOperandTypeBool:
-	case kOperandTypeInt:
-		return lhs.getInteger() && rhs.getInteger();
-
-	default:
-		error("Operand::operator&&(): Unimplemented 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 (!lhs.isNumber() || !rhs.isNumber()) {
-		error("Operand::operator+(): Unimplemented operand types %s and %s", operandTypeToStr(lhs.getType()), operandTypeToStr(rhs.getType()));
-	}
-
-	if (lhs.isDouble() || rhs.isDouble()) {
-		// If either operand is a double, perform double addition.
-		double lhsValue = lhs.isDouble() ? lhs.getDouble() : static_cast<double>(lhs.getInteger());
-		double rhsValue = rhs.isDouble() ? rhs.getDouble() : static_cast<double>(rhs.getInteger());
-		returnValue.putDouble(lhsValue + rhsValue);
-	} else {
-		// Otherwise, perform integer addition.
-		returnValue.putInteger(lhs.getInteger() + rhs.getInteger());
-	}
-
-	return returnValue;
-}
-
-Operand Operand::operator-(const Operand &other) const {
-	Operand lhs = getLiteralValue();
-	Operand rhs = other.getLiteralValue();
-	Operand returnValue(lhs.getType());
-
-	if (!lhs.isNumber() || !rhs.isNumber()) {
-		error("Operand::operator-(): Unimplemented operand types %s and %s", operandTypeToStr(lhs.getType()), operandTypeToStr(rhs.getType()));
-	}
-
-	if (lhs.isDouble() || rhs.isDouble()) {
-		// If either operand is a double, perform double subtraction.
-		double lhsValue = lhs.isDouble() ? lhs.getDouble() : static_cast<double>(lhs.getInteger());
-		double rhsValue = rhs.isDouble() ? rhs.getDouble() : static_cast<double>(rhs.getInteger());
-		returnValue.putDouble(lhsValue - rhsValue);
-	} else {
-		// Otherwise, perform integer subtraction.
-		returnValue.putInteger(lhs.getInteger() - rhs.getInteger());
-	}
-
-	return returnValue;
-}
-
-Operand Operand::operator*(const Operand &other) const {
-	Operand lhs = getLiteralValue();
-	Operand rhs = other.getLiteralValue();
-	Operand returnValue(lhs.getType());
-
-	if (!lhs.isNumber() || !rhs.isNumber()) {
-		error("Operand::operator*(): Unimplemented operand types %s and %s", operandTypeToStr(lhs.getType()), operandTypeToStr(rhs.getType()));
-	}
-
-	if (lhs.isDouble() || rhs.isDouble()) {
-		// If either operand is a double, perform double multiplication.
-		double lhsValue = lhs.isDouble() ? lhs.getDouble() : static_cast<double>(lhs.getInteger());
-		double rhsValue = rhs.isDouble() ? rhs.getDouble() : static_cast<double>(rhs.getInteger());
-		returnValue.putDouble(lhsValue * rhsValue);
-	} else {
-		// Otherwise, perform integer subtraction.
-		returnValue.putInteger(lhs.getInteger() * rhs.getInteger());
-	}
-
-	return returnValue;
-}
-
-Operand Operand::operator/(const Operand &other) const {
-	Operand lhs = getLiteralValue();
-	Operand rhs = other.getLiteralValue();
-	Operand returnValue(lhs.getType());
-
-	if (!lhs.isNumber() || !rhs.isNumber()) {
-		error("Operand::operator/(): Unimplemented operand types %s and %s", operandTypeToStr(lhs.getType()), operandTypeToStr(rhs.getType()));
-	}
-
-	if (lhs.isDouble() || rhs.isDouble()) {
-		// If either operand is a double, perform double division.
-		double lhsValue = lhs.isDouble() ? lhs.getDouble() : static_cast<double>(lhs.getInteger());
-		double rhsValue = rhs.isDouble() ? rhs.getDouble() : static_cast<double>(rhs.getInteger());
-		returnValue.putDouble(lhsValue / rhsValue);
-	} else {
-		// Otherwise, perform integer division.
-		returnValue.putInteger(lhs.getInteger() / rhs.getInteger());
-	}
-
-	return returnValue;
-
-}
-
-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 kOperandTypeBool:
-	case kOperandTypeInt:
-		if (rhs.getInteger() == 0) {
-			error("Operand::operator%%(): Attempted mod by zero");
-		}
-		returnValue.putInteger(lhs.getInteger() % rhs.getInteger());
-		return returnValue;
-
-	default:
-		error("Operand::operator/(): Unimplemented operand types %s and %s", operandTypeToStr(lhs.getType()), operandTypeToStr(rhs.getType()));
-	}
-}
-
-Operand Operand::operator-() const {
-	Operand literalValue = getLiteralValue();
-	Operand returnValue(literalValue.getType());
-
-	// If the types being compared end up being incompatible, the respective get
-	// method on the rhs will raise the error.
-	switch (literalValue.getType()) {
-	case kOperandTypeBool:
-	case kOperandTypeInt:
-		returnValue.putInteger(-literalValue.getInteger());
-		return returnValue;
-
-	case kOperandTypeTime:
-	case kOperandTypeFloat:
-		returnValue.putDouble(-literalValue.getDouble());
-		return returnValue;
-
-	default:
-		error("Operand::operator-(): Unimplemented operand type %s", operandTypeToStr(literalValue.getType()));
-	}
-}
-
-} // End of namespace MediaStation
diff --git a/engines/mediastation/mediascript/scriptconstants.cpp b/engines/mediastation/mediascript/scriptconstants.cpp
index faaa4e3690a..714fc08f105 100644
--- a/engines/mediastation/mediascript/scriptconstants.cpp
+++ b/engines/mediastation/mediascript/scriptconstants.cpp
@@ -326,7 +326,7 @@ const char *operandTypeToStr(OperandType type) {
 		return "Int";
 	case kOperandTypeString:
 		return "String";
-	case kOperandTypeDollarSignVariable:
+	case kOperandTypeParamToken:
 		return "DollarSignVariable";
 	case kOperandTypeAssetId:
 		return "AssetId";
@@ -345,26 +345,28 @@ const char *operandTypeToStr(OperandType type) {
 	}
 }
 
-const char *variableTypeToStr(VariableType type) {
+const char *scriptValueTypeToStr(ScriptValueType type) {
 	switch (type) {
-	case kVariableTypeEmpty:
+	case kScriptValueTypeEmpty:
 		return "Empty";
-	case kVariableTypeFunction:
-		return "Function";
-	case kVariableTypeCollection:
-		return "Collection";
-	case kVariableTypeString:
-		return "String";
-	case kVariableTypeAssetId:
-		return "AssetId";
-	case kVariableTypeInt:
+	case kScriptValueTypeFloat:
+		return "Float";
+	case kScriptValueTypeBool:
+		return "Bool";
+	case kScriptValueTypeTime:
+		return "Time";
+	case kScriptValueTypeParamToken:
 		return "Int";
-	case kVariableTypeUnk2:
-		return "Unknown2";
-	case kVariableTypeBoolean:
-		return "Boolean";
-	case kVariableTypeFloat:
-		return "Literal";
+	case kScriptValueTypeAssetId:
+		return "AssetId";
+	case kScriptValueTypeString:
+		return "String";
+	case kScriptValueTypeCollection:
+		return "Collection";
+	case kScriptValueTypeFunctionId:
+		return "FunctionId";
+	case kScriptValueTypeMethodId:
+		return "MethodId";
 	default:
 		return "UNKNOWN";
 	}
diff --git a/engines/mediastation/mediascript/scriptconstants.h b/engines/mediastation/mediascript/scriptconstants.h
index 6867ff28ed1..0c6e3b300eb 100644
--- a/engines/mediastation/mediascript/scriptconstants.h
+++ b/engines/mediastation/mediascript/scriptconstants.h
@@ -242,7 +242,7 @@ enum OperandType {
 	kOperandTypeFloat = 152,
 	kOperandTypeInt = 153,
 	kOperandTypeString = 154,
-	kOperandTypeDollarSignVariable = 155,
+	kOperandTypeParamToken = 155,
 	kOperandTypeAssetId = 156,
 	kOperandTypeTime = 157,
 	kOperandTypeVariable = 158,
@@ -252,25 +252,19 @@ enum OperandType {
 };
 const char *operandTypeToStr(OperandType type);
 
-enum VariableType {
-	kVariableTypeEmpty = 0x0000,
-	kVariableTypeFunction = 0x0008,
-	kVariableTypeCollection = 0x0007,
-	kVariableTypeString = 0x0006,
-	kVariableTypeAssetId = 0x0005,
-	kVariableTypeInt = 0x0004,
-	// These seem to be constants of some sort? This is what some of these
-	// IDs look like in PROFILE._ST:
-	//  - $downEar 10026
-	//  - $sitDown 10027
-	// Seems like these can also reference variables:
-	//  - var_6c14_bool_FirstThingLev3 315
-	//  - var_6c14_NextEncouragementSound 316
-	kVariableTypeUnk2 = 0x0003,
-	kVariableTypeBoolean = 0x0002,
-	kVariableTypeFloat = 0x0001
+enum ScriptValueType {
+	kScriptValueTypeEmpty = 0,
+	kScriptValueTypeFloat = 1,
+	kScriptValueTypeBool = 2,
+	kScriptValueTypeTime = 3,
+	kScriptValueTypeParamToken = 4,
+	kScriptValueTypeAssetId = 5,
+	kScriptValueTypeString = 6,
+	kScriptValueTypeCollection = 7,
+	kScriptValueTypeFunctionId = 8,
+	kScriptValueTypeMethodId = 9
 };
-const char *variableTypeToStr(VariableType type);
+const char *scriptValueTypeToStr(ScriptValueType type);
 
 } // End of namespace MediaStation
 
diff --git a/engines/mediastation/mediascript/scriptvalue.cpp b/engines/mediastation/mediascript/scriptvalue.cpp
new file mode 100644
index 00000000000..cdc130ac9fb
--- /dev/null
+++ b/engines/mediastation/mediascript/scriptvalue.cpp
@@ -0,0 +1,583 @@
+/* ScummVM - Graphic Adventure Engine
+ *
+ * ScummVM is the legal property of its developers, whose names
+ * are too numerous to list here. Please refer to the COPYRIGHT
+ * file distributed with this source distribution.
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#include "mediastation/mediastation.h"
+#include "mediastation/mediascript/scriptvalue.h"
+#include "mediastation/mediascript/function.h"
+
+namespace MediaStation {
+
+void ScriptValue::setToParamToken(int i) {
+	switch (_type) {
+	case kOperandTypeBool:
+	case kOperandTypeInt:
+	case kOperandTypeParamToken: {
+		_u.i = i;
+		break;
+	}
+
+	case kOperandTypeVariable: {
+		_u.variable->_value.i = i;
+		break;
+	}
+
+	default:
+		error("ScriptValue::putInteger(): Attempt to put integer into ScriptValue type %s (%d)",
+			operandTypeToStr(_type), static_cast<uint>(_type));
+	}
+}
+
+int ScriptValue::asParamToken() {
+	switch (_type) {
+	case kOperandTypeBool:
+	case kOperandTypeInt:
+	case kOperandTypeParamToken: {
+		return _u.i;
+	}
+
+	case kOperandTypeTime: {
+		return static_cast<int>(_u.d);
+	}
+
+	case kOperandTypeVariable: {
+		return _u.variable->_value.i;
+	}
+
+	default:
+		error("ScriptValue::getInteger(): Attempt to get integer from ScriptValue type %s (%d)",
+			operandTypeToStr(_type), static_cast<uint>(_type));
+	}
+}
+
+void ScriptValue::setToFloat(double d) {
+	switch (_type) {
+	case kOperandTypeTime:
+	case kOperandTypeFloat: {
+		_u.d = d;
+		break;
+	}
+
+	case kOperandTypeVariable: {
+		// TODO: Add assertion.
+		_u.variable->_value.d = d;
+		break;
+	}
+
+	default:
+		error("ScriptValue::setToFloat(): Attempt to put double into ScriptValue type %s (%d)",
+			operandTypeToStr(_type), static_cast<uint>(_type));
+	}
+}
+
+double ScriptValue::asFloat() {
+	switch (_type) {
+	case kOperandTypeTime:
+	case kOperandTypeFloat: {
+		return _u.d;
+	}
+
+	case kOperandTypeBool:
+	case kOperandTypeInt: {
+		return static_cast<double>(_u.i);
+	}
+
+	case kOperandTypeVariable: {
+		// TODO: Add assertion that this is the proper type.
+		return _u.variable->_value.d;
+	}
+
+	default:
+		error("ScriptValue::asFloat(): Attempt to get double from ScriptValue type %s (%d)",
+			operandTypeToStr(_type), static_cast<uint>(_type));
+	}
+}
+
+void ScriptValue::setToString(Common::String *string) {
+	switch (_type) {
+	case kOperandTypeString: {
+		_u.string = string;
+		break;
+	}
+
+	case kOperandTypeVariable: {
+		assert(_u.variable->_type == kScriptValueTypeString);
+		_u.variable->_value.string = string;
+		break;
+	}
+
+	default:
+		error("ScriptValue::setToString(): Attempt to put string into ScriptValue type %s (%d)",
+			operandTypeToStr(_type), static_cast<uint>(_type));
+	}
+}
+
+Common::String *ScriptValue::asString() {
+	switch (_type) {
+	case kOperandTypeString: {
+		return _u.string;
+	}
+
+	case kOperandTypeVariable: {
+		assert(_u.variable->_type == kScriptValueTypeString);
+		return _u.variable->_value.string;
+	}
+
+	default:
+		error("ScriptValue::asString(): Attempt to get string from ScriptValue type %s (%d)",
+			operandTypeToStr(_type), static_cast<uint>(_type));
+	}
+}
+
+void ScriptValue::putVariable(Variable *variable) {
+	switch (_type) {
+	case kOperandTypeVariable: {
+		_u.variable = variable;
+		break;
+	}
+
+	default:
+		error("ScriptValue::putVariable(): Attempt to put variable into ScriptValue type %s (%d)",
+			operandTypeToStr(_type), static_cast<uint>(_type));
+	}
+}
+
+Variable *ScriptValue::getVariable() {
+	switch (_type) {
+	case kOperandTypeVariable: {
+		return _u.variable;
+	}
+
+	default:
+		error("ScriptValue::getVariable(): Attempt to get variable from ScriptValue type %s (%d)",
+			operandTypeToStr(_type), static_cast<uint>(_type));
+	}
+}
+
+void ScriptValue::setToFunctionId(uint functionId) {
+	switch (_type) {
+	case kOperandTypeFunctionId: {
+		_u.functionId = functionId;
+		break;
+	}
+
+	default:
+		error("ScriptValue::setToFunctionId(): Attempt to put function ID into ScriptValue type %s (%d)",
+			operandTypeToStr(_type), static_cast<uint>(_type));
+	}
+}
+
+uint ScriptValue::asFunctionId() {
+	switch (_type) {
+	case kOperandTypeFunctionId: {
+		return _u.functionId;
+	}
+
+	case kOperandTypeVariable: {
+		assert(_u.variable->_type == kScriptValueTypeFunctionId);
+		return _u.variable->_value.functionId;
+	}
+
+	default:
+		error("ScriptValue::getFunction(): Attempt to get function ID from ScriptValue type %s (%d)",
+			operandTypeToStr(_type), static_cast<uint>(_type));
+	}
+}
+
+void ScriptValue::setToMethodId(BuiltInMethod methodId) {
+	switch (_type) {
+	case kOperandTypeMethodId: {
+		_u.methodId = methodId;
+		break;
+	}
+
+	default:
+		error("ScriptValue::setToFunctionId(): Attempt to put method ID into ScriptValue type %s (%d)",
+			operandTypeToStr(_type), static_cast<uint>(_type));
+	}
+}
+
+BuiltInMethod ScriptValue::asMethodId() {
+	switch (_type) {
+	case kOperandTypeMethodId: {
+		return _u.methodId;
+	}
+
+	default:
+		error("ScriptValue::getFunction(): Attempt to get method ID from ScriptValue type %s (%d)",
+			operandTypeToStr(_type), static_cast<uint>(_type));
+	}
+}
+
+void ScriptValue::setToAssetId(uint32 assetId) {
+	switch (_type) {
+	case kOperandTypeAssetId: {
+		_u.assetId = assetId;
+		break;
+	}
+
+	case kOperandTypeVariable: {
+		assert(_u.variable->_type == kScriptValueTypeAssetId);
+		_u.variable->_value.assetId = assetId;
+		break;
+	}
+
+	default:
+		error("ScriptValue::setToAssetId(): Attempt to put asset ID into ScriptValue type %s (%d)",
+			operandTypeToStr(_type), static_cast<uint>(_type));
+	}
+}
+
+Asset *ScriptValue::getAsset() {
+	switch (_type) {
+	case kOperandTypeAssetId: {
+		if (_u.assetId == 0) {
+			return nullptr;
+		} else {
+			return g_engine->getAssetById(_u.assetId);
+		}
+	}
+
+	case kOperandTypeVariable: {
+		assert(_u.variable->_type == kScriptValueTypeAssetId);
+		return g_engine->getAssetById(_u.variable->_value.assetId);
+	}
+
+	default:
+		error("ScriptValue::getAsset(): Attempt to get asset from ScriptValue type %s (%d)",
+			operandTypeToStr(_type), static_cast<uint>(_type));
+	}
+}
+
+uint32 ScriptValue::asAssetId() {
+	switch (_type) {
+	case kOperandTypeAssetId: {
+		return _u.assetId;
+	}
+
+	case kOperandTypeVariable: {
+		assert(_u.variable->_type == kScriptValueTypeAssetId);
+		return _u.variable->_value.assetId;
+	}
+
+	default:
+		error("ScriptValue::getAssetId(): Attempt to get asset ID from ScriptValue type %s (%d)",
+			operandTypeToStr(_type), static_cast<uint>(_type));
+	}
+}
+
+void ScriptValue::setToCollection(Common::SharedPtr<Collection> collection) {
+	switch (_type) {
+	case kOperandTypeCollection: {
+		_collection = collection;
+		break;
+	}
+
+	case kOperandTypeVariable: {
+		assert(_u.variable->_type == kScriptValueTypeCollection);
+		_u.variable->_c = collection;
+		break;
+	}
+
+	default:
+		error("ScriptValue::setToCollection(): Attempt to put collection into ScriptValue type %s (%d)",
+			operandTypeToStr(_type), static_cast<uint>(_type));
+	}
+}
+
+Common::SharedPtr<Collection> ScriptValue::asCollection() {
+	switch (_type) {
+	case kOperandTypeCollection: {
+		return _collection;
+	}
+
+	case kOperandTypeVariable: {
+		assert(_u.variable->_type == kScriptValueTypeCollection);
+		return _u.variable->_c;
+	}
+
+	default:
+		error("ScriptValue::asCollection(): Attempt to get collection from ScriptValue type %s (%d)",
+			operandTypeToStr(_type), static_cast<uint>(_type));
+	}
+}
+
+ScriptValue ScriptValue::getLiteralValue() const {
+	// This function dereferences any variable to get the actual
+	// "direct" value (a literal asset ID or otherwise).
+	if (_type == kOperandTypeVariable) {
+		return _u.variable->getValue();
+	} else {
+		return *this;
+	}
+}
+
+bool ScriptValue::operator==(const ScriptValue &other) const {
+	ScriptValue lhs = getLiteralValue();
+	ScriptValue rhs = other.getLiteralValue();
+
+	if (lhs.isDouble() || rhs.isDouble()) {
+		// If either ScriptValue is a double, perform double comparison.
+		double lhsValue = lhs.isDouble() ? lhs.asFloat() : static_cast<double>(lhs.asParamToken());
+		double rhsValue = rhs.isDouble() ? rhs.asFloat() : static_cast<double>(rhs.asParamToken());
+		return lhsValue == rhsValue;
+	} else {
+		switch (lhs.getType()) {
+		case kOperandTypeBool:
+		case kOperandTypeInt:
+			return lhs.asParamToken() == rhs.asParamToken();
+
+		case kOperandTypeAssetId:
+			if (rhs.getType() == kOperandTypeInt) {
+				// This might happen if, for example, a given asset wasn't found
+				// in a collection and the script sets the return value to -1.
+				return static_cast<int>(lhs.asAssetId()) == rhs.asParamToken();
+			} else {
+				// If the types are incompatiable, rhs will raise the error.
+				return lhs.asAssetId() == rhs.asAssetId();
+			}
+
+		case kOperandTypeString:
+			return *lhs.asString() == *rhs.asString();
+
+		default:
+			error("ScriptValue::operator==(): Unimplemented ScriptValue types %s and %s", operandTypeToStr(lhs.getType()), operandTypeToStr(rhs.getType()));
+		}
+	}
+}
+
+bool ScriptValue::operator<(const ScriptValue &other) const {
+	ScriptValue lhs = getLiteralValue();
+	ScriptValue rhs = other.getLiteralValue();
+
+	if (!lhs.isNumber() || !rhs.isNumber()) {
+		error("ScriptValue::operator<(): Unimplemented ScriptValue types %s and %s", operandTypeToStr(lhs.getType()), operandTypeToStr(rhs.getType()));
+	}
+
+	if (lhs.isDouble() || rhs.isDouble()) {
+		// If either ScriptValue is a double, perform double comparison.
+		double lhsValue = lhs.isDouble() ? lhs.asFloat() : static_cast<double>(lhs.asParamToken());
+		double rhsValue = rhs.isDouble() ? rhs.asFloat() : static_cast<double>(rhs.asParamToken());
+		return lhsValue < rhsValue;
+	} else {
+		// Otherwise, perform integer comparison.
+		return lhs.asParamToken() < rhs.asParamToken();
+	}
+}
+
+bool ScriptValue::operator>(const ScriptValue &other) const {
+	ScriptValue lhs = getLiteralValue();
+	ScriptValue rhs = other.getLiteralValue();
+
+	if (!lhs.isNumber() || !rhs.isNumber()) {
+		error("ScriptValue::operator>(): Unimplemented ScriptValue types %s and %s", operandTypeToStr(lhs.getType()), operandTypeToStr(rhs.getType()));
+	}
+
+	if (lhs.isDouble() || rhs.isDouble()) {
+		// If either ScriptValue is a double, perform double comparison.
+		double lhsValue = lhs.isDouble() ? lhs.asFloat() : static_cast<double>(lhs.asParamToken());
+		double rhsValue = rhs.isDouble() ? rhs.asFloat() : static_cast<double>(rhs.asParamToken());
+		return lhsValue > rhsValue;
+	} else {
+		// Otherwise, perform integer comparison.
+		return lhs.asParamToken() > rhs.asParamToken();
+	}
+}
+
+bool ScriptValue::operator||(const ScriptValue &other) const {
+	ScriptValue lhs = getLiteralValue();
+	ScriptValue 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 kOperandTypeBool:
+	case kOperandTypeInt:
+		return lhs.asParamToken() || rhs.asParamToken();
+
+	default:
+		error("ScriptValue::operator||(): Unimplemented ScriptValue types %s and %s", operandTypeToStr(lhs.getType()), operandTypeToStr(rhs.getType()));
+	}
+}
+
+bool ScriptValue::operator!() const {
+	ScriptValue literalValue = getLiteralValue();
+
+	// If the types being compared end up being incompatible, the respective get
+	// method will raise the error.
+	switch (literalValue.getType()) {
+	case kOperandTypeBool:
+	case kOperandTypeInt:
+		return !literalValue.asParamToken();
+
+	default:
+		error("ScriptValue::operator!(): Unimplemented ScriptValue type %s", operandTypeToStr(literalValue.getType()));
+	}
+}
+
+bool ScriptValue::operator&&(const ScriptValue &other) const {
+	ScriptValue lhs = getLiteralValue();
+	ScriptValue rhs = other.getLiteralValue();
+
+	// If the types being compared end up being incompatible, the respective get
+	// method will raise the error.
+	switch (lhs.getType()) {
+	case kOperandTypeBool:
+	case kOperandTypeInt:
+		return lhs.asParamToken() && rhs.asParamToken();
+
+	default:
+		error("ScriptValue::operator&&(): Unimplemented ScriptValue types %s and %s", operandTypeToStr(lhs.getType()), operandTypeToStr(rhs.getType()));
+	}
+}
+
+ScriptValue ScriptValue::operator+(const ScriptValue &other) const {
+	ScriptValue lhs = getLiteralValue();
+	ScriptValue rhs = other.getLiteralValue();
+	ScriptValue returnValue(lhs.getType());
+
+	if (!lhs.isNumber() || !rhs.isNumber()) {
+		error("ScriptValue::operator+(): Unimplemented ScriptValue types %s and %s", operandTypeToStr(lhs.getType()), operandTypeToStr(rhs.getType()));
+	}
+
+	if (lhs.isDouble() || rhs.isDouble()) {
+		// If either ScriptValue is a double, perform double addition.
+		double lhsValue = lhs.isDouble() ? lhs.asFloat() : static_cast<double>(lhs.asParamToken());
+		double rhsValue = rhs.isDouble() ? rhs.asFloat() : static_cast<double>(rhs.asParamToken());
+		returnValue.setToFloat(lhsValue + rhsValue);
+	} else {
+		// Otherwise, perform integer addition.
+		returnValue.setToParamToken(lhs.asParamToken() + rhs.asParamToken());
+	}
+
+	return returnValue;
+}
+
+ScriptValue ScriptValue::operator-(const ScriptValue &other) const {
+	ScriptValue lhs = getLiteralValue();
+	ScriptValue rhs = other.getLiteralValue();
+	ScriptValue returnValue(lhs.getType());
+
+	if (!lhs.isNumber() || !rhs.isNumber()) {
+		error("ScriptValue::operator-(): Unimplemented ScriptValue types %s and %s", operandTypeToStr(lhs.getType()), operandTypeToStr(rhs.getType()));
+	}
+
+	if (lhs.isDouble() || rhs.isDouble()) {
+		// If either ScriptValue is a double, perform double subtraction.
+		double lhsValue = lhs.isDouble() ? lhs.asFloat() : static_cast<double>(lhs.asParamToken());
+		double rhsValue = rhs.isDouble() ? rhs.asFloat() : static_cast<double>(rhs.asParamToken());
+		returnValue.setToFloat(lhsValue - rhsValue);
+	} else {
+		// Otherwise, perform integer subtraction.
+		returnValue.setToParamToken(lhs.asParamToken() - rhs.asParamToken());
+	}
+
+	return returnValue;
+}
+
+ScriptValue ScriptValue::operator*(const ScriptValue &other) const {
+	ScriptValue lhs = getLiteralValue();
+	ScriptValue rhs = other.getLiteralValue();
+	ScriptValue returnValue(lhs.getType());
+
+	if (!lhs.isNumber() || !rhs.isNumber()) {
+		error("ScriptValue::operator*(): Unimplemented ScriptValue types %s and %s", operandTypeToStr(lhs.getType()), operandTypeToStr(rhs.getType()));
+	}
+
+	if (lhs.isDouble() || rhs.isDouble()) {
+		// If either ScriptValue is a double, perform double multiplication.
+		double lhsValue = lhs.isDouble() ? lhs.asFloat() : static_cast<double>(lhs.asParamToken());
+		double rhsValue = rhs.isDouble() ? rhs.asFloat() : static_cast<double>(rhs.asParamToken());
+		returnValue.setToFloat(lhsValue * rhsValue);
+	} else {
+		// Otherwise, perform integer subtraction.
+		returnValue.setToParamToken(lhs.asParamToken() * rhs.asParamToken());
+	}
+
+	return returnValue;
+}
+
+ScriptValue ScriptValue::operator/(const ScriptValue &other) const {
+	ScriptValue lhs = getLiteralValue();
+	ScriptValue rhs = other.getLiteralValue();
+	ScriptValue returnValue(lhs.getType());
+
+	if (!lhs.isNumber() || !rhs.isNumber()) {
+		error("ScriptValue::operator/(): Unimplemented ScriptValue types %s and %s", operandTypeToStr(lhs.getType()), operandTypeToStr(rhs.getType()));
+	}
+
+	if (lhs.isDouble() || rhs.isDouble()) {
+		// If either ScriptValue is a double, perform double division.
+		double lhsValue = lhs.isDouble() ? lhs.asFloat() : static_cast<double>(lhs.asParamToken());
+		double rhsValue = rhs.isDouble() ? rhs.asFloat() : static_cast<double>(rhs.asParamToken());
+		returnValue.setToFloat(lhsValue / rhsValue);
+	} else {
+		// Otherwise, perform integer division.
+		returnValue.setToParamToken(lhs.asParamToken() / rhs.asParamToken());
+	}
+
+	return returnValue;
+
+}
+
+ScriptValue ScriptValue::operator%(const ScriptValue &other) const {
+	ScriptValue lhs = getLiteralValue();
+	ScriptValue rhs = other.getLiteralValue();
+	ScriptValue 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 kOperandTypeBool:
+	case kOperandTypeInt:
+		if (rhs.asParamToken() == 0) {
+			error("ScriptValue::operator%%(): Attempted mod by zero");
+		}
+		returnValue.setToParamToken(lhs.asParamToken() % rhs.asParamToken());
+		return returnValue;
+
+	default:
+		error("ScriptValue::operator/(): Unimplemented ScriptValue types %s and %s", operandTypeToStr(lhs.getType()), operandTypeToStr(rhs.getType()));
+	}
+}
+
+ScriptValue ScriptValue::operator-() const {
+	ScriptValue literalValue = getLiteralValue();
+	ScriptValue returnValue(literalValue.getType());
+
+	// If the types being compared end up being incompatible, the respective get
+	// method on the rhs will raise the error.
+	switch (literalValue.getType()) {
+	case kOperandTypeBool:
+	case kOperandTypeInt:
+		returnValue.setToParamToken(-literalValue.asParamToken());
+		return returnValue;
+
+	case kOperandTypeTime:
+	case kOperandTypeFloat:
+		returnValue.setToFloat(-literalValue.asFloat());
+		return returnValue;
+
+	default:
+		error("ScriptValue::operator-(): Unimplemented ScriptValue type %s", operandTypeToStr(literalValue.getType()));
+	}
+}
+
+} // End of namespace MediaStation
diff --git a/engines/mediastation/mediascript/operand.h b/engines/mediastation/mediascript/scriptvalue.h
similarity index 56%
rename from engines/mediastation/mediascript/operand.h
rename to engines/mediastation/mediascript/scriptvalue.h
index 2059b25dc70..2d2e27b41a3 100644
--- a/engines/mediastation/mediascript/operand.h
+++ b/engines/mediastation/mediascript/scriptvalue.h
@@ -19,8 +19,8 @@
  *
  */
 
-#ifndef MEDIASTATION_MEDIASCRIPT_OPERAND_H
-#define MEDIASTATION_MEDIASCRIPT_OPERAND_H
+#ifndef MEDIASTATION_MEDIASCRIPT_SCRIPTVALUE_H
+#define MEDIASTATION_MEDIASCRIPT_SCRIPTVALUE_H
 
 #include "common/ptr.h"
 #include "common/str.h"
@@ -32,56 +32,54 @@ namespace MediaStation {
 
 class Asset;
 
-class Operand {
+class ScriptValue {
 public:
-	Operand() : _type(kOperandTypeEmpty) {}
-	Operand(OperandType type) : _type(type) {}
+	ScriptValue() : _type(kOperandTypeEmpty) {}
+	ScriptValue(OperandType type) : _type(type) {}
 
-	OperandType getType() const {
-		return _type;
-	}
+	OperandType getType() const { return _type; }
 
-	void putInteger(int i);
-	int getInteger();
+	void setToParamToken(int i);
+	int asParamToken();
 
-	void putDouble(double d);
-	double getDouble();
+	void setToFloat(double d);
+	double asFloat();
 
-	void putString(Common::String *string);
-	Common::String *getString();
+	void setToString(Common::String *string);
+	Common::String *asString();
 
 	void putVariable(Variable *variable);
 	Variable *getVariable();
 
-	void putFunctionId(uint functionId);
-	uint getFunctionId();
+	void setToFunctionId(uint functionId);
+	uint asFunctionId();
 
-	void putMethodId(BuiltInMethod methodId);
-	BuiltInMethod getMethodId();
+	void setToMethodId(BuiltInMethod methodId);
+	BuiltInMethod asMethodId();
 
-	void putAsset(uint32 assetId);
+	void setToAssetId(uint32 assetId);
 	Asset *getAsset();
-	uint32 getAssetId();
+	uint32 asAssetId();
 
-	void putCollection(Common::SharedPtr<Collection> collection);
-	Common::SharedPtr<Collection> getCollection();
+	void setToCollection(Common::SharedPtr<Collection> collection);
+	Common::SharedPtr<Collection> asCollection();
 
-	Operand getLiteralValue() const;
+	ScriptValue getLiteralValue() const;
 
-	bool operator==(const Operand &other) const;
-	bool operator<(const Operand &other) const;
-	bool operator>(const Operand &other) const;
+	bool operator==(const ScriptValue &other) const;
+	bool operator<(const ScriptValue &other) const;
+	bool operator>(const ScriptValue &other) const;
 
-	bool operator||(const Operand &other) const;
+	bool operator||(const ScriptValue &other) const;
 	bool operator!() const;
-	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;
+	bool operator&&(const ScriptValue &other) const;
+
+	ScriptValue operator+(const ScriptValue &other) const;
+	ScriptValue operator-(const ScriptValue &other) const;
+	ScriptValue operator*(const ScriptValue &other) const;
+	ScriptValue operator/(const ScriptValue &other) const;
+	ScriptValue operator%(const ScriptValue &other) const;
+	ScriptValue operator-() const;
 
 private:
 	bool isInteger() { return getType() == kOperandTypeBool || getType() == kOperandTypeInt; };
diff --git a/engines/mediastation/mediascript/variable.cpp b/engines/mediastation/mediascript/variable.cpp
index 90aebda8e5b..8edde17aaad 100644
--- a/engines/mediastation/mediascript/variable.cpp
+++ b/engines/mediastation/mediascript/variable.cpp
@@ -21,30 +21,30 @@
 
 #include "mediastation/mediastation.h"
 #include "mediastation/mediascript/variable.h"
-#include "mediastation/mediascript/operand.h"
+#include "mediastation/mediascript/scriptvalue.h"
 #include "mediastation/mediascript/codechunk.h"
 #include "mediastation/datum.h"
 #include "mediastation/debugchannels.h"
 
 namespace MediaStation {
 
-Operand Collection::callMethod(BuiltInMethod method, Common::Array<Operand> &args) {
+ScriptValue Collection::callMethod(BuiltInMethod method, Common::Array<ScriptValue> &args) {
 	switch (method) {
 	case kIsEmptyMethod: {
-		Operand returnValue(kOperandTypeBool);
-		returnValue.putInteger(static_cast<uint>(empty()));
+		ScriptValue returnValue(kOperandTypeBool);
+		returnValue.setToParamToken(static_cast<uint>(empty()));
 		return returnValue;
 	}
 
 	case kAppendMethod: {
-		for (Operand arg : args) {
+		for (ScriptValue arg : args) {
 			push_back(arg);
 		}
-		return Operand();
+		return ScriptValue();
 	}
 
 	case kDeleteFirstMethod: {
-		Operand returnValue = remove_at(0);
+		ScriptValue returnValue = remove_at(0);
 		return returnValue;
 	}
 
@@ -53,36 +53,36 @@ Operand Collection::callMethod(BuiltInMethod method, Common::Array<Operand> &arg
 		assert(args.size() == 1);
 		for (uint i = 0; i < size(); i++) {
 			if (args[0] == operator[](i)) {
-				Operand returnValue = remove_at(i);
+				ScriptValue returnValue = remove_at(i);
 				return returnValue;
 			}
 		}
 
 		// The item wasn't found.
-		return Operand();
+		return ScriptValue();
 	}
 
 	case kCountMethod: {
-		Operand returnValue = Operand(kOperandTypeBool);
-		returnValue.putInteger(size());
+		ScriptValue returnValue = ScriptValue(kOperandTypeBool);
+		returnValue.setToParamToken(size());
 		return returnValue;
 	}
 
 	case kGetAtMethod: {
 		assert(args.size() == 1);
-		Operand returnValue = operator[](args[0].getInteger());
+		ScriptValue returnValue = operator[](args[0].asParamToken());
 		return returnValue;
 	}
 
 	case kSendMethod: {
 		// Call a method on each item in the collection.
-		BuiltInMethod methodToSend = static_cast<BuiltInMethod>(args[0].getMethodId());
-		Common::Array<Operand> sendArgs;
+		BuiltInMethod methodToSend = static_cast<BuiltInMethod>(args[0].asMethodId());
+		Common::Array<ScriptValue> sendArgs;
 		for (uint i = 0; i < size(); i++) {
-			Operand self = operator[](i);
+			ScriptValue self = operator[](i);
 			CodeChunk::callBuiltInMethod(methodToSend, self, sendArgs);
 		}
-		return Operand();
+		return ScriptValue();
 	}
 
 	case kSeekMethod: {
@@ -95,8 +95,8 @@ Operand Collection::callMethod(BuiltInMethod method, Common::Array<Operand> &arg
 		}
 
 		// The item wasn't found.
-		Operand returnValue(kOperandTypeBool);
-		returnValue.putInteger(-1);
+		ScriptValue returnValue(kOperandTypeBool);
+		returnValue.setToParamToken(-1);
 		return returnValue;
 	}
 
@@ -106,18 +106,18 @@ Operand Collection::callMethod(BuiltInMethod method, Common::Array<Operand> &arg
 			uint j = g_engine->_randomSource.getRandomNumber(size() - 1);
 			SWAP(operator[](i), operator[](j));
 		}
-		return Operand();
+		return ScriptValue();
 	}
 
 	case kSortMethod: {
 		assert(args.empty());
 		Common::sort(begin(), end());
-		return Operand();
+		return ScriptValue();
 	}
 
 	case kEmptyMethod: {
 		clear();
-		return Operand();
+		return ScriptValue();
 	}
 
 	default:
@@ -130,22 +130,22 @@ Variable::Variable(Chunk &chunk, bool readId) {
 		_id = Datum(chunk).u.i;
 	}
 
-	_type = static_cast<VariableType>(Datum(chunk).u.i);
+	_type = static_cast<ScriptValueType>(Datum(chunk).u.i);
 	debugC(7, kDebugScript, "Variable::Variable(): id = %d, type %s (%d) (@0x%llx)",
-		_id, variableTypeToStr(_type), static_cast<uint>(_type), static_cast<long long int>(chunk.pos()));
+		_id, scriptValueTypeToStr(_type), static_cast<uint>(_type), static_cast<long long int>(chunk.pos()));
 	switch (_type) {
-	case kVariableTypeCollection: {
+	case kScriptValueTypeCollection: {
 		uint totalItems = Datum(chunk).u.i;
 		_c = Common::SharedPtr<Collection>(new Collection);
 		for (uint i = 0; i < totalItems; i++) {
-			debugC(7, kDebugLoading, "Variable::Variable(): %s: Value %d of %d", variableTypeToStr(_type), i, totalItems);
+			debugC(7, kDebugLoading, "Variable::Variable(): %s: Value %d of %d", scriptValueTypeToStr(_type), i, totalItems);
 			Variable variable = Variable(chunk, readId = false);
 			_c->push_back(variable.getValue());
 		}
 		break;
 	}
 
-	case kVariableTypeString: {
+	case kScriptValueTypeString: {
 		// TODO: This copies the string. Can we read it directly from the chunk?
 		int size = Datum(chunk).u.i;
 		char *buffer = new char[size + 1];
@@ -153,41 +153,41 @@ Variable::Variable(Chunk &chunk, bool readId) {
 		buffer[size] = '\0';
 		_value.string = new Common::String(buffer);
 		delete[] buffer;
-		debugC(7, kDebugLoading, "Variable::Variable(): %s: %s", variableTypeToStr(_type), _value.string->c_str());
+		debugC(7, kDebugLoading, "Variable::Variable(): %s: %s", scriptValueTypeToStr(_type), _value.string->c_str());
 		break;
 	}
 
-	case kVariableTypeAssetId: {
+	case kScriptValueTypeAssetId: {
 		_value.assetId = Datum(chunk, kDatumTypeUint16_1).u.i;
-		debugC(7, kDebugLoading, "Variable::Variable(): %s: %d", variableTypeToStr(_type), _value.assetId);
+		debugC(7, kDebugLoading, "Variable::Variable(): %s: %d", scriptValueTypeToStr(_type), _value.assetId);
 		break;
 	}
 
-	case kVariableTypeBoolean: {
+	case kScriptValueTypeBool: {
 		uint rawValue = Datum(chunk, kDatumTypeUint8).u.i;
-		debugC(7, kDebugLoading, " Variable::Variable(): %s: %d", variableTypeToStr(_type), rawValue);
+		debugC(7, kDebugLoading, " Variable::Variable(): %s: %d", scriptValueTypeToStr(_type), rawValue);
 		_value.i = static_cast<int>(rawValue == 1);
 		break;
 	}
 
-	case kVariableTypeFloat: {
+	case kScriptValueTypeFloat: {
 		Datum datum = Datum(chunk);
 		if ((datum.t != kDatumTypeFloat64_1) && (datum.t != kDatumTypeFloat64_2)) {
 			error("Variable::Variable(): Got a non-float datum type 0x%x to put into a float variable", datum.t);
 		}
 		_value.d = datum.u.f;
-		debugC(7, kDebugLoading, "Variable::Variable(): %s: %f", variableTypeToStr(_type), _value.d);
+		debugC(7, kDebugLoading, "Variable::Variable(): %s: %f", scriptValueTypeToStr(_type), _value.d);
 		break;
 	}
 
-	case kVariableTypeInt: {
+	case kScriptValueTypeParamToken: {
 		_value.i = Datum(chunk).u.i;
-		debugC(7, kDebugLoading, "Variable::Variable(): %s: %d", variableTypeToStr(_type), _value.i);
+		debugC(7, kDebugLoading, "Variable::Variable(): %s: %d", scriptValueTypeToStr(_type), _value.i);
 		break;
 	}
 
 	default:
-		error("Variable::Variable(): Got unknown variable value type %s (%d)", variableTypeToStr(_type), static_cast<uint>(_type));
+		error("Variable::Variable(): Got unknown variable value type %s (%d)", scriptValueTypeToStr(_type), static_cast<uint>(_type));
 	}
 }
 
@@ -195,91 +195,91 @@ Variable::~Variable() {
 	clear();
 }
 
-Operand Variable::getValue() {
+ScriptValue Variable::getValue() {
 	switch (_type) {
-	case kVariableTypeEmpty: {
+	case kScriptValueTypeEmpty: {
 		error("Variable::getValue(): Attempt to get value from an empty variable");
 	}
 
-	case kVariableTypeCollection: {
-		Operand returnValue(kOperandTypeCollection);
-		returnValue.putCollection(_c);
+	case kScriptValueTypeCollection: {
+		ScriptValue returnValue(kOperandTypeCollection);
+		returnValue.setToCollection(_c);
 		return returnValue;
 	}
 
-	case kVariableTypeString: {
-		Operand returnValue(kOperandTypeString);
-		returnValue.putString(_value.string);
+	case kScriptValueTypeString: {
+		ScriptValue returnValue(kOperandTypeString);
+		returnValue.setToString(_value.string);
 		return returnValue;
 	}
 
-	case kVariableTypeAssetId: {
-		Operand returnValue(kOperandTypeAssetId);
-		returnValue.putAsset(_value.assetId);
+	case kScriptValueTypeAssetId: {
+		ScriptValue returnValue(kOperandTypeAssetId);
+		returnValue.setToAssetId(_value.assetId);
 		return returnValue;
 	}
 
-	case kVariableTypeBoolean: {
+	case kScriptValueTypeBool: {
 		// TODO: Is this value type correct?
 		// Shouldn't matter too much, though, since it's still an integer type.
-		Operand returnValue(kOperandTypeBool);
-		returnValue.putInteger(_value.i);
+		ScriptValue returnValue(kOperandTypeBool);
+		returnValue.setToParamToken(_value.i);
 		return returnValue;
 	}
 
-	case kVariableTypeInt: {
+	case kScriptValueTypeParamToken: {
 		// TODO: Is this value type correct?
 		// Shouldn't matter too much, though, since it's still an integer type.
-		Operand returnValue(kOperandTypeBool);
-		returnValue.putInteger(_value.i);
+		ScriptValue returnValue(kOperandTypeBool);
+		returnValue.setToParamToken(_value.i);
 		return returnValue;
 	}
 
-	case kVariableTypeFloat: {
+	case kScriptValueTypeFloat: {
 		// TODO: Is this value type correct?
 		// Shouldn't matter too much, though, since it's still a floating-point type.
-		Operand returnValue(kOperandTypeTime);
-		returnValue.putDouble(_value.d);
+		ScriptValue returnValue(kOperandTypeTime);
+		returnValue.setToFloat(_value.d);
 		return returnValue;
 	}
 
 	default:
-		error("Variable::getValue(): Attempt to get value from unknown variable type %s (%d)", variableTypeToStr(_type), static_cast<uint>(_type));
+		error("Variable::getValue(): Attempt to get value from unknown variable type %s (%d)", scriptValueTypeToStr(_type), static_cast<uint>(_type));
 	}
 }
 
-void Variable::putValue(Operand value) {
+void Variable::putValue(ScriptValue value) {
 	clear();
 
 	switch (value.getType()) {
 	case kOperandTypeEmpty: {
-		error("Variable::putValue(): Assigning an empty operand to a variable not supported");
+		error("Variable::putValue(): Assigning an empty ScriptValue to a variable not supported");
 	}
 
 	case kOperandTypeBool:
 	case kOperandTypeInt:
-	case kOperandTypeDollarSignVariable: {
-		_type = kVariableTypeInt;
-		_value.i = value.getInteger();
+	case kOperandTypeParamToken: {
+		_type = kScriptValueTypeParamToken;
+		_value.i = value.asParamToken();
 		break;
 	}
 
 	case kOperandTypeTime:
 	case kOperandTypeFloat: {
-		_type = kVariableTypeFloat;
-		_value.d = value.getDouble();
+		_type = kScriptValueTypeFloat;
+		_value.d = value.asFloat();
 		break;
 	}
 
 	case kOperandTypeString: {
-		_type = kVariableTypeString;
-		_value.string = value.getString();
+		_type = kScriptValueTypeString;
+		_value.string = value.asString();
 		break;
 	}
 
 	case kOperandTypeAssetId: {
-		_type = kVariableTypeAssetId;
-		_value.assetId = value.getAssetId();
+		_type = kScriptValueTypeAssetId;
+		_value.assetId = value.asAssetId();
 		break;
 	}
 
@@ -289,31 +289,31 @@ void Variable::putValue(Operand value) {
 	}
 
 	case kOperandTypeFunctionId: {
-		_type = kVariableTypeFunction;
-		_value.functionId = value.getFunctionId();
+		_type = kScriptValueTypeFunctionId;
+		_value.functionId = value.asFunctionId();
 		break;
 	}
 
 	case kOperandTypeCollection: {
-		_type = kVariableTypeCollection;
-		_c = value.getCollection();
+		_type = kScriptValueTypeCollection;
+		_c = value.asCollection();
 		break;
 	}
 
 	default:
-		error("Variable::putValue(): Assigning an unknown operand type %s (%d) to a variable not supported",
+		error("Variable::putValue(): Assigning an unknown ScriptValue type %s (%d) to a variable not supported",
 			operandTypeToStr(value.getType()), static_cast<uint>(value.getType()));
 	}
 }
 
 void Variable::clear() {
 	switch (_type) {
-	case kVariableTypeCollection: {
+	case kScriptValueTypeCollection: {
 		_c.reset();
 		break;
 	}
 
-	case kVariableTypeString: {
+	case kScriptValueTypeString: {
 		delete _value.string;
 		_value.string = nullptr;
 		break;
@@ -323,7 +323,7 @@ void Variable::clear() {
 		break;
 	}
 
-	_type = kVariableTypeEmpty;
+	_type = kScriptValueTypeEmpty;
 }
 
 } // End of namespace MediaStation
diff --git a/engines/mediastation/mediascript/variable.h b/engines/mediastation/mediascript/variable.h
index 22abd70b89f..ec445656b0d 100644
--- a/engines/mediastation/mediascript/variable.h
+++ b/engines/mediastation/mediascript/variable.h
@@ -32,17 +32,17 @@
 
 namespace MediaStation {
 
-class Operand;
+class ScriptValue;
 
-class Collection : public Common::Array<Operand> {
+class Collection : public Common::Array<ScriptValue> {
 public:
-	Operand callMethod(BuiltInMethod method, Common::Array<Operand> &args);
+	ScriptValue callMethod(BuiltInMethod method, Common::Array<ScriptValue> &args);
 };
 
 class Variable {
 public:
 	uint32 _id = 0;
-	VariableType _type = kVariableTypeEmpty;
+	ScriptValueType _type = kScriptValueTypeEmpty;
 	union {
 		Common::String *string;
 		uint functionId;
@@ -56,8 +56,8 @@ public:
 	Variable(Chunk &chunk, bool readId = true);
 	~Variable();
 
-	Operand getValue();
-	void putValue(Operand value);
+	ScriptValue getValue();
+	void putValue(ScriptValue value);
 
 private:
 	void clear();
diff --git a/engines/mediastation/mediastation.cpp b/engines/mediastation/mediastation.cpp
index b6116fc9195..37a657b37f9 100644
--- a/engines/mediastation/mediastation.cpp
+++ b/engines/mediastation/mediastation.cpp
@@ -394,7 +394,7 @@ void MediaStationEngine::addPlayingAsset(Asset *assetToAdd) {
 	g_engine->_assetsPlaying.push_back(assetToAdd);
 }
 
-Operand MediaStationEngine::callMethod(BuiltInMethod methodId, Common::Array<Operand> &args) {
+ScriptValue MediaStationEngine::callMethod(BuiltInMethod methodId, Common::Array<ScriptValue> &args) {
 	switch (methodId) {
 	case kBranchToScreenMethod: {
 		assert(args.size() >= 1);
@@ -402,16 +402,16 @@ Operand MediaStationEngine::callMethod(BuiltInMethod methodId, Common::Array<Ope
 			// TODO: Figure out what the rest of the args can be.
 			warning("MediaStationEngine::callMethod(): branchToScreen got more than one arg");
 		}
-		uint32 contextId = args[0].getAssetId();
+		uint32 contextId = args[0].asAssetId();
 		_requestedScreenBranchId = contextId;
-		return Operand();
+		return ScriptValue();
 	}
 
 	case kReleaseContextMethod: {
 		assert(args.size() == 1);
-		uint32 contextId = args[0].getAssetId();
+		uint32 contextId = args[0].asAssetId();
 		_requestedContextReleaseId.push_back(contextId);
-		return Operand();
+		return ScriptValue();
 	}
 
 	default:
@@ -506,26 +506,26 @@ Asset *MediaStationEngine::findAssetToAcceptMouseEvents() {
 	return intersectingAsset;
 }
 
-Operand MediaStationEngine::callBuiltInFunction(BuiltInFunction function, Common::Array<Operand> &args) {
+ScriptValue MediaStationEngine::callBuiltInFunction(BuiltInFunction function, Common::Array<ScriptValue> &args) {
 	switch (function) {
 	case kEffectTransitionFunction:
 	case kEffectTransitionOnSyncFunction: {
 		// TODO: effectTransitionOnSync should be split out into its own function.
 		effectTransition(args);
-		return Operand();
+		return ScriptValue();
 	}
 
 	case kDrawingFunction: {
 		// Not entirely sure what this function does, but it seems like a way to
 		// call into some drawing functions built into the IBM/Crayola executable.
 		warning("MediaStationEngine::callBuiltInFunction(): Built-in drawing function not implemented");
-		return Operand();
+		return ScriptValue();
 	}
 
 	case kUnk1Function: {
 		warning("MediaStationEngine::callBuiltInFunction(): Function 10 not implemented");
-		Operand returnValue = Operand(kOperandTypeBool);
-		returnValue.putInteger(1);
+		ScriptValue returnValue = ScriptValue(kOperandTypeBool);
+		returnValue.setToParamToken(1);
 		return returnValue;
 	}
 
diff --git a/engines/mediastation/mediastation.h b/engines/mediastation/mediastation.h
index dd11d511290..a6b1c868b7a 100644
--- a/engines/mediastation/mediastation.h
+++ b/engines/mediastation/mediastation.h
@@ -86,9 +86,10 @@ public:
 	Asset *getAssetByChunkReference(uint chunkReference);
 	Function *getFunctionById(uint functionId);
 
-	Operand callMethod(BuiltInMethod methodId, Common::Array<Operand> &args);
-	Operand callBuiltInFunction(BuiltInFunction function, Common::Array<Operand> &args);
+	ScriptValue callMethod(BuiltInMethod methodId, Common::Array<ScriptValue> &args);
+	ScriptValue callBuiltInFunction(BuiltInFunction function, Common::Array<ScriptValue> &args);
 	Common::HashMap<uint32, Variable *> _variables;
+
 	Common::RandomSource _randomSource;
 
 	Graphics::Screen *_screen = nullptr;
@@ -129,7 +130,7 @@ private:
 	void releaseContext(uint32 contextId);
 	Asset *findAssetToAcceptMouseEvents();
 
-	void effectTransition(Common::Array<Operand> &args);
+	void effectTransition(Common::Array<ScriptValue> &args);
 };
 
 extern MediaStationEngine *g_engine;
diff --git a/engines/mediastation/module.mk b/engines/mediastation/module.mk
index 070ef331544..5be2c2a1d45 100644
--- a/engines/mediastation/module.mk
+++ b/engines/mediastation/module.mk
@@ -24,8 +24,8 @@ MODULE_OBJS = \
 	mediascript/codechunk.o \
 	mediascript/eventhandler.o \
 	mediascript/function.o \
-	mediascript/operand.o \
 	mediascript/scriptconstants.o \
+	mediascript/scriptvalue.o \
 	mediascript/variable.o \
 	mediastation.o \
 	metaengine.o \
diff --git a/engines/mediastation/transitions.cpp b/engines/mediastation/transitions.cpp
index 267e7066806..2f2c20bd133 100644
--- a/engines/mediastation/transitions.cpp
+++ b/engines/mediastation/transitions.cpp
@@ -37,8 +37,8 @@ enum TransitionType {
 	kTransitionCircleOut = 328
 };
 
-void MediaStationEngine::effectTransition(Common::Array<Operand> &args) {
-	TransitionType transitionType = static_cast<TransitionType>(args[0].getInteger());
+void MediaStationEngine::effectTransition(Common::Array<ScriptValue> &args) {
+	TransitionType transitionType = static_cast<TransitionType>(args[0].asParamToken());
 	switch (transitionType) {
 	case kTransitionFadeToBlack:
 	case kTransitionSetToBlack: {
@@ -76,7 +76,7 @@ void MediaStationEngine::effectTransition(Common::Array<Operand> &args) {
 	}
 
 	case kTransitionSetToPercentOfPaletteObject: {
-		double percentComplete = args[1].getDouble();
+		double percentComplete = args[1].asFloat();
 
 		// TODO: Implement percent of palette transition.
 		warning("MediaStationEngine::effectTransition(): Setting to %f%% of palette not implemented, changing palette immediately", percentComplete);


Commit: b48dd3cff4503f8ba2b052d30ce4fd5792b6bf1c
    https://github.com/scummvm/scummvm/commit/b48dd3cff4503f8ba2b052d30ce4fd5792b6bf1c
Author: Nathanael Gentry (nathanael.gentrydb8 at gmail.com)
Date: 2025-04-11T14:54:23-04:00

Commit Message:
MEDIASTATION: Correctly implement script value type methods

Including getters, setters, comparison, and math operators

Notably, the Variable class is also gone. Instead, as
revealed by the recently discovered debug symbols, ScriptValues
are directly stored as variables in each context.

Changed paths:
  A engines/mediastation/mediascript/collection.cpp
  A engines/mediastation/mediascript/collection.h
  R engines/mediastation/mediascript/variable.cpp
  R engines/mediastation/mediascript/variable.h
    engines/mediastation/assets/hotspot.cpp
    engines/mediastation/assets/image.cpp
    engines/mediastation/assets/movie.cpp
    engines/mediastation/assets/path.cpp
    engines/mediastation/assets/sound.cpp
    engines/mediastation/assets/sprite.cpp
    engines/mediastation/assets/text.cpp
    engines/mediastation/assets/timer.cpp
    engines/mediastation/context.cpp
    engines/mediastation/context.h
    engines/mediastation/mediascript/codechunk.cpp
    engines/mediastation/mediascript/codechunk.h
    engines/mediastation/mediascript/scriptconstants.cpp
    engines/mediastation/mediascript/scriptconstants.h
    engines/mediastation/mediascript/scriptvalue.cpp
    engines/mediastation/mediascript/scriptvalue.h
    engines/mediastation/mediastation.cpp
    engines/mediastation/mediastation.h
    engines/mediastation/module.mk
    engines/mediastation/transitions.cpp


diff --git a/engines/mediastation/assets/hotspot.cpp b/engines/mediastation/assets/hotspot.cpp
index 058eb2a95ff..da02b85261f 100644
--- a/engines/mediastation/assets/hotspot.cpp
+++ b/engines/mediastation/assets/hotspot.cpp
@@ -69,38 +69,39 @@ bool Hotspot::isInside(const Common::Point &pointToCheck) {
 }
 
 ScriptValue Hotspot::callMethod(BuiltInMethod methodId, Common::Array<ScriptValue> &args) {
+	ScriptValue returnValue;
+
 	switch (methodId) {
 	case kMouseActivateMethod: {
 		assert(args.empty());
 		_isActive = true;
 		g_engine->addPlayingAsset(this);
 		g_engine->_needsHotspotRefresh = true;
-		return ScriptValue();
+		return returnValue;
 	}
 
 	case kMouseDeactivateMethod: {
 		assert(args.empty());
 		_isActive = false;
 		g_engine->_needsHotspotRefresh = true;
-		return ScriptValue();
+		return returnValue;
 	}
 
 	case kIsActiveMethod: {
 		assert(args.empty());
-		ScriptValue returnValue(kOperandTypeBool);
-		returnValue.setToParamToken(static_cast<int>(_isActive));
+		returnValue.setToBool(_isActive);
 		return returnValue;
 	}
 
 	case kTriggerAbsXPositionMethod: {
-		ScriptValue returnValue(kOperandTypeBool);
-		returnValue.setToParamToken(g_engine->_mousePos.x);
+		double mouseX = static_cast<double>(g_engine->_mousePos.x);
+		returnValue.setToFloat(mouseX);
 		return returnValue;
 	}
 
 	case kTriggerAbsYPositionMethod: {
-		ScriptValue returnValue(kOperandTypeBool);
-		returnValue.setToParamToken(g_engine->_mousePos.y);
+		double mouseY = static_cast<double>(g_engine->_mousePos.y);
+		returnValue.setToFloat(mouseY);
 		return returnValue;
 	}
 
diff --git a/engines/mediastation/assets/image.cpp b/engines/mediastation/assets/image.cpp
index eabc5fb479b..942ef1b2686 100644
--- a/engines/mediastation/assets/image.cpp
+++ b/engines/mediastation/assets/image.cpp
@@ -41,23 +41,25 @@ Image::~Image() {
 }
 
 ScriptValue Image::callMethod(BuiltInMethod methodId, Common::Array<ScriptValue> &args) {
+	ScriptValue returnValue;
+
 	switch (methodId) {
 	case kSpatialShowMethod: {
 		assert(args.empty());
 		spatialShow();
-		return ScriptValue();
+		return returnValue;
 	}
 
 	case kSpatialHideMethod: {
 		assert(args.empty());
 		spatialHide();
-		return ScriptValue();
+		return returnValue;
 	}
 
 	case kSetDissolveFactorMethod: {
 		assert(args.size() == 1);
 		warning("Image::callMethod(): setDissolveFactor not implemented yet");
-		return ScriptValue();
+		return returnValue;
 	}
 
 	case kSpatialMoveToMethod: {
@@ -68,8 +70,8 @@ ScriptValue Image::callMethod(BuiltInMethod methodId, Common::Array<ScriptValue>
 		g_engine->_dirtyRects.push_back(bbox);
 
 		// Update location and mark new location dirty.
-		int newXAdjust = args[0].asParamToken();
-		int newYAdjust = args[1].asParamToken();
+		int newXAdjust = static_cast<int>(args[0].asFloat());
+		int newYAdjust = static_cast<int>(args[1].asFloat());
 		if (_xAdjust != newXAdjust || _yAdjust != newYAdjust) {
 			_xAdjust = newXAdjust;
 			_yAdjust = newYAdjust;
@@ -78,7 +80,7 @@ ScriptValue Image::callMethod(BuiltInMethod methodId, Common::Array<ScriptValue>
 			g_engine->_dirtyRects.push_back(bbox);
 		}
 
-		return ScriptValue();
+		return returnValue;
 	}
 
 	default:
diff --git a/engines/mediastation/assets/movie.cpp b/engines/mediastation/assets/movie.cpp
index 65deb7392ce..e0f50bac700 100644
--- a/engines/mediastation/assets/movie.cpp
+++ b/engines/mediastation/assets/movie.cpp
@@ -187,76 +187,80 @@ Movie::~Movie() {
 }
 
 ScriptValue Movie::callMethod(BuiltInMethod methodId, Common::Array<ScriptValue> &args) {
+	ScriptValue returnValue;
+
 	switch (methodId) {
 	case kTimePlayMethod: {
 		assert(args.empty());
 		timePlay();
-		return ScriptValue();
+		return returnValue;
 	}
 
 	case kSpatialShowMethod: {
 		assert(args.empty());
 		spatialShow();
-		return ScriptValue();
+		return returnValue;
 	}
 
 	case kTimeStopMethod: {
 		assert(args.empty());
 		timeStop();
-		return ScriptValue();
+		return returnValue;
 	}
 
 	case kSpatialHideMethod: {
 		assert(args.empty());
 		spatialHide();
-		return ScriptValue();
+		return returnValue;
 	}
 
 	case kIsVisibleMethod: {
 		assert(args.empty());
-		ScriptValue returnValue(kOperandTypeBool);
-		returnValue.setToParamToken(_isShowing);
+		returnValue.setToBool(_isShowing);
 		return returnValue;
 	}
 
 	case kSpatialCenterMoveToMethod: {
 		assert(args.size() == 2);
-		spatialCenterMoveTo(args[0].asParamToken(), args[1].asParamToken());
-		return ScriptValue();
+		int x = static_cast<int>(args[0].asFloat());
+		int y = static_cast<int>(args[1].asFloat());
+		spatialCenterMoveTo(x, y);
+		return returnValue;
 	}
 
 	case kSpatialMoveToMethod: {
 		assert(args.size() == 2);
-		spatialMoveTo(args[0].asParamToken(), args[1].asParamToken());
-		return ScriptValue();
+		int x = static_cast<int>(args[0].asFloat());
+		int y = static_cast<int>(args[1].asFloat());
+		spatialMoveTo(x, y);
+		return returnValue;
 	}
 
 	case kIsPlayingMethod: {
 		assert(args.empty());
-		ScriptValue returnValue(kOperandTypeBool);
-		returnValue.setToParamToken(static_cast<uint>(_isPlaying));
+		returnValue.setToBool(_isPlaying);
 		return returnValue;
 	}
 
 	case kXPositionMethod: {
 		assert(args.empty());
-		ScriptValue returnValue(kOperandTypeBool);
-		returnValue.setToParamToken(_header->_boundingBox->left);
+		double left = static_cast<double>(_header->_boundingBox->left);
+		returnValue.setToFloat(left);
 		return returnValue;
 
 	}
 
 	case kYPositionMethod: {
 		assert(args.empty());
-		ScriptValue returnValue(kOperandTypeBool);
-		returnValue.setToParamToken(_header->_boundingBox->top);
+		double top = static_cast<double>(_header->_boundingBox->top);
+		returnValue.setToFloat(top);
 		return returnValue;
 	}
 
 	case kSetDissolveFactorMethod: {
 		assert(args.size() == 1);
 		warning("Movie::callMethod(): setDissolveFactor not implemented yet");
-		return ScriptValue();
+		return returnValue;
 	}
 
 	default:
diff --git a/engines/mediastation/assets/path.cpp b/engines/mediastation/assets/path.cpp
index c153547f310..6f3f6496672 100644
--- a/engines/mediastation/assets/path.cpp
+++ b/engines/mediastation/assets/path.cpp
@@ -29,23 +29,24 @@ Path::~Path() {
 }
 
 ScriptValue Path::callMethod(BuiltInMethod methodId, Common::Array<ScriptValue> &args) {
+	ScriptValue returnValue;
+
 	switch (methodId) {
 	case kTimePlayMethod: {
 		assert(args.size() == 0);
 		timePlay();
-		return ScriptValue();
+		return returnValue;
 	}
 
 	case kSetDurationMethod: {
 		assert(args.size() == 1);
 		uint durationInMilliseconds = static_cast<uint>(args[0].asFloat() * 1000);
 		setDuration(durationInMilliseconds);
-		return ScriptValue();
+		return returnValue;
 	}
 
 	case kPercentCompleteMethod: {
 		assert(args.size() == 0);
-		ScriptValue returnValue(kOperandTypeTime);
 		returnValue.setToFloat(percentComplete());
 		return returnValue;
 	}
@@ -53,13 +54,12 @@ ScriptValue Path::callMethod(BuiltInMethod methodId, Common::Array<ScriptValue>
 	case kSetDissolveFactorMethod: {
 		assert(args.size() == 1);
 		warning("Path::callMethod(): setDissolveFactor not implemented yet");
-		return ScriptValue();
+		return returnValue;
 	}
 
 	case kIsPlayingMethod: {
 		assert(args.empty());
-		ScriptValue returnValue(kOperandTypeBool);
-		returnValue.setToParamToken(_isActive);
+		returnValue.setToBool(_isActive);
 		return returnValue;
 	}
 
diff --git a/engines/mediastation/assets/sound.cpp b/engines/mediastation/assets/sound.cpp
index 78fed5f8416..b3467d51237 100644
--- a/engines/mediastation/assets/sound.cpp
+++ b/engines/mediastation/assets/sound.cpp
@@ -56,17 +56,19 @@ void Sound::process() {
 }
 
 ScriptValue Sound::callMethod(BuiltInMethod methodId, Common::Array<ScriptValue> &args) {
+	ScriptValue returnValue;
+
 	switch (methodId) {
 	case kTimePlayMethod: {
 		assert(args.empty());
 		timePlay();
-		return ScriptValue();
+		return returnValue;
 	}
 
 	case kTimeStopMethod: {
 		assert(args.empty());
 		timeStop();
-		return ScriptValue();
+		return returnValue;
 	}
 
 	default:
diff --git a/engines/mediastation/assets/sprite.cpp b/engines/mediastation/assets/sprite.cpp
index d0091a4da66..6528a0c7826 100644
--- a/engines/mediastation/assets/sprite.cpp
+++ b/engines/mediastation/assets/sprite.cpp
@@ -86,35 +86,37 @@ Sprite::~Sprite() {
 }
 
 ScriptValue Sprite::callMethod(BuiltInMethod methodId, Common::Array<ScriptValue> &args) {
+	ScriptValue returnValue;
+
 	switch (methodId) {
 	case kSpatialShowMethod: {
 		assert(args.empty());
 		spatialShow();
-		return ScriptValue();
+		return returnValue;
 	}
 
 	case kSpatialHideMethod: {
 		assert(args.empty());
 		spatialHide();
-		return ScriptValue();
+		return returnValue;
 	}
 
 	case kTimePlayMethod: {
 		assert(args.empty());
 		timePlay();
-		return ScriptValue();
+		return returnValue;
 	}
 
 	case kTimeStopMethod: {
 		assert(args.empty());
 		timeStop();
-		return ScriptValue();
+		return returnValue;
 	}
 
 	case kMovieResetMethod: {
 		assert(args.empty());
 		movieReset();
-		return ScriptValue();
+		return returnValue;
 	}
 
 	case kSetCurrentClipMethod: {
@@ -123,7 +125,7 @@ ScriptValue Sprite::callMethod(BuiltInMethod methodId, Common::Array<ScriptValue
 			error("Sprite::callMethod(): (%d) setClip() called with unhandled arg: %d", _header->_id, args[0].asParamToken());
 		}
 		setCurrentClip();
-		return ScriptValue();
+		return returnValue;
 	}
 
 	case kSetSpriteFrameByIdMethod: {
@@ -131,12 +133,11 @@ ScriptValue Sprite::callMethod(BuiltInMethod methodId, Common::Array<ScriptValue
 		uint32 externalFrameId = args[0].asParamToken();
 		uint32 internalFrameId = _header->_spriteFrameMapping.getVal(externalFrameId);
 		showFrame(_frames[internalFrameId]);
-		return ScriptValue();
+		return returnValue;
 	}
 
 	case kIsPlayingMethod: {
-		ScriptValue returnValue(kOperandTypeBool);
-		returnValue.setToParamToken(static_cast<int>(_isPlaying));
+		returnValue.setToBool(_isPlaying);
 		return returnValue;
 	}
 
@@ -149,8 +150,8 @@ ScriptValue Sprite::callMethod(BuiltInMethod methodId, Common::Array<ScriptValue
 		}
 
 		// Update the location and mark the new location dirty.
-		int newXAdjust = args[0].asParamToken();
-		int newYAdjust = args[1].asParamToken();
+		int newXAdjust = static_cast<int>(args[0].asFloat());
+		int newYAdjust = static_cast<int>(args[1].asFloat());
 		if (_xAdjust != newXAdjust || _yAdjust != newYAdjust) {
 			debugC(5, kDebugGraphics, "Sprite::callMethod(): (%d) Moving sprite to (%d, %d)", _header->_id, newXAdjust, newYAdjust);
 			_xAdjust = newXAdjust;
@@ -160,7 +161,7 @@ ScriptValue Sprite::callMethod(BuiltInMethod methodId, Common::Array<ScriptValue
 			}
 		}
 
-		return ScriptValue();
+		return returnValue;
 	}
 
 	default:
diff --git a/engines/mediastation/assets/text.cpp b/engines/mediastation/assets/text.cpp
index a835bdadc24..16df7642ba3 100644
--- a/engines/mediastation/assets/text.cpp
+++ b/engines/mediastation/assets/text.cpp
@@ -24,6 +24,8 @@
 namespace MediaStation {
 
 ScriptValue Text::callMethod(BuiltInMethod methodId, Common::Array<ScriptValue> &args) {
+	ScriptValue returnValue;
+
 	switch (methodId) {
 	case kTextMethod: {
 		assert(args.empty());
@@ -39,14 +41,14 @@ ScriptValue Text::callMethod(BuiltInMethod methodId, Common::Array<ScriptValue>
 		assert(args.empty());
 		_isActive = true;
 		warning("Text::callMethod(): spatialShow method not implemented yet");
-		return ScriptValue();
+		return returnValue;
 	}
 
 	case kSpatialHideMethod: {
 		assert(args.empty());
 		_isActive = false;
 		warning("Text::callMethod(): spatialHide method not implemented yet");
-		return ScriptValue();
+		return returnValue;
 	}
 
 	default:
diff --git a/engines/mediastation/assets/timer.cpp b/engines/mediastation/assets/timer.cpp
index 41e28680dc4..de9d2226f36 100644
--- a/engines/mediastation/assets/timer.cpp
+++ b/engines/mediastation/assets/timer.cpp
@@ -27,23 +27,24 @@
 namespace MediaStation {
 
 ScriptValue Timer::callMethod(BuiltInMethod methodId, Common::Array<ScriptValue> &args) {
+	ScriptValue returnValue;
+
 	switch (methodId) {
 	case kTimePlayMethod: {
 		assert(args.size() == 0);
 		timePlay();
-		return ScriptValue();
+		return returnValue;
 	}
 
 	case kTimeStopMethod: {
 		assert(args.size() == 0);
 		timeStop();
-		return ScriptValue();
+		return returnValue;
 	}
 
 	case kIsPlayingMethod: {
 		assert(args.size() == 0);
-		ScriptValue returnValue(kOperandTypeBool);
-		returnValue.setToParamToken(static_cast<int>(_isActive));
+		returnValue.setToBool(_isActive);
 		return returnValue;
 	}
 
diff --git a/engines/mediastation/context.cpp b/engines/mediastation/context.cpp
index b3ae09fa4c0..86f88d43ab8 100644
--- a/engines/mediastation/context.cpp
+++ b/engines/mediastation/context.cpp
@@ -25,6 +25,7 @@
 #include "mediastation/debugchannels.h"
 
 #include "mediastation/bitmap.h"
+#include "mediastation/mediascript/collection.h"
 #include "mediastation/assets/canvas.h"
 #include "mediastation/assets/palette.h"
 #include "mediastation/assets/image.h"
@@ -127,6 +128,11 @@ Context::~Context() {
 		delete it->_value;
 	}
 	_functions.clear();
+
+	for (auto it = _variables.begin(); it != _variables.end(); ++it) {
+		delete it->_value;
+	}
+	_variables.clear();
 }
 
 Asset *Context::getAssetById(uint assetId) {
@@ -137,11 +143,14 @@ Asset *Context::getAssetByChunkReference(uint chunkReference) {
 	return _assetsByChunkReference.getValOrDefault(chunkReference);
 }
 
-
 Function *Context::getFunctionById(uint functionId) {
 	return _functions.getValOrDefault(functionId);
 }
 
+ScriptValue *Context::getVariable(uint variableId) {
+	return _variables.getValOrDefault(variableId);
+}
+
 void Context::registerActiveAssets() {
 	for (auto it = _assets.begin(); it != _assets.end(); ++it) {
 		if (it->_value->isActive()) {
@@ -177,20 +186,7 @@ void Context::readParametersSection(Chunk &chunk) {
 		}
 
 		case kContextParametersVariable: {
-			uint repeatedFileNumber = Datum(chunk, kDatumTypeUint16_1).u.i;
-			if (repeatedFileNumber != _fileNumber) {
-				warning("ContextParameters::ContextParameters(): Repeated file number didn't match: %d != %d", repeatedFileNumber, _fileNumber);
-			}
-			Variable *variable = new Variable(chunk);
-			if (g_engine->_variables.contains(variable->_id)) {
-				// Don't overwrite the variable if it already exists. This can happen if we have
-				// unloaded a screen but are returning to it later.
-				debugC(5, kDebugScript, "ContextParameters::ContextParameters(): Skipping re-creation of existing global variable %d (type: %s)", variable->_id, scriptValueTypeToStr(variable->_type));
-				delete variable;
-			} else {
-				g_engine->_variables.setVal(variable->_id, variable);
-				debugC(5, kDebugScript, "ContextParameters::ContextParameters(): Created global variable %d (type: %s)", variable->_id, scriptValueTypeToStr(variable->_type));
-			}
+			readVariable(chunk);
 			break;
 		}
 
@@ -208,6 +204,23 @@ void Context::readParametersSection(Chunk &chunk) {
 	}
 }
 
+void Context::readVariable(Chunk &chunk) {
+	uint repeatedFileNumber = Datum(chunk, kDatumTypeUint16_1).u.i;
+	if (repeatedFileNumber != _fileNumber) {
+		warning("Context::readVariable(): Repeated file number didn't match: %d != %d", repeatedFileNumber, _fileNumber);
+	}
+
+	uint id = Datum(chunk).u.i;
+	if (g_engine->getVariable(id) != nullptr) {
+		error("Context::readVariable(): Global variable %d already exists", id);
+	}
+
+	ScriptValue *value = new ScriptValue(&chunk);
+	_variables.setVal(id, value);
+	debugC(5, kDebugScript, "Context::readVariable(): Created global variable %d (type: %s)",
+		id, scriptValueTypeToStr(value->getType()));
+}
+
 void Context::readOldStyleHeaderSections(Subfile &subfile, Chunk &chunk) {
 	error("Context::readOldStyleHeaderSections(): Not implemented yet");
 }
diff --git a/engines/mediastation/context.h b/engines/mediastation/context.h
index 4558de2fcf0..0334138b259 100644
--- a/engines/mediastation/context.h
+++ b/engines/mediastation/context.h
@@ -30,7 +30,6 @@
 #include "mediastation/datafile.h"
 #include "mediastation/assetheader.h"
 #include "mediastation/mediascript/function.h"
-#include "mediastation/mediascript/variable.h"
 
 namespace MediaStation {
 
@@ -70,6 +69,7 @@ public:
 	Asset *getAssetById(uint assetId);
 	Asset *getAssetByChunkReference(uint chunkReference);
 	Function *getFunctionById(uint functionId);
+	ScriptValue *getVariable(uint variableId);
 	void registerActiveAssets();
 
 private:
@@ -82,8 +82,10 @@ private:
 	Common::HashMap<uint, Asset *> _assets;
 	Common::HashMap<uint, Function *> _functions;
 	Common::HashMap<uint, Asset *> _assetsByChunkReference;
+	Common::HashMap<uint, ScriptValue *> _variables;
 
 	void readParametersSection(Chunk &chunk);
+	void readVariable(Chunk &chunk);
 	void readOldStyleHeaderSections(Subfile &subfile, Chunk &chunk);
 	void readNewStyleHeaderSections(Subfile &subfile, Chunk &chunk);
 	bool readHeaderSection(Subfile &subfile, Chunk &chunk);
diff --git a/engines/mediastation/mediascript/codechunk.cpp b/engines/mediastation/mediascript/codechunk.cpp
index 4cf9da62598..9012728300f 100644
--- a/engines/mediastation/mediascript/codechunk.cpp
+++ b/engines/mediastation/mediascript/codechunk.cpp
@@ -23,6 +23,7 @@
 
 #include "mediastation/mediastation.h"
 #include "mediastation/mediascript/codechunk.h"
+#include "mediastation/mediascript/collection.h"
 #include "mediastation/datum.h"
 #include "mediastation/debugchannels.h"
 
@@ -40,7 +41,7 @@ ScriptValue CodeChunk::execute(Common::Array<ScriptValue> *args, Common::Array<S
 	ScriptValue returnValue;
 	while (_bytecode->pos() < _bytecode->size()) {
 		ScriptValue instructionResult = evaluateExpression();
-		if (instructionResult.getType() != kOperandTypeEmpty) {
+		if (instructionResult.getType() != kScriptValueTypeEmpty) {
 			returnValue = instructionResult;
 		}
 	}
@@ -71,7 +72,7 @@ ScriptValue CodeChunk::evaluateExpression() {
 	ScriptValue returnValue;
 	switch (instructionType) {
 	case kExpressionTypeEmpty: {
-		return ScriptValue();
+		return returnValue;
 	}
 
 	case kExpressionTypeOperation:
@@ -96,16 +97,12 @@ ScriptValue CodeChunk::evaluateExpression() {
 ScriptValue CodeChunk::evaluateOperation() {
 	Opcode opcode = static_cast<Opcode>(Datum(*_bytecode).u.i);
 	debugCN(5, kDebugScript, "%s ", opcodeToStr(opcode));
+
+	ScriptValue returnValue;
 	switch (opcode) {
 	case kOpcodeAssignVariable: {
-		uint32 id = Datum(*_bytecode).u.i;
-		VariableScope scope = static_cast<VariableScope>(Datum(*_bytecode).u.i);
-		debugC(5, kDebugScript, "%d (%s) ", id, variableScopeToStr(scope));
-		debugCN(5, kDebugScript, "  Value: ");
-		ScriptValue newValue = evaluateExpression();
-
-		putVariable(id, scope, newValue);
-		return ScriptValue();
+		evaluateAssign();
+		return returnValue;
 	}
 
 	case kOpcodeCallFunction: {
@@ -131,7 +128,7 @@ ScriptValue CodeChunk::evaluateOperation() {
 			ScriptValue arg = evaluateExpression();
 			args.push_back(arg);
 		}
-		ScriptValue returnValue = callBuiltInMethod(methodId, selfObject, args);
+		returnValue = callBuiltInMethod(methodId, selfObject, args);
 		return returnValue;
 	}
 
@@ -141,7 +138,7 @@ ScriptValue CodeChunk::evaluateOperation() {
 		assert(_locals == nullptr);
 		_locals = new Common::Array<ScriptValue>(localVariableCount);
 		_weOwnLocals = true;
-		return ScriptValue();
+		return returnValue;
 	}
 
 	case kOpcodeOr: {
@@ -150,19 +147,17 @@ ScriptValue CodeChunk::evaluateOperation() {
 		debugCN(5, kDebugScript, "    rhs: ");
 		ScriptValue value2 = evaluateExpression();
 
-		ScriptValue returnValue(kOperandTypeBool);
-		bool logicalOr = (value1 || value2);
-		returnValue.setToParamToken(static_cast<uint>(logicalOr));
+		returnValue.setToBool(value1 || value2);
 		return returnValue;
 	}
 
-	case kOpcodeNot: {
-		debugCN(5, kDebugScript, "\n    value: ");
-		ScriptValue value = evaluateExpression();
+	case kOpcodeXor: {
+		debugCN(5, kDebugScript, "\n    lhs: ");
+		ScriptValue value1 = evaluateExpression();
+		debugCN(5, kDebugScript, "    rhs: ");
+		ScriptValue value2 = evaluateExpression();
 
-		ScriptValue returnValue(kOperandTypeBool);
-		bool logicalNot = !(static_cast<bool>(value.asParamToken()));
-		returnValue.setToParamToken(static_cast<uint>(logicalNot));
+		returnValue.setToBool(value1 ^ value2);
 		return returnValue;
 	}
 
@@ -172,9 +167,7 @@ ScriptValue CodeChunk::evaluateOperation() {
 		debugCN(5, kDebugScript, "    rhs: ");
 		ScriptValue value2 = evaluateExpression();
 
-		ScriptValue returnValue(kOperandTypeBool);
-		bool logicalAnd = (value1 && value2);
-		returnValue.setToParamToken(static_cast<uint>(logicalAnd));
+		returnValue.setToBool(value1 && value2);
 		return returnValue;
 	}
 
@@ -184,16 +177,13 @@ ScriptValue CodeChunk::evaluateOperation() {
 
 		CodeChunk ifBlock(*_bytecode);
 		CodeChunk elseBlock(*_bytecode);
-		// Doesn't seem like there is a real bool type for values,
-		// ao just get an integer.
-		if (condition.asParamToken()) {
+		if (condition.asBool()) {
 			ifBlock.execute(_args, _locals);
 		} else {
 			elseBlock.execute(_args, _locals);
 		}
 
-		// If blocks themselves shouldn't return anything.
-		return ScriptValue();
+		return returnValue;
 	}
 
 	case kOpcodeEquals: {
@@ -202,9 +192,7 @@ ScriptValue CodeChunk::evaluateOperation() {
 		debugCN(5, kDebugScript, "    rhs: ");
 		ScriptValue value2 = evaluateExpression();
 
-		ScriptValue returnValue(kOperandTypeBool);
-		bool equal = (value1 == value2);
-		returnValue.setToParamToken(static_cast<uint>(equal));
+		returnValue.setToBool(value1 == value2);
 		return returnValue;
 	}
 
@@ -214,9 +202,7 @@ ScriptValue CodeChunk::evaluateOperation() {
 		debugCN(5, kDebugScript, "    rhs: ");
 		ScriptValue value2 = evaluateExpression();
 
-		ScriptValue returnValue(kOperandTypeBool);
-		bool notEqual = !(value1 == value2);
-		returnValue.setToParamToken(static_cast<uint>(notEqual));
+		returnValue.setToBool(!(value1 == value2));
 		return returnValue;
 	}
 
@@ -226,9 +212,7 @@ ScriptValue CodeChunk::evaluateOperation() {
 		debugCN(5, kDebugScript, "    rhs: ");
 		ScriptValue value2 = evaluateExpression();
 
-		ScriptValue returnValue(kOperandTypeBool);
-		bool lessThan = (value1 < value2);
-		returnValue.setToParamToken(static_cast<uint>(lessThan));
+		returnValue.setToBool(value1 < value2);
 		return returnValue;
 	}
 
@@ -238,9 +222,7 @@ ScriptValue CodeChunk::evaluateOperation() {
 		debugCN(5, kDebugScript, "    rhs: ");
 		ScriptValue value2 = evaluateExpression();
 
-		ScriptValue returnValue(kOperandTypeBool);
-		bool greaterThan = (value1 > value2);
-		returnValue.setToParamToken(static_cast<uint>(greaterThan));
+		returnValue.setToBool(value1 > value2);
 		return returnValue;
 	}
 
@@ -250,9 +232,7 @@ ScriptValue CodeChunk::evaluateOperation() {
 		debugCN(5, kDebugScript, "    rhs: ");
 		ScriptValue value2 = evaluateExpression();
 
-		ScriptValue returnValue(kOperandTypeBool);
-		bool lessThanOrEqualTo = (value1 < value2) || (value1 == value2);
-		returnValue.setToParamToken(static_cast<uint>(lessThanOrEqualTo));
+		returnValue.setToBool((value1 < value2) || (value1 == value2));
 		return returnValue;
 	}
 
@@ -262,9 +242,7 @@ ScriptValue CodeChunk::evaluateOperation() {
 		debugCN(5, kDebugScript, "    rhs: ");
 		ScriptValue value2 = evaluateExpression();
 
-		ScriptValue returnValue(kOperandTypeBool);
-		bool greaterThanOrEqualTo = (value1 > value2) || (value1 == value2);
-		returnValue.setToParamToken(static_cast<uint>(greaterThanOrEqualTo));
+		returnValue.setToBool((value1 > value2) || (value1 == value2));
 		return returnValue;
 	}
 
@@ -274,7 +252,7 @@ ScriptValue CodeChunk::evaluateOperation() {
 		debugCN(5, kDebugScript, "    rhs: ");
 		ScriptValue value2 = evaluateExpression();
 
-		ScriptValue returnValue = value1 + value2;
+		returnValue = value1 + value2;
 		return returnValue;
 	}
 
@@ -284,7 +262,7 @@ ScriptValue CodeChunk::evaluateOperation() {
 		debugCN(5, kDebugScript, "    rhs: ");
 		ScriptValue value2 = evaluateExpression();
 
-		ScriptValue returnValue = value1 - value2;
+		returnValue = value1 - value2;
 		return returnValue;
 	}
 
@@ -294,7 +272,7 @@ ScriptValue CodeChunk::evaluateOperation() {
 		debugCN(5, kDebugScript, "    rhs: ");
 		ScriptValue value2 = evaluateExpression();
 
-		ScriptValue returnValue = value1 * value2;
+		returnValue = value1 * value2;
 		return returnValue;
 	}
 
@@ -304,7 +282,7 @@ ScriptValue CodeChunk::evaluateOperation() {
 		debugCN(5, kDebugScript, "    rhs: ");
 		ScriptValue value2 = evaluateExpression();
 
-		ScriptValue returnValue = value1 / value2;
+		returnValue = value1 / value2;
 		return returnValue;
 	}
 
@@ -314,7 +292,7 @@ ScriptValue CodeChunk::evaluateOperation() {
 		debugCN(5, kDebugScript, "    rhs: ");
 		ScriptValue value2 = evaluateExpression();
 
-		ScriptValue returnValue = value1 % value2;
+		returnValue = value1 % value2;
 		return returnValue;
 	}
 
@@ -336,7 +314,7 @@ ScriptValue CodeChunk::evaluateOperation() {
 		uint parameterCount = Datum(*_bytecode).u.i;
 		ScriptValue variable = evaluateExpression();
 		uint functionId = variable.asFunctionId();
-		debugC(5, kDebugScript, "Variable %d [function %d] (%d params)", variable.getVariable()->_id, functionId, parameterCount);
+		debugC(5, kDebugScript, "[Indirect function %d] (%d params)", functionId, parameterCount);
 
 		return callFunction(functionId, parameterCount);
 	}
@@ -350,15 +328,15 @@ ScriptValue CodeChunk::evaluateValue() {
 	OperandType operandType = static_cast<OperandType>(Datum(*_bytecode).u.i);
 	debugCN(5, kDebugScript, "%s ", operandTypeToStr(operandType));
 
-	ScriptValue returnValue(operandType);
+	ScriptValue returnValue;
 	switch (operandType) {
 	case kOperandTypeBool: {
 		int b = Datum(*_bytecode).u.i;
 		if (b != 0 && b != 1) {
-			error("Got invalid boolean value %d", b);
+			error("Got invalid literal bool value %d", b);
 		}
 		debugC(5, kDebugScript, "%d ", b);
-		returnValue.setToParamToken(b == 1 ? true : false);
+		returnValue.setToBool(b == 1 ? true : false);
 		return returnValue;
 	}
 
@@ -372,21 +350,17 @@ ScriptValue CodeChunk::evaluateValue() {
 	case kOperandTypeInt: {
 		int i = Datum(*_bytecode).u.i;
 		debugC(5, kDebugScript, "%d ", i);
-		returnValue.setToParamToken(i);
+		// Ints are stored internally as doubles.
+		returnValue.setToFloat(static_cast<double>(i));
 		return returnValue;
 	}
 
 	case kOperandTypeString: {
 		// This is indeed a raw string, not a string wrapped in a datum!
-		// TODO: This copies the string. Can we read it directly from the chunk?
-		int size = Datum(*_bytecode, kDatumTypeUint16_1).u.i;
-		char *buffer = new char[size + 1];
-		_bytecode->read(buffer, size);
-		buffer[size] = '\0';
-		Common::String *string = new Common::String(buffer);
-		debugC(5, kDebugScript, "%s ", string->c_str());
+		uint size = Datum(*_bytecode, kDatumTypeUint16_1).u.i;
+		Common::String string = _bytecode->readString('\0', size);
+		debugC(5, kDebugScript, "%s ", string.c_str());
 		returnValue.setToString(string);
-		delete[] buffer;
 		return returnValue;
 	}
 
@@ -407,13 +381,13 @@ ScriptValue CodeChunk::evaluateValue() {
 	case kOperandTypeTime: {
 		double d = Datum(*_bytecode).u.f;
 		debugC(5, kDebugScript, "%f ", d);
-		returnValue.setToFloat(d);
+		returnValue.setToTime(d);
 		return returnValue;
 	}
 
 	case kOperandTypeVariable: {
-		// TODO: Implement this as we go through the re-architecting.
-		error("kOperandTypeVariable not implemented yet");
+		returnValue = ScriptValue(_bytecode);
+		return returnValue;
 	}
 
 	case kOperandTypeFunctionId: {
@@ -436,11 +410,8 @@ ScriptValue CodeChunk::evaluateValue() {
 }
 
 ScriptValue CodeChunk::evaluateVariable() {
-	uint32 id = Datum(*_bytecode).u.i;
-	VariableScope scope = static_cast<VariableScope>(Datum(*_bytecode).u.i);
-	debugC(5, kDebugScript, "Variable %d (%s)", id, variableScopeToStr(scope));
-	ScriptValue variable = getVariable(id, scope);
-	return variable;
+	ScriptValue *variable = readAndReturnVariable();
+	return *variable;
 }
 
 ScriptValue CodeChunk::callFunction(uint functionId, uint parameterCount) {
@@ -466,98 +437,103 @@ ScriptValue CodeChunk::callFunction(uint functionId, uint parameterCount) {
 	return returnValue;
 }
 
-ScriptValue CodeChunk::getVariable(uint32 id, VariableScope scope) {
+ScriptValue *CodeChunk::readAndReturnVariable() {
+	uint id = Datum(*_bytecode).u.i;
+	VariableScope scope = static_cast<VariableScope>(Datum(*_bytecode).u.i);
+	debugC(5, kDebugScript, "%d (%s)", id, variableScopeToStr(scope));
+
+	ScriptValue returnValue;
 	switch (scope) {
 	case kVariableScopeGlobal: {
-		ScriptValue returnValue(kOperandTypeVariable);
-		Variable *variable = g_engine->_variables.getVal(id);
-		returnValue.putVariable(variable);
-		return returnValue;
+		ScriptValue *variable = g_engine->getVariable(id);
+		if (variable == nullptr) {
+			error("Global variable %d doesn't exist", id);
+		}
+		return variable;
 	}
 
 	case kVariableScopeLocal: {
 		uint index = id - 1;
-		return _locals->operator[](index);
+		return &_locals->operator[](index);
+	}
+
+	case kVariableScopeIndirectParameter: {
+		ScriptValue indexValue = evaluateExpression();
+		uint index = static_cast<uint>(indexValue.asFloat() + id);
+		return &_args->operator[](index);
 	}
 
 	case kVariableScopeParameter: {
-		uint32 index = id - 1;
+		uint index = id - 1;
 		if (_args == nullptr) {
-			error("CodeChunk::getVariable(): Requested a parameter in a code chunk that has no parameters");
+			error("Requested a parameter in a code chunk that has no parameters");
 		}
-		return _args->operator[](index);
+		return &_args->operator[](index);
 	}
 
 	default:
-		error("CodeChunk::getVariable(): Got unimplemented variable scope %s (%d)", variableScopeToStr(scope), static_cast<uint>(scope));
+		error("Got unknown variable scope %s (%d)", variableScopeToStr(scope), static_cast<uint>(scope));
 	}
 }
 
-void CodeChunk::putVariable(uint32 id, VariableScope scope, ScriptValue &value) {
-	switch (scope) {
-	case kVariableScopeGlobal: {
-		Variable *variable = g_engine->_variables.getVal(id);
-		if (variable == nullptr) {
-			error("CodeChunk::putVariable(): Attempted to assign to a non-existent global variable %d", id);
-		}
-		variable->putValue(value);
-		break;
-	}
+ScriptValue CodeChunk::evaluateAssign() {
+	debugCN(5, kDebugScript, "Variable ");
+	ScriptValue *targetVariable = readAndReturnVariable();
 
-	case kVariableScopeLocal: {
-		uint index = id - 1;
-		_locals->operator[](index) = value;
-		break;
-	}
+	debugC(5, kDebugScript, "  Value: ");
+	ScriptValue value = evaluateExpression();
 
-	case kVariableScopeParameter: {
-		error("CodeChunk::putVariable(): Attempted to assign to a parameter");
-		break;
+	if (value.getType() == kScriptValueTypeEmpty) {
+		error("Attempt to assign an empty value to a variable");
 	}
 
-	default:
-		error("CodeChunk::getVariable(): Got unimplemented variable scope %s (%d)", variableScopeToStr(scope), static_cast<uint>(scope));
+	if (targetVariable != nullptr) {
+		*targetVariable = value;
+		return value;
+	} else {
+		error("Attempt to assign to null variable");
 	}
 }
 
 ScriptValue CodeChunk::callBuiltInMethod(BuiltInMethod method, ScriptValue &self, Common::Array<ScriptValue> &args) {
-	ScriptValue literalSelf = self.getLiteralValue();
-	OperandType literalType = literalSelf.getType();
-	switch (literalType) {
-	case kOperandTypeAssetId: {
+	ScriptValue returnValue;
+
+	switch (self.getType()) {
+	case kScriptValueTypeAssetId: {
 		if (self.asAssetId() == 1) {
 			// This is a "document" method that we need to handle specially.
 			// The document (@doc) accepts engine-level methods like changing the
 			// active screen.
 			// HACK: This is so we don't have to implement a separate document class
 			// just to house these methods. Rather, we just call in the engine.
-			ScriptValue returnValue = g_engine->callMethod(method, args);
+			returnValue = g_engine->callMethod(method, args);
 			return returnValue;
 		} else if (self.asAssetId() == 0) {
 			// It seems to be valid to call a method on a null asset ID, in
 			// which case nothing happens. Still issue warning for traceability.
 			warning("CodeChunk::callBuiltInMethod(): Attempt to call method on a null asset ID");
-			return ScriptValue();
+			return returnValue;
 		} else {
 			// This is a regular asset that we can process directly.
-			Asset *selfAsset = self.getAsset();
+			uint assetId = self.asAssetId();
+			Asset *selfAsset = g_engine->getAssetById(assetId);
 			if (selfAsset == nullptr) {
 				error("CodeChunk::callBuiltInMethod(): Attempt to call method on asset ID %d, which isn't loaded", self.asAssetId());
 			}
-			ScriptValue returnValue = selfAsset->callMethod(method, args);
+			returnValue = selfAsset->callMethod(method, args);
 			return returnValue;
 		}
 	}
 
-	case kOperandTypeCollection: {
-		Common::SharedPtr<Collection> collection = literalSelf.asCollection();
-		ScriptValue returnValue = collection->callMethod(method, args);
+	case kScriptValueTypeCollection: {
+		Common::SharedPtr<Collection> collection = self.asCollection();
+		returnValue = collection->callMethod(method, args);
 		return returnValue;
 	}
 
 	default:
 		error("CodeChunk::callBuiltInMethod(): Attempt to call method on unimplemented ScriptValue type %s (%d)",
-			operandTypeToStr(literalType), static_cast<uint>(literalType));
+			scriptValueTypeToStr(self.getType()), static_cast<uint>(self.getType()));
 	}
 }
 
diff --git a/engines/mediastation/mediascript/codechunk.h b/engines/mediastation/mediascript/codechunk.h
index 23606e7e642..49abfc8e0e2 100644
--- a/engines/mediastation/mediascript/codechunk.h
+++ b/engines/mediastation/mediascript/codechunk.h
@@ -26,7 +26,6 @@
 #include "common/stream.h"
 
 #include "mediastation/datafile.h"
-#include "mediastation/mediascript/variable.h"
 #include "mediastation/mediascript/scriptvalue.h"
 #include "mediastation/mediascript/scriptconstants.h"
 
@@ -48,8 +47,9 @@ private:
 	ScriptValue evaluateVariable();
 
 	ScriptValue callFunction(uint functionId, uint parameterCount);
-	ScriptValue getVariable(uint32 id, VariableScope scope);
-	void putVariable(uint32 id, VariableScope scope, ScriptValue &value);
+	ScriptValue *readAndReturnVariable();
+
+	ScriptValue evaluateAssign();
 
 	bool _weOwnLocals = false;
 	Common::Array<ScriptValue> *_locals = nullptr;
diff --git a/engines/mediastation/mediascript/collection.cpp b/engines/mediastation/mediascript/collection.cpp
new file mode 100644
index 00000000000..5c591c239eb
--- /dev/null
+++ b/engines/mediastation/mediascript/collection.cpp
@@ -0,0 +1,129 @@
+/* ScummVM - Graphic Adventure Engine
+ *
+ * ScummVM is the legal property of its developers, whose names
+ * are too numerous to list here. Please refer to the COPYRIGHT
+ * file distributed with this source distribution.
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#include "mediastation/mediastation.h"
+#include "mediastation/mediascript/collection.h"
+#include "mediastation/mediascript/scriptvalue.h"
+#include "mediastation/mediascript/codechunk.h"
+#include "mediastation/datum.h"
+#include "mediastation/debugchannels.h"
+
+namespace MediaStation {
+
+ScriptValue Collection::callMethod(BuiltInMethod method, Common::Array<ScriptValue> &args) {
+	ScriptValue returnValue;
+
+	switch (method) {
+	case kIsEmptyMethod: {
+		returnValue.setToBool(empty());
+		return returnValue;
+	}
+
+	case kAppendMethod: {
+		for (ScriptValue arg : args) {
+			push_back(arg);
+		}
+		return returnValue;
+	}
+
+	case kDeleteFirstMethod: {
+		returnValue = remove_at(0);
+		return returnValue;
+	}
+
+	case kDeleteAtMethod: {
+		// Find the item in the collection, then remove and return it.
+		assert(args.size() == 1);
+		for (uint i = 0; i < size(); i++) {
+			if (args[0] == operator[](i)) {
+				returnValue = remove_at(i);
+				return returnValue;
+			}
+		}
+
+		// The item wasn't found.
+		return returnValue;
+	}
+
+	case kCountMethod: {
+		double size = static_cast<double>(this->size());
+		returnValue.setToFloat(size);
+		return returnValue;
+	}
+
+	case kGetAtMethod: {
+		assert(args.size() == 1);
+		uint index = static_cast<uint>(args[0].asFloat());
+		returnValue = operator[](index);
+		return returnValue;
+	}
+
+	case kSendMethod: {
+		// Call a method on each item in the collection.
+		BuiltInMethod methodToSend = static_cast<BuiltInMethod>(args[0].asMethodId());
+		Common::Array<ScriptValue> sendArgs;
+		for (uint i = 0; i < size(); i++) {
+			ScriptValue self = operator[](i);
+			CodeChunk::callBuiltInMethod(methodToSend, self, sendArgs);
+		}
+		return returnValue;
+	}
+
+	case kSeekMethod: {
+		// Find the item in the collection if it exists.
+		assert(args.size() == 1);
+		for (uint i = 0; i < size(); i++) {
+			if (args[0] == operator[](i)) {
+				return operator[](i);
+			}
+		}
+
+		// The item wasn't found.
+		returnValue.setToFloat(-1.0);
+		return returnValue;
+	}
+
+	case kJumbleMethod: {
+		// Scramble the items in the collection.
+		for (uint i = size() - 1; i > 0; --i) {
+			uint j = g_engine->_randomSource.getRandomNumber(size() - 1);
+			SWAP(operator[](i), operator[](j));
+		}
+		return returnValue;
+	}
+
+	case kSortMethod: {
+		assert(args.empty());
+		Common::sort(begin(), end());
+		return returnValue;
+	}
+
+	case kEmptyMethod: {
+		clear();
+		return returnValue;
+	}
+
+	default:
+		error("Collection::callMethod(): Attempt to call unimplemented method %s (%d)", builtInMethodToStr(method), static_cast<uint>(method));
+	}
+}
+
+} // End of namespace MediaStation
diff --git a/engines/mediastation/mediascript/variable.h b/engines/mediastation/mediascript/collection.h
similarity index 71%
rename from engines/mediastation/mediascript/variable.h
rename to engines/mediastation/mediascript/collection.h
index ec445656b0d..8788b08d9d9 100644
--- a/engines/mediastation/mediascript/variable.h
+++ b/engines/mediastation/mediascript/collection.h
@@ -19,8 +19,8 @@
  *
  */
 
-#ifndef MEDIASTATION_MEDIASCRIPT_VARIABLE_DECLARATION_H
-#define MEDIASTATION_MEDIASCRIPT_VARIABLE_DECLARATION_H
+#ifndef MEDIASTATION_MEDIASCRIPT_COLLECTION_H
+#define MEDIASTATION_MEDIASCRIPT_COLLECTION_H
 
 #include "common/ptr.h"
 #include "common/str.h"
@@ -39,30 +39,6 @@ public:
 	ScriptValue callMethod(BuiltInMethod method, Common::Array<ScriptValue> &args);
 };
 
-class Variable {
-public:
-	uint32 _id = 0;
-	ScriptValueType _type = kScriptValueTypeEmpty;
-	union {
-		Common::String *string;
-		uint functionId;
-		int i;
-		double d;
-		uint assetId;
-	} _value;
-	Common::SharedPtr<Collection> _c;
-
-	Variable();
-	Variable(Chunk &chunk, bool readId = true);
-	~Variable();
-
-	ScriptValue getValue();
-	void putValue(ScriptValue value);
-
-private:
-	void clear();
-};
-
 } // End of namespace MediaStation
 
 #endif
\ No newline at end of file
diff --git a/engines/mediastation/mediascript/scriptconstants.cpp b/engines/mediastation/mediascript/scriptconstants.cpp
index 714fc08f105..9e202b3054f 100644
--- a/engines/mediastation/mediascript/scriptconstants.cpp
+++ b/engines/mediastation/mediascript/scriptconstants.cpp
@@ -46,8 +46,8 @@ const char *opcodeToStr(Opcode opcode) {
 		return "AssignVariable";
 	case kOpcodeOr:
 		return "Or";
-	case kOpcodeNot:
-		return "Not";
+	case kOpcodeXor:
+		return "Xor";
 	case kOpcodeAnd:
 		return "And";
 	case kOpcodeEquals:
diff --git a/engines/mediastation/mediascript/scriptconstants.h b/engines/mediastation/mediascript/scriptconstants.h
index 0c6e3b300eb..8e1d51a0562 100644
--- a/engines/mediastation/mediascript/scriptconstants.h
+++ b/engines/mediastation/mediascript/scriptconstants.h
@@ -36,7 +36,7 @@ enum Opcode {
 	kOpcodeIfElse = 202,
 	kOpcodeAssignVariable = 203,
 	kOpcodeOr = 204,
-	kOpcodeNot = 205,
+	kOpcodeXor = 205,
 	kOpcodeAnd = 206,
 	kOpcodeEquals = 207,
 	kOpcodeNotEquals = 208,
@@ -87,6 +87,7 @@ enum BuiltInFunction {
 const char *builtInFunctionToStr(BuiltInFunction function);
 
 enum BuiltInMethod {
+	kInvalidMethod = 0,
 	// TODO: What object types does CursorSet apply to?
 	// Currently it's only in var_7be1_cursor_currentTool in
 	// IBM/Crayola.
diff --git a/engines/mediastation/mediascript/scriptvalue.cpp b/engines/mediastation/mediascript/scriptvalue.cpp
index cdc130ac9fb..6e411caf0b6 100644
--- a/engines/mediastation/mediascript/scriptvalue.cpp
+++ b/engines/mediastation/mediascript/scriptvalue.cpp
@@ -19,565 +19,527 @@
  *
  */
 
-#include "mediastation/mediastation.h"
+#include "mediastation/datum.h"
 #include "mediastation/mediascript/scriptvalue.h"
 #include "mediastation/mediascript/function.h"
 
 namespace MediaStation {
 
-void ScriptValue::setToParamToken(int i) {
+ScriptValue::ScriptValue(Common::SeekableReadStream *stream) {
+	_type = static_cast<ScriptValueType>(Datum(*stream).u.i);
+
 	switch (_type) {
-	case kOperandTypeBool:
-	case kOperandTypeInt:
-	case kOperandTypeParamToken: {
-		_u.i = i;
+	case kScriptValueTypeEmpty:
+		break;
+
+	case kScriptValueTypeFloat: {
+		double d = Datum(*stream).u.f;
+		setToFloat(d);
 		break;
 	}
 
-	case kOperandTypeVariable: {
-		_u.variable->_value.i = i;
+	case kScriptValueTypeBool: {
+		uint rawValue = Datum(*stream, kDatumTypeUint8).u.i;
+		if (rawValue != 0 && rawValue != 1) {
+			error("Got invalid literal bool value %d", rawValue);
+		}
+		setToBool(rawValue);
 		break;
 	}
 
-	default:
-		error("ScriptValue::putInteger(): Attempt to put integer into ScriptValue type %s (%d)",
-			operandTypeToStr(_type), static_cast<uint>(_type));
+	case kScriptValueTypeTime: {
+		double d = Datum(*stream).u.f;
+		setToFloat(d);
+		break;
 	}
-}
 
-int ScriptValue::asParamToken() {
-	switch (_type) {
-	case kOperandTypeBool:
-	case kOperandTypeInt:
-	case kOperandTypeParamToken: {
-		return _u.i;
+	case kScriptValueTypeParamToken: {
+		uint paramToken = Datum(*stream).u.i;
+		setToParamToken(paramToken);
+		break;
 	}
 
-	case kOperandTypeTime: {
-		return static_cast<int>(_u.d);
+	case kScriptValueTypeAssetId: {
+		uint assetId = Datum(*stream).u.i;
+		setToAssetId(assetId);
+		break;
 	}
 
-	case kOperandTypeVariable: {
-		return _u.variable->_value.i;
+	case kScriptValueTypeString: {
+		uint size = Datum(*stream).u.i;
+		Common::String string = stream->readString('\0', size);
+		setToString(string);
+		break;
 	}
 
-	default:
-		error("ScriptValue::getInteger(): Attempt to get integer from ScriptValue type %s (%d)",
-			operandTypeToStr(_type), static_cast<uint>(_type));
+	case kScriptValueTypeCollection: {
+		uint totalItems = Datum(*stream).u.i;
+		Common::SharedPtr<Collection> collection(new Collection);
+		for (uint i = 0; i < totalItems; i++) {
+			ScriptValue collectionValue = ScriptValue(stream);
+			collection->push_back(collectionValue);
+		}
+		setToCollection(collection);
+		break;
 	}
-}
 
-void ScriptValue::setToFloat(double d) {
-	switch (_type) {
-	case kOperandTypeTime:
-	case kOperandTypeFloat: {
-		_u.d = d;
+	case kScriptValueTypeFunctionId: {
+		uint functionId = Datum(*stream).u.i;
+		setToFunctionId(functionId);
 		break;
 	}
 
-	case kOperandTypeVariable: {
-		// TODO: Add assertion.
-		_u.variable->_value.d = d;
+	case kScriptValueTypeMethodId: {
+		BuiltInMethod methodId = static_cast<BuiltInMethod>(Datum(*stream).u.i);
+		setToMethodId(methodId);
 		break;
 	}
 
 	default:
-		error("ScriptValue::setToFloat(): Attempt to put double into ScriptValue type %s (%d)",
-			operandTypeToStr(_type), static_cast<uint>(_type));
+		error("Got unknown script value type %s", scriptValueTypeToStr(_type));
 	}
 }
 
-double ScriptValue::asFloat() {
-	switch (_type) {
-	case kOperandTypeTime:
-	case kOperandTypeFloat: {
-		return _u.d;
-	}
+void ScriptValue::setToFloat(double d) {
+	_type = kScriptValueTypeFloat;
+	_u.d = d;
+}
 
-	case kOperandTypeBool:
-	case kOperandTypeInt: {
-		return static_cast<double>(_u.i);
+double ScriptValue::asFloat() const {
+	if (_type == kScriptValueTypeFloat) {
+		return _u.d;
+	} else {
+		issueValueMismatchWarning(kScriptValueTypeFloat);
+		return 0.0;
 	}
+}
 
-	case kOperandTypeVariable: {
-		// TODO: Add assertion that this is the proper type.
-		return _u.variable->_value.d;
-	}
+void ScriptValue::setToBool(bool b) {
+	_type = kScriptValueTypeBool;
+	_u.b = b;
+}
 
-	default:
-		error("ScriptValue::asFloat(): Attempt to get double from ScriptValue type %s (%d)",
-			operandTypeToStr(_type), static_cast<uint>(_type));
+bool ScriptValue::asBool() const {
+	if (_type == kScriptValueTypeBool) {
+		return _u.b;
+	} else {
+		issueValueMismatchWarning(kScriptValueTypeBool);
+		return false;
 	}
 }
 
-void ScriptValue::setToString(Common::String *string) {
-	switch (_type) {
-	case kOperandTypeString: {
-		_u.string = string;
-		break;
-	}
+void ScriptValue::setToTime(double d) {
+	_type = kScriptValueTypeTime;
+	_u.d = d;
+}
 
-	case kOperandTypeVariable: {
-		assert(_u.variable->_type == kScriptValueTypeString);
-		_u.variable->_value.string = string;
-		break;
+double ScriptValue::asTime() const {
+	if (_type == kScriptValueTypeTime) {
+		return _u.d;
+	} else {
+		issueValueMismatchWarning(kScriptValueTypeTime);
+		return 0.0;
 	}
+}
 
-	default:
-		error("ScriptValue::setToString(): Attempt to put string into ScriptValue type %s (%d)",
-			operandTypeToStr(_type), static_cast<uint>(_type));
-	}
+void ScriptValue::setToParamToken(uint paramToken) {
+	_type = kScriptValueTypeParamToken;
+	_u.paramToken = paramToken;
 }
 
-Common::String *ScriptValue::asString() {
-	switch (_type) {
-	case kOperandTypeString: {
-		return _u.string;
+uint ScriptValue::asParamToken() const {
+	if (_type == kScriptValueTypeParamToken) {
+		return _u.paramToken;
+	} else {
+		issueValueMismatchWarning(kScriptValueTypeParamToken);
+		return 0;
 	}
+}
 
-	case kOperandTypeVariable: {
-		assert(_u.variable->_type == kScriptValueTypeString);
-		return _u.variable->_value.string;
-	}
+void ScriptValue::setToAssetId(uint assetId) {
+	_type = kScriptValueTypeAssetId;
+	_u.assetId = assetId;
+}
 
-	default:
-		error("ScriptValue::asString(): Attempt to get string from ScriptValue type %s (%d)",
-			operandTypeToStr(_type), static_cast<uint>(_type));
+uint ScriptValue::asAssetId() const {
+	if (_type == kScriptValueTypeAssetId) {
+		return _u.assetId;
+	} else {
+		issueValueMismatchWarning(kScriptValueTypeAssetId);
+		return 0;
 	}
 }
 
-void ScriptValue::putVariable(Variable *variable) {
-	switch (_type) {
-	case kOperandTypeVariable: {
-		_u.variable = variable;
-		break;
-	}
+void ScriptValue::setToString(const Common::String &string) {
+	_type = kScriptValueTypeString;
+	_string = string;
+}
 
-	default:
-		error("ScriptValue::putVariable(): Attempt to put variable into ScriptValue type %s (%d)",
-			operandTypeToStr(_type), static_cast<uint>(_type));
+Common::String ScriptValue::asString() const {
+	if (_type == kScriptValueTypeString) {
+		return _string;
+	} else {
+		return Common::String("");
 	}
 }
 
-Variable *ScriptValue::getVariable() {
-	switch (_type) {
-	case kOperandTypeVariable: {
-		return _u.variable;
-	}
+void ScriptValue::setToCollection(Common::SharedPtr<Collection> collection) {
+	_type = kScriptValueTypeCollection;
+	_collection = collection;
+}
 
-	default:
-		error("ScriptValue::getVariable(): Attempt to get variable from ScriptValue type %s (%d)",
-			operandTypeToStr(_type), static_cast<uint>(_type));
+Common::SharedPtr<Collection> ScriptValue::asCollection() const {
+	if (_type == kScriptValueTypeCollection) {
+		return _collection;
+	} else {
+		issueValueMismatchWarning(kScriptValueTypeCollection);
+		return nullptr;
 	}
 }
 
 void ScriptValue::setToFunctionId(uint functionId) {
-	switch (_type) {
-	case kOperandTypeFunctionId: {
-		_u.functionId = functionId;
-		break;
-	}
-
-	default:
-		error("ScriptValue::setToFunctionId(): Attempt to put function ID into ScriptValue type %s (%d)",
-			operandTypeToStr(_type), static_cast<uint>(_type));
-	}
+	_type = kScriptValueTypeFunctionId;
+	_u.functionId = functionId;
 }
 
-uint ScriptValue::asFunctionId() {
-	switch (_type) {
-	case kOperandTypeFunctionId: {
+uint ScriptValue::asFunctionId() const {
+	if (_type == kScriptValueTypeFunctionId) {
 		return _u.functionId;
-	}
-
-	case kOperandTypeVariable: {
-		assert(_u.variable->_type == kScriptValueTypeFunctionId);
-		return _u.variable->_value.functionId;
-	}
-
-	default:
-		error("ScriptValue::getFunction(): Attempt to get function ID from ScriptValue type %s (%d)",
-			operandTypeToStr(_type), static_cast<uint>(_type));
+	} else {
+		issueValueMismatchWarning(kScriptValueTypeFunctionId);
+		return 0;
 	}
 }
 
 void ScriptValue::setToMethodId(BuiltInMethod methodId) {
-	switch (_type) {
-	case kOperandTypeMethodId: {
-		_u.methodId = methodId;
-		break;
-	}
-
-	default:
-		error("ScriptValue::setToFunctionId(): Attempt to put method ID into ScriptValue type %s (%d)",
-			operandTypeToStr(_type), static_cast<uint>(_type));
-	}
+	_type = kScriptValueTypeMethodId;
+	_u.methodId = methodId;
 }
 
-BuiltInMethod ScriptValue::asMethodId() {
-	switch (_type) {
-	case kOperandTypeMethodId: {
+BuiltInMethod ScriptValue::asMethodId() const {
+	if (_type == kScriptValueTypeMethodId) {
 		return _u.methodId;
+	} else {
+		issueValueMismatchWarning(kScriptValueTypeMethodId);
+		return kInvalidMethod;
 	}
+}
 
-	default:
-		error("ScriptValue::getFunction(): Attempt to get method ID from ScriptValue type %s (%d)",
-			operandTypeToStr(_type), static_cast<uint>(_type));
+bool ScriptValue::compare(Opcode op, const ScriptValue &lhs, const ScriptValue &rhs) {
+	if (lhs.getType() != rhs.getType()) {
+		error("Attempt to compare mismatched types %s and %s",
+		      scriptValueTypeToStr(lhs.getType()), scriptValueTypeToStr(rhs.getType()));
 	}
-}
 
-void ScriptValue::setToAssetId(uint32 assetId) {
-	switch (_type) {
-	case kOperandTypeAssetId: {
-		_u.assetId = assetId;
+	switch (lhs.getType()) {
+	case kScriptValueTypeEmpty:
+		return compareEmptyValues(op);
+
+	case kScriptValueTypeFloat:
+		return compare(op, lhs.asFloat(), rhs.asFloat());
 		break;
-	}
 
-	case kOperandTypeVariable: {
-		assert(_u.variable->_type == kScriptValueTypeAssetId);
-		_u.variable->_value.assetId = assetId;
+	case kScriptValueTypeBool:
+		return compare(op, lhs.asBool(), rhs.asBool());
+		break;
+
+	case kScriptValueTypeTime:
+		return compare(op, lhs.asTime(), rhs.asTime());
+		break;
+
+	case kScriptValueTypeParamToken:
+		return compare(op, lhs.asParamToken(), rhs.asParamToken());
+		break;
+
+	case kScriptValueTypeAssetId:
+		return compare(op, lhs.asAssetId(), rhs.asAssetId());
+		break;
+
+	case kScriptValueTypeString:
+		return compareStrings(op, lhs.asString(), rhs.asString());
+		break;
+
+	case kScriptValueTypeCollection:
+		return compare(op, lhs.asCollection(), rhs.asCollection());
+		break;
+
+	case kScriptValueTypeFunctionId:
+		return compare(op, lhs.asFunctionId(), rhs.asFunctionId());
+		break;
+
+	case kScriptValueTypeMethodId:
+		return compare(op, static_cast<uint>(lhs.asMethodId()), static_cast<uint>(rhs.asMethodId()));
 		break;
-	}
 
 	default:
-		error("ScriptValue::setToAssetId(): Attempt to put asset ID into ScriptValue type %s (%d)",
-			operandTypeToStr(_type), static_cast<uint>(_type));
+		error("Got unknown script value type %d", lhs.getType());
 	}
 }
 
-Asset *ScriptValue::getAsset() {
-	switch (_type) {
-	case kOperandTypeAssetId: {
-		if (_u.assetId == 0) {
-			return nullptr;
-		} else {
-			return g_engine->getAssetById(_u.assetId);
-		}
-	}
+bool ScriptValue::compareEmptyValues(Opcode op) {
+	// Empty values are considered equal.
+	switch (op) {
+	case kOpcodeEquals:
+		return true;
 
-	case kOperandTypeVariable: {
-		assert(_u.variable->_type == kScriptValueTypeAssetId);
-		return g_engine->getAssetById(_u.variable->_value.assetId);
-	}
+	case kOpcodeNotEquals:
+		return false;
 
 	default:
-		error("ScriptValue::getAsset(): Attempt to get asset from ScriptValue type %s (%d)",
-			operandTypeToStr(_type), static_cast<uint>(_type));
+		error("Got invalid empty value operation %s", opcodeToStr(op));
 	}
 }
 
-uint32 ScriptValue::asAssetId() {
-	switch (_type) {
-	case kOperandTypeAssetId: {
-		return _u.assetId;
-	}
+bool ScriptValue::compareStrings(Opcode op, const Common::String &left, const Common::String &right) {
+	switch (op) {
+	case kOpcodeEquals:
+		return (left == right);
 
-	case kOperandTypeVariable: {
-		assert(_u.variable->_type == kScriptValueTypeAssetId);
-		return _u.variable->_value.assetId;
-	}
+	case kOpcodeNotEquals:
+		return (left != right);
+
+	case kOpcodeLessThan:
+		return (left < right);
+
+	case kOpcodeGreaterThan:
+		return (left > right);
+
+	case kOpcodeLessThanOrEqualTo:
+		return (left <= right);
+
+	case kOpcodeGreaterThanOrEqualTo:
+		return (left >= right);
 
 	default:
-		error("ScriptValue::getAssetId(): Attempt to get asset ID from ScriptValue type %s (%d)",
-			operandTypeToStr(_type), static_cast<uint>(_type));
+		error("Got invalid string operation %s", opcodeToStr(op));
 	}
 }
 
-void ScriptValue::setToCollection(Common::SharedPtr<Collection> collection) {
-	switch (_type) {
-	case kOperandTypeCollection: {
-		_collection = collection;
-		break;
-	}
+bool ScriptValue::compare(Opcode op, uint left, uint right) {
+	switch (op) {
+	case kOpcodeEquals:
+		return (left == right);
 
-	case kOperandTypeVariable: {
-		assert(_u.variable->_type == kScriptValueTypeCollection);
-		_u.variable->_c = collection;
-		break;
-	}
+	case kOpcodeNotEquals:
+		return (left != right);
 
 	default:
-		error("ScriptValue::setToCollection(): Attempt to put collection into ScriptValue type %s (%d)",
-			operandTypeToStr(_type), static_cast<uint>(_type));
+		error("Got invalid param token operation %s", opcodeToStr(op));
 	}
 }
 
-Common::SharedPtr<Collection> ScriptValue::asCollection() {
-	switch (_type) {
-	case kOperandTypeCollection: {
-		return _collection;
-	}
+bool ScriptValue::compare(Opcode op, bool left, bool right) {
+	switch (op) {
+	case kOpcodeEquals:
+		return (left == right);
 
-	case kOperandTypeVariable: {
-		assert(_u.variable->_type == kScriptValueTypeCollection);
-		return _u.variable->_c;
-	}
+	case kOpcodeNotEquals:
+		return (left != right);
 
 	default:
-		error("ScriptValue::asCollection(): Attempt to get collection from ScriptValue type %s (%d)",
-			operandTypeToStr(_type), static_cast<uint>(_type));
+		error("Got invalid bool operation %s", opcodeToStr(op));
 	}
 }
 
-ScriptValue ScriptValue::getLiteralValue() const {
-	// This function dereferences any variable to get the actual
-	// "direct" value (a literal asset ID or otherwise).
-	if (_type == kOperandTypeVariable) {
-		return _u.variable->getValue();
-	} else {
-		return *this;
-	}
-}
+bool ScriptValue::compare(Opcode op, double left, double right) {
+	switch (op) {
+	case kOpcodeEquals:
+		return (left == right);
 
-bool ScriptValue::operator==(const ScriptValue &other) const {
-	ScriptValue lhs = getLiteralValue();
-	ScriptValue rhs = other.getLiteralValue();
-
-	if (lhs.isDouble() || rhs.isDouble()) {
-		// If either ScriptValue is a double, perform double comparison.
-		double lhsValue = lhs.isDouble() ? lhs.asFloat() : static_cast<double>(lhs.asParamToken());
-		double rhsValue = rhs.isDouble() ? rhs.asFloat() : static_cast<double>(rhs.asParamToken());
-		return lhsValue == rhsValue;
-	} else {
-		switch (lhs.getType()) {
-		case kOperandTypeBool:
-		case kOperandTypeInt:
-			return lhs.asParamToken() == rhs.asParamToken();
-
-		case kOperandTypeAssetId:
-			if (rhs.getType() == kOperandTypeInt) {
-				// This might happen if, for example, a given asset wasn't found
-				// in a collection and the script sets the return value to -1.
-				return static_cast<int>(lhs.asAssetId()) == rhs.asParamToken();
-			} else {
-				// If the types are incompatiable, rhs will raise the error.
-				return lhs.asAssetId() == rhs.asAssetId();
-			}
-
-		case kOperandTypeString:
-			return *lhs.asString() == *rhs.asString();
-
-		default:
-			error("ScriptValue::operator==(): Unimplemented ScriptValue types %s and %s", operandTypeToStr(lhs.getType()), operandTypeToStr(rhs.getType()));
-		}
+	case kOpcodeNotEquals:
+		return (left != right);
+
+	case kOpcodeLessThan:
+		return (left < right);
+
+	case kOpcodeGreaterThan:
+		return (left > right);
+
+	case kOpcodeLessThanOrEqualTo:
+		return (left <= right);
+
+	case kOpcodeGreaterThanOrEqualTo:
+		return (left >= right);
+
+	default:
+		error("Got invalid float operation %s", opcodeToStr(op));
 	}
 }
 
-bool ScriptValue::operator<(const ScriptValue &other) const {
-	ScriptValue lhs = getLiteralValue();
-	ScriptValue rhs = other.getLiteralValue();
+bool ScriptValue::compare(Opcode op, Common::SharedPtr<Collection> left, Common::SharedPtr<Collection> right) {
+	switch (op) {
+	case kOpcodeEquals:
+		return (left == right);
 
-	if (!lhs.isNumber() || !rhs.isNumber()) {
-		error("ScriptValue::operator<(): Unimplemented ScriptValue types %s and %s", operandTypeToStr(lhs.getType()), operandTypeToStr(rhs.getType()));
-	}
+	case kOpcodeNotEquals:
+		return (left != right);
 
-	if (lhs.isDouble() || rhs.isDouble()) {
-		// If either ScriptValue is a double, perform double comparison.
-		double lhsValue = lhs.isDouble() ? lhs.asFloat() : static_cast<double>(lhs.asParamToken());
-		double rhsValue = rhs.isDouble() ? rhs.asFloat() : static_cast<double>(rhs.asParamToken());
-		return lhsValue < rhsValue;
-	} else {
-		// Otherwise, perform integer comparison.
-		return lhs.asParamToken() < rhs.asParamToken();
+	default:
+		error("Got invalid collection operation %s", opcodeToStr(op));
 	}
 }
 
-bool ScriptValue::operator>(const ScriptValue &other) const {
-	ScriptValue lhs = getLiteralValue();
-	ScriptValue rhs = other.getLiteralValue();
+ScriptValue ScriptValue::evalMathOperation(Opcode op, const ScriptValue &left, const ScriptValue &right) {
+	ScriptValue returnValue;
+	double result = 0.0;
 
-	if (!lhs.isNumber() || !rhs.isNumber()) {
-		error("ScriptValue::operator>(): Unimplemented ScriptValue types %s and %s", operandTypeToStr(lhs.getType()), operandTypeToStr(rhs.getType()));
+	switch (left.getType()) {
+	case kScriptValueTypeFloat: {
+		if (right.getType() == kScriptValueTypeTime) {
+			result = binaryMathOperation(op, left.asFloat(), right.asTime());
+		} else if (right.getType() == kScriptValueTypeFloat) {
+			result = binaryMathOperation(op, left.asFloat(), right.asFloat());
+		} else {
+			error("Attempted to do math operation on unsupported value type %s", scriptValueTypeToStr(right.getType()));
+		}
+		returnValue.setToFloat(result);
+		break;
 	}
 
-	if (lhs.isDouble() || rhs.isDouble()) {
-		// If either ScriptValue is a double, perform double comparison.
-		double lhsValue = lhs.isDouble() ? lhs.asFloat() : static_cast<double>(lhs.asParamToken());
-		double rhsValue = rhs.isDouble() ? rhs.asFloat() : static_cast<double>(rhs.asParamToken());
-		return lhsValue > rhsValue;
-	} else {
-		// Otherwise, perform integer comparison.
-		return lhs.asParamToken() > rhs.asParamToken();
+	case kScriptValueTypeTime: {
+		if (right.getType() == kScriptValueTypeTime) {
+			result = binaryMathOperation(op, left.asTime(), right.asTime());
+		} else if (right.getType() == kScriptValueTypeFloat) {
+			result = binaryMathOperation(op, left.asTime(), right.asFloat());
+		} else {
+			error("Attempted to do math operation on unsupported value type %s", scriptValueTypeToStr(right.getType()));
+		}
+		returnValue.setToFloat(result);
+		break;
 	}
-}
-
-bool ScriptValue::operator||(const ScriptValue &other) const {
-	ScriptValue lhs = getLiteralValue();
-	ScriptValue 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 kOperandTypeBool:
-	case kOperandTypeInt:
-		return lhs.asParamToken() || rhs.asParamToken();
+	case kScriptValueTypeString: {
+		returnValue.setToString(left.asString() + right.asString());
+		break;
+	}
 
 	default:
-		error("ScriptValue::operator||(): Unimplemented ScriptValue types %s and %s", operandTypeToStr(lhs.getType()), operandTypeToStr(rhs.getType()));
+		error("Attempted to do math operation on unsupported value type %s", scriptValueTypeToStr(right.getType()));
 	}
+
+	return returnValue;
 }
 
-bool ScriptValue::operator!() const {
-	ScriptValue literalValue = getLiteralValue();
+double ScriptValue::binaryMathOperation(Opcode op, double left, double right) {
+	switch (op) {
+	case kOpcodeAdd:
+		return left + right;
 
-	// If the types being compared end up being incompatible, the respective get
-	// method will raise the error.
-	switch (literalValue.getType()) {
-	case kOperandTypeBool:
-	case kOperandTypeInt:
-		return !literalValue.asParamToken();
+	case kOpcodeSubtract:
+		return left - right;
 
-	default:
-		error("ScriptValue::operator!(): Unimplemented ScriptValue type %s", operandTypeToStr(literalValue.getType()));
-	}
-}
+	case kOpcodeMultiply:
+		return left * right;
 
-bool ScriptValue::operator&&(const ScriptValue &other) const {
-	ScriptValue lhs = getLiteralValue();
-	ScriptValue rhs = other.getLiteralValue();
+	case kOpcodeDivide:
+		if (right != 0.0) {
+			return left / right;
+		} else {
+			error("Division by zero");
+		}
 
-	// If the types being compared end up being incompatible, the respective get
-	// method will raise the error.
-	switch (lhs.getType()) {
-	case kOperandTypeBool:
-	case kOperandTypeInt:
-		return lhs.asParamToken() && rhs.asParamToken();
+	case kOpcodeModulo:
+		if (right != 0.0) {
+			return fmod(left, right);
+		} else {
+			error("Division by zero");
+		}
 
 	default:
-		error("ScriptValue::operator&&(): Unimplemented ScriptValue types %s and %s", operandTypeToStr(lhs.getType()), operandTypeToStr(rhs.getType()));
+		error("Got unvalid binary math operation %s", opcodeToStr(op));
 	}
 }
 
-ScriptValue ScriptValue::operator+(const ScriptValue &other) const {
-	ScriptValue lhs = getLiteralValue();
-	ScriptValue rhs = other.getLiteralValue();
-	ScriptValue returnValue(lhs.getType());
+bool ScriptValue::operator==(const ScriptValue &other) const {
+	return compare(kOpcodeEquals, *this, other);
+}
 
-	if (!lhs.isNumber() || !rhs.isNumber()) {
-		error("ScriptValue::operator+(): Unimplemented ScriptValue types %s and %s", operandTypeToStr(lhs.getType()), operandTypeToStr(rhs.getType()));
-	}
+bool ScriptValue::operator!=(const ScriptValue &other) const {
+	return compare(kOpcodeNotEquals, *this, other);
+}
 
-	if (lhs.isDouble() || rhs.isDouble()) {
-		// If either ScriptValue is a double, perform double addition.
-		double lhsValue = lhs.isDouble() ? lhs.asFloat() : static_cast<double>(lhs.asParamToken());
-		double rhsValue = rhs.isDouble() ? rhs.asFloat() : static_cast<double>(rhs.asParamToken());
-		returnValue.setToFloat(lhsValue + rhsValue);
-	} else {
-		// Otherwise, perform integer addition.
-		returnValue.setToParamToken(lhs.asParamToken() + rhs.asParamToken());
-	}
+bool ScriptValue::operator<(const ScriptValue &other) const {
+	return compare(kOpcodeLessThan, *this, other);
+}
 
-	return returnValue;
+bool ScriptValue::operator>(const ScriptValue &other) const {
+	return compare(kOpcodeGreaterThan, *this, other);
 }
 
-ScriptValue ScriptValue::operator-(const ScriptValue &other) const {
-	ScriptValue lhs = getLiteralValue();
-	ScriptValue rhs = other.getLiteralValue();
-	ScriptValue returnValue(lhs.getType());
+bool ScriptValue::operator<=(const ScriptValue &other) const {
+	return compare(kOpcodeLessThanOrEqualTo, *this, other);
+}
 
-	if (!lhs.isNumber() || !rhs.isNumber()) {
-		error("ScriptValue::operator-(): Unimplemented ScriptValue types %s and %s", operandTypeToStr(lhs.getType()), operandTypeToStr(rhs.getType()));
-	}
+bool ScriptValue::operator>=(const ScriptValue &other) const {
+	return compare(kOpcodeGreaterThanOrEqualTo, *this, other);
+}
 
-	if (lhs.isDouble() || rhs.isDouble()) {
-		// If either ScriptValue is a double, perform double subtraction.
-		double lhsValue = lhs.isDouble() ? lhs.asFloat() : static_cast<double>(lhs.asParamToken());
-		double rhsValue = rhs.isDouble() ? rhs.asFloat() : static_cast<double>(rhs.asParamToken());
-		returnValue.setToFloat(lhsValue - rhsValue);
-	} else {
-		// Otherwise, perform integer subtraction.
-		returnValue.setToParamToken(lhs.asParamToken() - rhs.asParamToken());
+bool ScriptValue::operator||(const ScriptValue &other) const {
+	if (getType() != kScriptValueTypeBool || other.getType() != kScriptValueTypeBool) {
+		error("Expected bools for binary comparison, got %s and %s", scriptValueTypeToStr(getType()), scriptValueTypeToStr(other.getType()));
 	}
 
-	return returnValue;
+	return asBool() || other.asBool();
 }
 
-ScriptValue ScriptValue::operator*(const ScriptValue &other) const {
-	ScriptValue lhs = getLiteralValue();
-	ScriptValue rhs = other.getLiteralValue();
-	ScriptValue returnValue(lhs.getType());
-
-	if (!lhs.isNumber() || !rhs.isNumber()) {
-		error("ScriptValue::operator*(): Unimplemented ScriptValue types %s and %s", operandTypeToStr(lhs.getType()), operandTypeToStr(rhs.getType()));
-	}
-
-	if (lhs.isDouble() || rhs.isDouble()) {
-		// If either ScriptValue is a double, perform double multiplication.
-		double lhsValue = lhs.isDouble() ? lhs.asFloat() : static_cast<double>(lhs.asParamToken());
-		double rhsValue = rhs.isDouble() ? rhs.asFloat() : static_cast<double>(rhs.asParamToken());
-		returnValue.setToFloat(lhsValue * rhsValue);
-	} else {
-		// Otherwise, perform integer subtraction.
-		returnValue.setToParamToken(lhs.asParamToken() * rhs.asParamToken());
+bool ScriptValue::operator^(const ScriptValue &other) const {
+	if (getType() != kScriptValueTypeBool || other.getType() != kScriptValueTypeBool) {
+		error("Expected bools for binary comparison, got %s and %s", scriptValueTypeToStr(getType()), scriptValueTypeToStr(other.getType()));
 	}
 
-	return returnValue;
+	return asBool() ^ other.asBool();
 }
 
-ScriptValue ScriptValue::operator/(const ScriptValue &other) const {
-	ScriptValue lhs = getLiteralValue();
-	ScriptValue rhs = other.getLiteralValue();
-	ScriptValue returnValue(lhs.getType());
-
-	if (!lhs.isNumber() || !rhs.isNumber()) {
-		error("ScriptValue::operator/(): Unimplemented ScriptValue types %s and %s", operandTypeToStr(lhs.getType()), operandTypeToStr(rhs.getType()));
+bool ScriptValue::operator&&(const ScriptValue &other) const {
+	if (getType() != kScriptValueTypeBool || other.getType() != kScriptValueTypeBool) {
+		error("Expected bools for binary comparison, got %s and %s", scriptValueTypeToStr(getType()), scriptValueTypeToStr(other.getType()));
 	}
 
-	if (lhs.isDouble() || rhs.isDouble()) {
-		// If either ScriptValue is a double, perform double division.
-		double lhsValue = lhs.isDouble() ? lhs.asFloat() : static_cast<double>(lhs.asParamToken());
-		double rhsValue = rhs.isDouble() ? rhs.asFloat() : static_cast<double>(rhs.asParamToken());
-		returnValue.setToFloat(lhsValue / rhsValue);
-	} else {
-		// Otherwise, perform integer division.
-		returnValue.setToParamToken(lhs.asParamToken() / rhs.asParamToken());
-	}
+	return asBool() && other.asBool();
+}
 
-	return returnValue;
+ScriptValue ScriptValue::operator+(const ScriptValue &other) const {
+	return evalMathOperation(kOpcodeAdd, *this, other);
+}
 
+ScriptValue ScriptValue::operator-(const ScriptValue &other) const {
+	return evalMathOperation(kOpcodeSubtract, *this, other);
 }
 
-ScriptValue ScriptValue::operator%(const ScriptValue &other) const {
-	ScriptValue lhs = getLiteralValue();
-	ScriptValue rhs = other.getLiteralValue();
-	ScriptValue returnValue(lhs.getType());
+ScriptValue ScriptValue::operator*(const ScriptValue &other) const {
+	return evalMathOperation(kOpcodeMultiply, *this, other);
+}
 
-	// If the types being compared end up being incompatible, the respective get
-	// method on the rhs will raise the error.
-	switch (lhs.getType()) {
-	case kOperandTypeBool:
-	case kOperandTypeInt:
-		if (rhs.asParamToken() == 0) {
-			error("ScriptValue::operator%%(): Attempted mod by zero");
-		}
-		returnValue.setToParamToken(lhs.asParamToken() % rhs.asParamToken());
-		return returnValue;
+ScriptValue ScriptValue::operator/(const ScriptValue &other) const {
+	return evalMathOperation(kOpcodeDivide, *this, other);
+}
 
-	default:
-		error("ScriptValue::operator/(): Unimplemented ScriptValue types %s and %s", operandTypeToStr(lhs.getType()), operandTypeToStr(rhs.getType()));
-	}
+ScriptValue ScriptValue::operator%(const ScriptValue &other) const {
+	return evalMathOperation(kOpcodeModulo, *this, other);
 }
 
 ScriptValue ScriptValue::operator-() const {
-	ScriptValue literalValue = getLiteralValue();
-	ScriptValue returnValue(literalValue.getType());
-
-	// If the types being compared end up being incompatible, the respective get
-	// method on the rhs will raise the error.
-	switch (literalValue.getType()) {
-	case kOperandTypeBool:
-	case kOperandTypeInt:
-		returnValue.setToParamToken(-literalValue.asParamToken());
-		return returnValue;
-
-	case kOperandTypeTime:
-	case kOperandTypeFloat:
-		returnValue.setToFloat(-literalValue.asFloat());
-		return returnValue;
+	ScriptValue returnValue;
+	switch (getType()) {
+	case kScriptValueTypeFloat:
+		returnValue.setToFloat(-asFloat());
+		break;
+
+	case kScriptValueTypeTime:
+		returnValue.setToTime(-asTime());
+		break;
 
 	default:
-		error("ScriptValue::operator-(): Unimplemented ScriptValue type %s", operandTypeToStr(literalValue.getType()));
+		error("Attempted to negate type %s", scriptValueTypeToStr(getType()));
 	}
+	return returnValue;
+}
+
+void ScriptValue::issueValueMismatchWarning(ScriptValueType expectedType) const {
+	// The original just blithely returns 0 (or equivalent) when you call a
+	// getter for the wrong type (for instance, calling asFloat() on a bool),
+	// but for debugging purposes we'll issue a warning.
+	warning("Script value type mismatch: Expected %s, got %s", scriptValueTypeToStr(expectedType), scriptValueTypeToStr(_type));
 }
 
 } // End of namespace MediaStation
diff --git a/engines/mediastation/mediascript/scriptvalue.h b/engines/mediastation/mediascript/scriptvalue.h
index 2d2e27b41a3..abbd5da6711 100644
--- a/engines/mediastation/mediascript/scriptvalue.h
+++ b/engines/mediastation/mediascript/scriptvalue.h
@@ -24,9 +24,10 @@
 
 #include "common/ptr.h"
 #include "common/str.h"
+#include "common/stream.h"
 
 #include "mediastation/mediascript/scriptconstants.h"
-#include "mediastation/mediascript/variable.h"
+#include "mediastation/mediascript/collection.h"
 
 namespace MediaStation {
 
@@ -34,44 +35,47 @@ class Asset;
 
 class ScriptValue {
 public:
-	ScriptValue() : _type(kOperandTypeEmpty) {}
-	ScriptValue(OperandType type) : _type(type) {}
+	ScriptValue() : _type(kScriptValueTypeEmpty) {}
+	ScriptValue(Common::SeekableReadStream *stream);
 
-	OperandType getType() const { return _type; }
-
-	void setToParamToken(int i);
-	int asParamToken();
+	ScriptValueType getType() const { return _type; }
 
 	void setToFloat(double d);
-	double asFloat();
+	double asFloat() const;
 
-	void setToString(Common::String *string);
-	Common::String *asString();
+	void setToBool(bool b);
+	bool asBool() const;
 
-	void putVariable(Variable *variable);
-	Variable *getVariable();
+	void setToTime(double d);
+	double asTime() const;
 
-	void setToFunctionId(uint functionId);
-	uint asFunctionId();
+	void setToParamToken(uint paramToken);
+	uint asParamToken() const;
 
-	void setToMethodId(BuiltInMethod methodId);
-	BuiltInMethod asMethodId();
+	void setToAssetId(uint assetId);
+	uint asAssetId() const;
 
-	void setToAssetId(uint32 assetId);
-	Asset *getAsset();
-	uint32 asAssetId();
+	void setToString(const Common::String &string);
+	Common::String asString() const;
 
 	void setToCollection(Common::SharedPtr<Collection> collection);
-	Common::SharedPtr<Collection> asCollection();
+	Common::SharedPtr<Collection> asCollection() const;
+
+	void setToFunctionId(uint functionId);
+	uint asFunctionId() const;
 
-	ScriptValue getLiteralValue() const;
+	void setToMethodId(BuiltInMethod methodId);
+	BuiltInMethod asMethodId() const;
 
 	bool operator==(const ScriptValue &other) const;
+	bool operator!=(const ScriptValue &other) const;
 	bool operator<(const ScriptValue &other) const;
 	bool operator>(const ScriptValue &other) const;
+	bool operator<=(const ScriptValue &other) const;
+	bool operator>=(const ScriptValue &other) const;
 
 	bool operator||(const ScriptValue &other) const;
-	bool operator!() const;
+	bool operator^(const ScriptValue &other) const;
 	bool operator&&(const ScriptValue &other) const;
 
 	ScriptValue operator+(const ScriptValue &other) const;
@@ -82,21 +86,30 @@ public:
 	ScriptValue operator-() const;
 
 private:
-	bool isInteger() { return getType() == kOperandTypeBool || getType() == kOperandTypeInt; };
-	bool isDouble() { return getType() == kOperandTypeFloat || getType() == kOperandTypeTime; };
-	bool isNumber() { return isInteger() || isDouble(); };
-
-	OperandType _type = kOperandTypeEmpty;
+	ScriptValueType _type = kScriptValueTypeEmpty;
 	union {
-		uint assetId = 0;
+		double d = 0;
+		bool b;
+		uint paramToken;
+		uint assetId;
 		uint functionId;
 		BuiltInMethod methodId;
-		Common::String *string;
-		Variable *variable;
-		int i;
-		double d;
 	} _u;
+	Common::String _string;
 	Common::SharedPtr<Collection> _collection;
+
+	static bool compare(Opcode op, const ScriptValue &left, const ScriptValue &right);
+	static bool compareEmptyValues(Opcode op);
+	static bool compareStrings(Opcode op, const Common::String &left, const Common::String &right);
+	static bool compare(Opcode op, uint left, uint right);
+	static bool compare(Opcode op, bool left, bool right);
+	static bool compare(Opcode op, double left, double right);
+	static bool compare(Opcode op, Common::SharedPtr<Collection> left, Common::SharedPtr<Collection> right);
+
+	static ScriptValue evalMathOperation(Opcode op, const ScriptValue &left, const ScriptValue &right);
+	static double binaryMathOperation(Opcode op, double left, double right);
+
+	void issueValueMismatchWarning(ScriptValueType actualType) const;
 };
 
 } // End of namespace MediaStation
diff --git a/engines/mediastation/mediascript/variable.cpp b/engines/mediastation/mediascript/variable.cpp
deleted file mode 100644
index 8edde17aaad..00000000000
--- a/engines/mediastation/mediascript/variable.cpp
+++ /dev/null
@@ -1,329 +0,0 @@
-/* ScummVM - Graphic Adventure Engine
- *
- * ScummVM is the legal property of its developers, whose names
- * are too numerous to list here. Please refer to the COPYRIGHT
- * file distributed with this source distribution.
- *
- * This program is free software: you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation, either version 3 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program.  If not, see <http://www.gnu.org/licenses/>.
- *
- */
-
-#include "mediastation/mediastation.h"
-#include "mediastation/mediascript/variable.h"
-#include "mediastation/mediascript/scriptvalue.h"
-#include "mediastation/mediascript/codechunk.h"
-#include "mediastation/datum.h"
-#include "mediastation/debugchannels.h"
-
-namespace MediaStation {
-
-ScriptValue Collection::callMethod(BuiltInMethod method, Common::Array<ScriptValue> &args) {
-	switch (method) {
-	case kIsEmptyMethod: {
-		ScriptValue returnValue(kOperandTypeBool);
-		returnValue.setToParamToken(static_cast<uint>(empty()));
-		return returnValue;
-	}
-
-	case kAppendMethod: {
-		for (ScriptValue arg : args) {
-			push_back(arg);
-		}
-		return ScriptValue();
-	}
-
-	case kDeleteFirstMethod: {
-		ScriptValue returnValue = remove_at(0);
-		return returnValue;
-	}
-
-	case kDeleteAtMethod: {
-		// Find the item in the collection, then remove and return it.
-		assert(args.size() == 1);
-		for (uint i = 0; i < size(); i++) {
-			if (args[0] == operator[](i)) {
-				ScriptValue returnValue = remove_at(i);
-				return returnValue;
-			}
-		}
-
-		// The item wasn't found.
-		return ScriptValue();
-	}
-
-	case kCountMethod: {
-		ScriptValue returnValue = ScriptValue(kOperandTypeBool);
-		returnValue.setToParamToken(size());
-		return returnValue;
-	}
-
-	case kGetAtMethod: {
-		assert(args.size() == 1);
-		ScriptValue returnValue = operator[](args[0].asParamToken());
-		return returnValue;
-	}
-
-	case kSendMethod: {
-		// Call a method on each item in the collection.
-		BuiltInMethod methodToSend = static_cast<BuiltInMethod>(args[0].asMethodId());
-		Common::Array<ScriptValue> sendArgs;
-		for (uint i = 0; i < size(); i++) {
-			ScriptValue self = operator[](i);
-			CodeChunk::callBuiltInMethod(methodToSend, self, sendArgs);
-		}
-		return ScriptValue();
-	}
-
-	case kSeekMethod: {
-		// Find the item in the collection if it exists.
-		assert(args.size() == 1);
-		for (uint i = 0; i < size(); i++) {
-			if (args[0] == operator[](i)) {
-				return operator[](i);
-			}
-		}
-
-		// The item wasn't found.
-		ScriptValue returnValue(kOperandTypeBool);
-		returnValue.setToParamToken(-1);
-		return returnValue;
-	}
-
-	case kJumbleMethod: {
-		// Scramble the items in the collection.
-		for (uint i = size() - 1; i > 0; --i) {
-			uint j = g_engine->_randomSource.getRandomNumber(size() - 1);
-			SWAP(operator[](i), operator[](j));
-		}
-		return ScriptValue();
-	}
-
-	case kSortMethod: {
-		assert(args.empty());
-		Common::sort(begin(), end());
-		return ScriptValue();
-	}
-
-	case kEmptyMethod: {
-		clear();
-		return ScriptValue();
-	}
-
-	default:
-		error("Collection::callMethod(): Attempt to call unimplemented method %s (%d)", builtInMethodToStr(method), static_cast<uint>(method));
-	}
-}
-
-Variable::Variable(Chunk &chunk, bool readId) {
-	if (readId) {
-		_id = Datum(chunk).u.i;
-	}
-
-	_type = static_cast<ScriptValueType>(Datum(chunk).u.i);
-	debugC(7, kDebugScript, "Variable::Variable(): id = %d, type %s (%d) (@0x%llx)",
-		_id, scriptValueTypeToStr(_type), static_cast<uint>(_type), static_cast<long long int>(chunk.pos()));
-	switch (_type) {
-	case kScriptValueTypeCollection: {
-		uint totalItems = Datum(chunk).u.i;
-		_c = Common::SharedPtr<Collection>(new Collection);
-		for (uint i = 0; i < totalItems; i++) {
-			debugC(7, kDebugLoading, "Variable::Variable(): %s: Value %d of %d", scriptValueTypeToStr(_type), i, totalItems);
-			Variable variable = Variable(chunk, readId = false);
-			_c->push_back(variable.getValue());
-		}
-		break;
-	}
-
-	case kScriptValueTypeString: {
-		// TODO: This copies the string. Can we read it directly from the chunk?
-		int size = Datum(chunk).u.i;
-		char *buffer = new char[size + 1];
-		chunk.read(buffer, size);
-		buffer[size] = '\0';
-		_value.string = new Common::String(buffer);
-		delete[] buffer;
-		debugC(7, kDebugLoading, "Variable::Variable(): %s: %s", scriptValueTypeToStr(_type), _value.string->c_str());
-		break;
-	}
-
-	case kScriptValueTypeAssetId: {
-		_value.assetId = Datum(chunk, kDatumTypeUint16_1).u.i;
-		debugC(7, kDebugLoading, "Variable::Variable(): %s: %d", scriptValueTypeToStr(_type), _value.assetId);
-		break;
-	}
-
-	case kScriptValueTypeBool: {
-		uint rawValue = Datum(chunk, kDatumTypeUint8).u.i;
-		debugC(7, kDebugLoading, " Variable::Variable(): %s: %d", scriptValueTypeToStr(_type), rawValue);
-		_value.i = static_cast<int>(rawValue == 1);
-		break;
-	}
-
-	case kScriptValueTypeFloat: {
-		Datum datum = Datum(chunk);
-		if ((datum.t != kDatumTypeFloat64_1) && (datum.t != kDatumTypeFloat64_2)) {
-			error("Variable::Variable(): Got a non-float datum type 0x%x to put into a float variable", datum.t);
-		}
-		_value.d = datum.u.f;
-		debugC(7, kDebugLoading, "Variable::Variable(): %s: %f", scriptValueTypeToStr(_type), _value.d);
-		break;
-	}
-
-	case kScriptValueTypeParamToken: {
-		_value.i = Datum(chunk).u.i;
-		debugC(7, kDebugLoading, "Variable::Variable(): %s: %d", scriptValueTypeToStr(_type), _value.i);
-		break;
-	}
-
-	default:
-		error("Variable::Variable(): Got unknown variable value type %s (%d)", scriptValueTypeToStr(_type), static_cast<uint>(_type));
-	}
-}
-
-Variable::~Variable() {
-	clear();
-}
-
-ScriptValue Variable::getValue() {
-	switch (_type) {
-	case kScriptValueTypeEmpty: {
-		error("Variable::getValue(): Attempt to get value from an empty variable");
-	}
-
-	case kScriptValueTypeCollection: {
-		ScriptValue returnValue(kOperandTypeCollection);
-		returnValue.setToCollection(_c);
-		return returnValue;
-	}
-
-	case kScriptValueTypeString: {
-		ScriptValue returnValue(kOperandTypeString);
-		returnValue.setToString(_value.string);
-		return returnValue;
-	}
-
-	case kScriptValueTypeAssetId: {
-		ScriptValue returnValue(kOperandTypeAssetId);
-		returnValue.setToAssetId(_value.assetId);
-		return returnValue;
-	}
-
-	case kScriptValueTypeBool: {
-		// TODO: Is this value type correct?
-		// Shouldn't matter too much, though, since it's still an integer type.
-		ScriptValue returnValue(kOperandTypeBool);
-		returnValue.setToParamToken(_value.i);
-		return returnValue;
-	}
-
-	case kScriptValueTypeParamToken: {
-		// TODO: Is this value type correct?
-		// Shouldn't matter too much, though, since it's still an integer type.
-		ScriptValue returnValue(kOperandTypeBool);
-		returnValue.setToParamToken(_value.i);
-		return returnValue;
-	}
-
-	case kScriptValueTypeFloat: {
-		// TODO: Is this value type correct?
-		// Shouldn't matter too much, though, since it's still a floating-point type.
-		ScriptValue returnValue(kOperandTypeTime);
-		returnValue.setToFloat(_value.d);
-		return returnValue;
-	}
-
-	default:
-		error("Variable::getValue(): Attempt to get value from unknown variable type %s (%d)", scriptValueTypeToStr(_type), static_cast<uint>(_type));
-	}
-}
-
-void Variable::putValue(ScriptValue value) {
-	clear();
-
-	switch (value.getType()) {
-	case kOperandTypeEmpty: {
-		error("Variable::putValue(): Assigning an empty ScriptValue to a variable not supported");
-	}
-
-	case kOperandTypeBool:
-	case kOperandTypeInt:
-	case kOperandTypeParamToken: {
-		_type = kScriptValueTypeParamToken;
-		_value.i = value.asParamToken();
-		break;
-	}
-
-	case kOperandTypeTime:
-	case kOperandTypeFloat: {
-		_type = kScriptValueTypeFloat;
-		_value.d = value.asFloat();
-		break;
-	}
-
-	case kOperandTypeString: {
-		_type = kScriptValueTypeString;
-		_value.string = value.asString();
-		break;
-	}
-
-	case kOperandTypeAssetId: {
-		_type = kScriptValueTypeAssetId;
-		_value.assetId = value.asAssetId();
-		break;
-	}
-
-	case kOperandTypeVariable: {
-		putValue(value.getLiteralValue());
-		break;
-	}
-
-	case kOperandTypeFunctionId: {
-		_type = kScriptValueTypeFunctionId;
-		_value.functionId = value.asFunctionId();
-		break;
-	}
-
-	case kOperandTypeCollection: {
-		_type = kScriptValueTypeCollection;
-		_c = value.asCollection();
-		break;
-	}
-
-	default:
-		error("Variable::putValue(): Assigning an unknown ScriptValue type %s (%d) to a variable not supported",
-			operandTypeToStr(value.getType()), static_cast<uint>(value.getType()));
-	}
-}
-
-void Variable::clear() {
-	switch (_type) {
-	case kScriptValueTypeCollection: {
-		_c.reset();
-		break;
-	}
-
-	case kScriptValueTypeString: {
-		delete _value.string;
-		_value.string = nullptr;
-		break;
-	}
-
-	default:
-		break;
-	}
-
-	_type = kScriptValueTypeEmpty;
-}
-
-} // End of namespace MediaStation
diff --git a/engines/mediastation/mediastation.cpp b/engines/mediastation/mediastation.cpp
index 37a657b37f9..5f22fe6ea7c 100644
--- a/engines/mediastation/mediastation.cpp
+++ b/engines/mediastation/mediastation.cpp
@@ -63,11 +63,6 @@ MediaStationEngine::~MediaStationEngine() {
 		delete it->_value;
 	}
 	_loadedContexts.clear();
-
-	for (auto it = _variables.begin(); it != _variables.end(); ++it) {
-		delete it->_value;
-	}
-	_variables.clear();
 }
 
 Asset *MediaStationEngine::getAssetById(uint assetId) {
@@ -100,6 +95,16 @@ Function *MediaStationEngine::getFunctionById(uint functionId) {
 	return nullptr;
 }
 
+ScriptValue *MediaStationEngine::getVariable(uint variableId) {
+	for (auto it = _loadedContexts.begin(); it != _loadedContexts.end(); ++it) {
+		ScriptValue *variable = it->_value->getVariable(variableId);
+		if (variable != nullptr) {
+			return variable;
+		}
+	}
+	return nullptr;
+}
+
 uint32 MediaStationEngine::getFeatures() const {
 	return _gameDescription->flags;
 }
@@ -395,6 +400,8 @@ void MediaStationEngine::addPlayingAsset(Asset *assetToAdd) {
 }
 
 ScriptValue MediaStationEngine::callMethod(BuiltInMethod methodId, Common::Array<ScriptValue> &args) {
+	ScriptValue returnValue;
+
 	switch (methodId) {
 	case kBranchToScreenMethod: {
 		assert(args.size() >= 1);
@@ -404,14 +411,14 @@ ScriptValue MediaStationEngine::callMethod(BuiltInMethod methodId, Common::Array
 		}
 		uint32 contextId = args[0].asAssetId();
 		_requestedScreenBranchId = contextId;
-		return ScriptValue();
+		return returnValue;
 	}
 
 	case kReleaseContextMethod: {
 		assert(args.size() == 1);
 		uint32 contextId = args[0].asAssetId();
 		_requestedContextReleaseId.push_back(contextId);
-		return ScriptValue();
+		return returnValue;
 	}
 
 	default:
@@ -507,25 +514,26 @@ Asset *MediaStationEngine::findAssetToAcceptMouseEvents() {
 }
 
 ScriptValue MediaStationEngine::callBuiltInFunction(BuiltInFunction function, Common::Array<ScriptValue> &args) {
+	ScriptValue returnValue;
+
 	switch (function) {
 	case kEffectTransitionFunction:
 	case kEffectTransitionOnSyncFunction: {
 		// TODO: effectTransitionOnSync should be split out into its own function.
 		effectTransition(args);
-		return ScriptValue();
+		return returnValue;
 	}
 
 	case kDrawingFunction: {
 		// Not entirely sure what this function does, but it seems like a way to
 		// call into some drawing functions built into the IBM/Crayola executable.
 		warning("MediaStationEngine::callBuiltInFunction(): Built-in drawing function not implemented");
-		return ScriptValue();
+		return returnValue;
 	}
 
 	case kUnk1Function: {
 		warning("MediaStationEngine::callBuiltInFunction(): Function 10 not implemented");
-		ScriptValue returnValue = ScriptValue(kOperandTypeBool);
-		returnValue.setToParamToken(1);
+		returnValue.setToFloat(1.0);
 		return returnValue;
 	}
 
diff --git a/engines/mediastation/mediastation.h b/engines/mediastation/mediastation.h
index a6b1c868b7a..b0e07b0064e 100644
--- a/engines/mediastation/mediastation.h
+++ b/engines/mediastation/mediastation.h
@@ -85,11 +85,10 @@ public:
 	Asset *getAssetById(uint assetId);
 	Asset *getAssetByChunkReference(uint chunkReference);
 	Function *getFunctionById(uint functionId);
+	ScriptValue *getVariable(uint variableId);
 
 	ScriptValue callMethod(BuiltInMethod methodId, Common::Array<ScriptValue> &args);
 	ScriptValue callBuiltInFunction(BuiltInFunction function, Common::Array<ScriptValue> &args);
-	Common::HashMap<uint32, Variable *> _variables;
-
 	Common::RandomSource _randomSource;
 
 	Graphics::Screen *_screen = nullptr;
diff --git a/engines/mediastation/module.mk b/engines/mediastation/module.mk
index 5be2c2a1d45..da72f49bc9a 100644
--- a/engines/mediastation/module.mk
+++ b/engines/mediastation/module.mk
@@ -22,11 +22,11 @@ MODULE_OBJS = \
 	datafile.o \
 	datum.o \
 	mediascript/codechunk.o \
+	mediascript/collection.o \
 	mediascript/eventhandler.o \
 	mediascript/function.o \
 	mediascript/scriptconstants.o \
 	mediascript/scriptvalue.o \
-	mediascript/variable.o \
 	mediastation.o \
 	metaengine.o \
 	transitions.o
diff --git a/engines/mediastation/transitions.cpp b/engines/mediastation/transitions.cpp
index 2f2c20bd133..14b23e267ef 100644
--- a/engines/mediastation/transitions.cpp
+++ b/engines/mediastation/transitions.cpp
@@ -64,13 +64,13 @@ void MediaStationEngine::effectTransition(Common::Array<ScriptValue> &args) {
 	case kTransitionFadeToPaletteObject: {
 		// TODO: Implement transition.
 		warning("MediaStationEngine::effectTransition(): Fading to palette object not implemented, changing palette immediately");
-		Asset *asset = args[1].getAsset();
+		Asset *asset = g_engine->getAssetById(args[1].asAssetId());
 		g_engine->setPalette(asset);
 		break;
 	}
 
 	case kTransitionSetToPaletteObject: {
-		Asset *asset = args[1].getAsset();
+		Asset *asset = g_engine->getAssetById(args[1].asAssetId());
 		g_engine->setPalette(asset);
 		break;
 	}
@@ -80,7 +80,7 @@ void MediaStationEngine::effectTransition(Common::Array<ScriptValue> &args) {
 
 		// TODO: Implement percent of palette transition.
 		warning("MediaStationEngine::effectTransition(): Setting to %f%% of palette not implemented, changing palette immediately", percentComplete);
-		Asset *asset = args[2].getAsset();
+		Asset *asset = g_engine->getAssetById(args[2].asAssetId());
 		g_engine->setPalette(asset);
 		break;
 	}


Commit: 04d375ea218813beca1aaf0ecd809157bf2a6790
    https://github.com/scummvm/scummvm/commit/04d375ea218813beca1aaf0ecd809157bf2a6790
Author: Nathanael Gentry (nathanael.gentrydb8 at gmail.com)
Date: 2025-04-11T14:54:23-04:00

Commit Message:
MEDIASTATION: Unify script value and event handler types

Changed paths:
    engines/mediastation/asset.cpp
    engines/mediastation/assetheader.cpp
    engines/mediastation/assets/timer.cpp
    engines/mediastation/mediascript/eventhandler.cpp
    engines/mediastation/mediascript/eventhandler.h
    engines/mediastation/mediascript/scriptconstants.cpp
    engines/mediastation/mediascript/scriptconstants.h


diff --git a/engines/mediastation/asset.cpp b/engines/mediastation/asset.cpp
index 18b10222d63..04a3f5d3c54 100644
--- a/engines/mediastation/asset.cpp
+++ b/engines/mediastation/asset.cpp
@@ -72,7 +72,8 @@ void Asset::processTimeEventHandlers() {
 	// TODO: Replace with a queue.
 	uint currentTime = g_system->getMillis();
 	for (EventHandler *timeEvent : _header->_timeHandlers) {
-		double timeEventInFractionalSeconds = timeEvent->_argumentValue.u.f;
+ 		// Indeed float, not time.
+		double timeEventInFractionalSeconds = timeEvent->_argumentValue.asFloat();
 		uint timeEventInMilliseconds = timeEventInFractionalSeconds * 1000;
 		bool timeEventAlreadyProcessed = timeEventInMilliseconds < _lastProcessedTime;
 		bool timeEventNeedsToBeProcessed = timeEventInMilliseconds <= currentTime - _startTime;
diff --git a/engines/mediastation/assetheader.cpp b/engines/mediastation/assetheader.cpp
index 33196c88e86..c808d1a34c5 100644
--- a/engines/mediastation/assetheader.cpp
+++ b/engines/mediastation/assetheader.cpp
@@ -109,13 +109,10 @@ void AssetHeader::readSection(AssetHeaderSectionType sectionType, Chunk& chunk)
 		}
 
 		case kKeyDownEvent: {
-			if (eventHandler->_argumentType != kAsciiCodeEventHandlerArgument) {
+			if (eventHandler->_argumentValue.getType() != kScriptValueTypeFloat) {
 				error("Keydown event handler doesn't have correct argument type");
 			}
-			if (eventHandler->_argumentValue.t != kDatumTypeFloat64_2) {
-				error("Keydown event handler doesn't have correct argument value type");
-			}
-			uint asciiCode = static_cast<uint>(eventHandler->_argumentValue.u.f);
+			uint asciiCode = static_cast<uint>(eventHandler->_argumentValue.asFloat());
 			_keyDownHandlers.setVal(asciiCode, eventHandler);
 			break;
 		}
@@ -131,10 +128,10 @@ void AssetHeader::readSection(AssetHeaderSectionType sectionType, Chunk& chunk)
 		}
 
 		default: {
-			if (eventHandler->_argumentType != kNullEventHandlerArgument && \
-				eventHandler->_argumentType != kUnk1EventHandlerArgument) {
+			if (eventHandler->_argumentValue.getType() != kScriptValueTypeEmpty && \
+				eventHandler->_argumentValue.getType() != kScriptValueTypeParamToken) {
 				error("AssetHeader::readSection(): Event handler of type %s has a non-null argument type %s",
-					eventTypeToStr(eventHandler->_type), eventHandlerArgumentTypeToStr(eventHandler->_argumentType));
+					eventTypeToStr(eventHandler->_type), scriptValueTypeToStr(eventHandler->_argumentValue.getType()));
 			}
 
 			if (_eventHandlers.contains(eventHandler->_type)) {
diff --git a/engines/mediastation/assets/timer.cpp b/engines/mediastation/assets/timer.cpp
index de9d2226f36..5f04ad08e01 100644
--- a/engines/mediastation/assets/timer.cpp
+++ b/engines/mediastation/assets/timer.cpp
@@ -69,9 +69,8 @@ void Timer::timePlay() {
 	// through each of the timer event handlers to figure it out?
 	_duration = 0;
 	for (EventHandler *timeEvent : _header->_timeHandlers) {
-		// TODO: Centralize this converstion to milliseconds, as the same logic
-		// is being used in several places.
-		double timeEventInFractionalSeconds = timeEvent->_argumentValue.u.f;
+		// Indeed float, not time.
+		double timeEventInFractionalSeconds = timeEvent->_argumentValue.asFloat();
 		uint timeEventInMilliseconds = timeEventInFractionalSeconds * 1000;
 		if (timeEventInMilliseconds > _duration) {
 			_duration = timeEventInMilliseconds;
diff --git a/engines/mediastation/mediascript/eventhandler.cpp b/engines/mediastation/mediascript/eventhandler.cpp
index 735c3d47ef2..bc2b27b1c09 100644
--- a/engines/mediastation/mediascript/eventhandler.cpp
+++ b/engines/mediastation/mediascript/eventhandler.cpp
@@ -29,15 +29,9 @@ EventHandler::EventHandler(Chunk &chunk) {
 	debugC(5, kDebugLoading, "EventHandler::EventHandler(): Type %s (%d) (@0x%llx)",
 		eventTypeToStr(_type), static_cast<uint>(_type), static_cast<long long int>(chunk.pos()));
 
-	_argumentType = static_cast<EventHandlerArgumentType>(Datum(chunk).u.i);
-	debugC(5, kDebugLoading, "EventHandler::EventHandler(): Argument type %s (%d) (@0x%llx)",
-		eventHandlerArgumentTypeToStr(_argumentType), static_cast<uint>(_argumentType), static_cast<long long int>(chunk.pos()));
-
-	_argumentValue = Datum(chunk);
-	if (_argumentType != kNullEventHandlerArgument) {
-		uint lengthInBytes = Datum(chunk, kDatumTypeUint32_1).u.i;
-		debugC(5, kDebugLoading, "EventHandler::EventHandler(): Null argument type, length = 0x%x (@0x%llx)", lengthInBytes, static_cast<long long int>(chunk.pos()));
-	}
+	_argumentValue = ScriptValue(&chunk);
+	uint lengthInBytes = Datum(chunk, kDatumTypeUint32_1).u.i;
+	debugC(5, kDebugLoading, "EventHandler::EventHandler(): length = 0x%x (@0x%llx)", lengthInBytes, static_cast<long long int>(chunk.pos()));
 
 	_code = new CodeChunk(chunk);
 }
@@ -45,13 +39,15 @@ EventHandler::EventHandler(Chunk &chunk) {
 ScriptValue EventHandler::execute(uint assetId) {
 	// TODO: The assetId is only passed in for debug visibility, there should be
 	// a better way to handle that.
-	debugC(5, kDebugScript, "\n********** EVENT HANDLER %s **********", getDebugHeader(assetId).c_str());
+	Common::String assetAndType = Common::String::format("(asset %d) (type = %s)", assetId, eventTypeToStr(_type));
+	Common::String argValue = getDebugHeader();
+	debugC(5, kDebugScript, "\n********** EVENT HANDLER %s %s **********", assetAndType.c_str(), argValue.c_str());
 
 	// The only argument that can be provided to an
 	// event handler is the _argumentValue.
 	ScriptValue returnValue = _code->execute();
 
-	debugC(5, kDebugScript, "********** END EVENT HANDLER %s **********", getDebugHeader(assetId).c_str());
+	debugC(5, kDebugScript, "********** END EVENT HANDLER %s %s **********", assetAndType.c_str(), argValue.c_str());
 	return returnValue;
 }
 
@@ -60,27 +56,25 @@ EventHandler::~EventHandler() {
 	_code = nullptr;
 }
 
-Common::String EventHandler::getDebugHeader(uint assetId) {
-	switch (_argumentType) {
-	case kNullEventHandlerArgument:
-		return Common::String::format("(asset %d) (type = %s) (no argument)", assetId, eventTypeToStr(_type));
+Common::String EventHandler::getDebugHeader() {
+	switch (_argumentValue.getType()) {
+	case kScriptValueTypeEmpty:
+		return "(no argument)";
 
-	case kAsciiCodeEventHandlerArgument: {
-		// Not sure why the ASCII code isn't just stored as an integer, but it's not.
-		uint asciiCode = static_cast<uint>(_argumentValue.u.f);
-		return Common::String::format("(asset %d) (type = %s) (ASCII code = %d)", assetId, eventTypeToStr(_type), asciiCode);
-	}
+	case kScriptValueTypeFloat:
+		return Common::String::format("(float = %f)", _argumentValue.asFloat());
+
+	case kScriptValueTypeAssetId:
+		return Common::String::format("(context = %d)", _argumentValue.asAssetId());
 
-	case kContextEventHandlerArgument:
-		return Common::String::format("(asset %d) (type = %s) (context = %d)", assetId, eventTypeToStr(_type), _argumentValue.u.i);
+	case kScriptValueTypeTime:
+		return Common::String::format("(time = %f)", _argumentValue.asTime());
 
-	case kTimeEventHandlerArgument:
-	case kUnk1EventHandlerArgument:
-		return Common::String::format("(asset %d) (type = %s) (time = %f)", assetId, eventTypeToStr(_type), _argumentValue.u.f);
+	case kScriptValueTypeParamToken:
+		return Common::String::format("(token = %d)", _argumentValue.asParamToken());
 
 	default:
-		error("EventHandler::getDebugHeader(): Unimplemented argument type %s (%d)",
-			eventHandlerArgumentTypeToStr(_argumentType), static_cast<uint>(_argumentType));
+		return Common::String::format("(arg type %s)", scriptValueTypeToStr(_argumentValue.getType()));
 	}
 }
 
diff --git a/engines/mediastation/mediascript/eventhandler.h b/engines/mediastation/mediascript/eventhandler.h
index 6ab0ed18b79..a72f7004af7 100644
--- a/engines/mediastation/mediascript/eventhandler.h
+++ b/engines/mediastation/mediascript/eventhandler.h
@@ -38,11 +38,10 @@ public:
 
 	ScriptValue execute(uint assetId);
 	EventType _type;
-	EventHandlerArgumentType _argumentType;
-	Datum _argumentValue;
+	ScriptValue _argumentValue;
 
 private:
-	Common::String getDebugHeader(uint assetId);
+	Common::String getDebugHeader();
 
 	CodeChunk *_code = nullptr;
 };
diff --git a/engines/mediastation/mediascript/scriptconstants.cpp b/engines/mediastation/mediascript/scriptconstants.cpp
index 9e202b3054f..20fbfc98757 100644
--- a/engines/mediastation/mediascript/scriptconstants.cpp
+++ b/engines/mediastation/mediascript/scriptconstants.cpp
@@ -297,23 +297,6 @@ const char *eventTypeToStr(EventType type) {
 	}
 }
 
-const char *eventHandlerArgumentTypeToStr(EventHandlerArgumentType type) {
-	switch (type) {
-	case kNullEventHandlerArgument:
-		return "Null";
-	case kAsciiCodeEventHandlerArgument:
-		return "AsciiCode";
-	case kTimeEventHandlerArgument:
-		return "Time";
-	case kUnk1EventHandlerArgument:
-		return "Unk1";
-	case kContextEventHandlerArgument:
-		return "Context";
-	default:
-		return "UNKNOWN";
-	}
-}
-
 const char *operandTypeToStr(OperandType type) {
 	switch (type) {
 	case kOperandTypeEmpty:
diff --git a/engines/mediastation/mediascript/scriptconstants.h b/engines/mediastation/mediascript/scriptconstants.h
index 8e1d51a0562..04c1d1be1e6 100644
--- a/engines/mediastation/mediascript/scriptconstants.h
+++ b/engines/mediastation/mediascript/scriptconstants.h
@@ -225,18 +225,6 @@ enum EventType {
 };
 const char *eventTypeToStr(EventType type);
 
-enum EventHandlerArgumentType {
-	kNullEventHandlerArgument = 0,
-	kAsciiCodeEventHandlerArgument = 1,
-	kTimeEventHandlerArgument = 3,
-	// TODO: This argument type Appears to happen with MovieStart
-	// and nowhere else. However, this event handler shouldn't even need an
-	// argument...
-	kUnk1EventHandlerArgument = 4,
-	kContextEventHandlerArgument = 5
-};
-const char *eventHandlerArgumentTypeToStr(EventHandlerArgumentType type);
-
 enum OperandType {
 	kOperandTypeEmpty = 0,
 	kOperandTypeBool = 151,


Commit: 997c76fdfc8f531f187e4538f5b1f8be2cd86900
    https://github.com/scummvm/scummvm/commit/997c76fdfc8f531f187e4538f5b1f8be2cd86900
Author: Nathanael Gentry (nathanael.gentrydb8 at gmail.com)
Date: 2025-04-11T14:54:23-04:00

Commit Message:
MEDIASTATION: Add original script execution and return behavior

Changed paths:
    engines/mediastation/mediascript/codechunk.cpp
    engines/mediastation/mediascript/codechunk.h
    engines/mediastation/mediascript/eventhandler.cpp
    engines/mediastation/mediascript/function.cpp


diff --git a/engines/mediastation/mediascript/codechunk.cpp b/engines/mediastation/mediascript/codechunk.cpp
index 9012728300f..8518d853a97 100644
--- a/engines/mediastation/mediascript/codechunk.cpp
+++ b/engines/mediastation/mediascript/codechunk.cpp
@@ -35,42 +35,58 @@ CodeChunk::CodeChunk(Common::SeekableReadStream &chunk) {
 	_bytecode = chunk.readStream(lengthInBytes);
 }
 
-ScriptValue CodeChunk::execute(Common::Array<ScriptValue> *args, Common::Array<ScriptValue> *locals) {
-	_locals = locals;
-	_args = args;
+ScriptValue CodeChunk::executeNextBlock() {
+	uint blockSize = Datum(*_bytecode, kDatumTypeUint32_1).u.i;
+	uint startingPos = _bytecode->pos();
+
 	ScriptValue returnValue;
-	while (_bytecode->pos() < _bytecode->size()) {
-		ScriptValue instructionResult = evaluateExpression();
-		if (instructionResult.getType() != kScriptValueTypeEmpty) {
-			returnValue = instructionResult;
+	ExpressionType expressionType = static_cast<ExpressionType>(Datum(*_bytecode).u.i);
+	while (expressionType != kExpressionTypeEmpty && !_returnImmediately) {
+		returnValue = evaluateExpression(expressionType);
+		expressionType = static_cast<ExpressionType>(Datum(*_bytecode).u.i);
+	}
+
+	// Verify we consumed the right number of script bytes.
+	if (!_returnImmediately) {
+		uint bytesRead = _bytecode->pos() - startingPos;
+		if (bytesRead != blockSize) {
+			error("Expected to have read %d script bytes, actually read %d", blockSize, bytesRead);
 		}
 	}
+	return returnValue;
+}
+
+void CodeChunk::skipNextBlock() {
+	uint lengthInBytes = Datum(*_bytecode, kDatumTypeUint32_1).u.i;
+	_bytecode->skip(lengthInBytes);
+}
+
+ScriptValue CodeChunk::execute(Common::Array<ScriptValue> *args) {
+	_args = args;
+	ScriptValue returnValue = executeNextBlock();
 
 	// Rewind the stream once we're finished, in case we need to execute
 	// this code again!
 	_bytecode->seek(0);
+	_locals.clear();
 	// We don't own the args, so we will prevent a potentially out-of-scope
 	// variable from being re-accessed.
 	_args = nullptr;
 
-	if (_weOwnLocals) {
-		delete _locals;
-	}
-	_locals = nullptr;
-
 	return returnValue;
 }
 
 ScriptValue CodeChunk::evaluateExpression() {
-	if (_bytecode->eos()) {
-		error("CodeChunk::evaluateExpression(): Attempt to read past end of bytecode chunk");
-	}
+	ExpressionType expressionType = static_cast<ExpressionType>(Datum(*_bytecode).u.i);
+	ScriptValue returnValue = evaluateExpression(expressionType);
+	return returnValue;
+}
 
-	ExpressionType instructionType = static_cast<ExpressionType>(Datum(*_bytecode).u.i);
-	debugCN(5, kDebugScript, "(%s) ", expressionTypeToStr(instructionType));
+ScriptValue CodeChunk::evaluateExpression(ExpressionType expressionType) {
+	debugCN(5, kDebugScript, "(%s) ", expressionTypeToStr(expressionType));
 
 	ScriptValue returnValue;
-	switch (instructionType) {
+	switch (expressionType) {
 	case kExpressionTypeEmpty: {
 		return returnValue;
 	}
@@ -88,7 +104,8 @@ ScriptValue CodeChunk::evaluateExpression() {
 		break;
 
 	default:
-		error("CodeChunk::getNextStatement(): Got unimplemented instruction type %s (%d)", expressionTypeToStr(instructionType), static_cast<uint>(instructionType));
+		error("Got unimplemented expression type %s (%d)",
+			expressionTypeToStr(expressionType), static_cast<uint>(expressionType));
 	}
 
 	return returnValue;
@@ -135,9 +152,8 @@ ScriptValue CodeChunk::evaluateOperation() {
 	case kOpcodeDeclareVariables: {
 		uint32 localVariableCount = Datum(*_bytecode).u.i;
 		debugC(5, kDebugScript, "%d", localVariableCount);
-		assert(_locals == nullptr);
-		_locals = new Common::Array<ScriptValue>(localVariableCount);
-		_weOwnLocals = true;
+		assert(_locals.empty());
+		_locals = Common::Array<ScriptValue>(localVariableCount);
 		return returnValue;
 	}
 
@@ -175,12 +191,16 @@ ScriptValue CodeChunk::evaluateOperation() {
 		debugCN(5, kDebugScript, "\n    condition: ");
 		ScriptValue condition = evaluateExpression();
 
-		CodeChunk ifBlock(*_bytecode);
-		CodeChunk elseBlock(*_bytecode);
+		if (condition.getType() != kScriptValueTypeBool) {
+			error("Expected bool, got %s", scriptValueTypeToStr(condition.getType()));
+		}
+
 		if (condition.asBool()) {
-			ifBlock.execute(_args, _locals);
+			executeNextBlock();
+			skipNextBlock();
 		} else {
-			elseBlock.execute(_args, _locals);
+			skipNextBlock();
+			executeNextBlock();
 		}
 
 		return returnValue;
@@ -454,7 +474,7 @@ ScriptValue *CodeChunk::readAndReturnVariable() {
 
 	case kVariableScopeLocal: {
 		uint index = id - 1;
-		return &_locals->operator[](index);
+		return &_locals.operator[](index);
 	}
 
 	case kVariableScopeIndirectParameter: {
@@ -538,14 +558,11 @@ ScriptValue CodeChunk::callBuiltInMethod(BuiltInMethod method, ScriptValue &self
 }
 
 CodeChunk::~CodeChunk() {
+	_locals.clear();
+
 	// We don't own the args, so we don't need to delete it.
 	_args = nullptr;
 
-	if (_weOwnLocals) {
-		delete _locals;
-	}
-	_locals = nullptr;
-
 	delete _bytecode;
 	_bytecode = nullptr;
 }
diff --git a/engines/mediastation/mediascript/codechunk.h b/engines/mediastation/mediascript/codechunk.h
index 49abfc8e0e2..61d4c7cbc66 100644
--- a/engines/mediastation/mediascript/codechunk.h
+++ b/engines/mediastation/mediascript/codechunk.h
@@ -36,12 +36,16 @@ public:
 	CodeChunk(Common::SeekableReadStream &chunk);
 	~CodeChunk();
 
-	ScriptValue execute(Common::Array<ScriptValue> *args = nullptr, Common::Array<ScriptValue> *locals = nullptr);
+	ScriptValue executeNextBlock();
+	ScriptValue execute(Common::Array<ScriptValue> *args = nullptr);
 
 	static ScriptValue callBuiltInMethod(BuiltInMethod method, ScriptValue &self, Common::Array<ScriptValue> &args);
 
 private:
+	void skipNextBlock();
+
 	ScriptValue evaluateExpression();
+	ScriptValue evaluateExpression(ExpressionType expressionType);
 	ScriptValue evaluateOperation();
 	ScriptValue evaluateValue();
 	ScriptValue evaluateVariable();
@@ -51,8 +55,9 @@ private:
 
 	ScriptValue evaluateAssign();
 
-	bool _weOwnLocals = false;
-	Common::Array<ScriptValue> *_locals = nullptr;
+	static const uint MAX_LOOP_ITERATION_COUNT = 1000;
+	bool _returnImmediately = false;
+	Common::Array<ScriptValue> _locals;
 	Common::Array<ScriptValue> *_args = nullptr;
 	Common::SeekableReadStream *_bytecode = nullptr;
 };
diff --git a/engines/mediastation/mediascript/eventhandler.cpp b/engines/mediastation/mediascript/eventhandler.cpp
index bc2b27b1c09..0c73d969b77 100644
--- a/engines/mediastation/mediascript/eventhandler.cpp
+++ b/engines/mediastation/mediascript/eventhandler.cpp
@@ -30,9 +30,6 @@ EventHandler::EventHandler(Chunk &chunk) {
 		eventTypeToStr(_type), static_cast<uint>(_type), static_cast<long long int>(chunk.pos()));
 
 	_argumentValue = ScriptValue(&chunk);
-	uint lengthInBytes = Datum(chunk, kDatumTypeUint32_1).u.i;
-	debugC(5, kDebugLoading, "EventHandler::EventHandler(): length = 0x%x (@0x%llx)", lengthInBytes, static_cast<long long int>(chunk.pos()));
-
 	_code = new CodeChunk(chunk);
 }
 
diff --git a/engines/mediastation/mediascript/function.cpp b/engines/mediastation/mediascript/function.cpp
index babb8b7319a..558fb165d0e 100644
--- a/engines/mediastation/mediascript/function.cpp
+++ b/engines/mediastation/mediascript/function.cpp
@@ -31,8 +31,6 @@ Function::Function(Chunk &chunk) {
 	// with 19900 added, so function 100 would be reported as 20000. But in
 	// bytecode, the zero-based ID is used, so that's what we'll store here.
 	_id = Datum(chunk).u.i;
-	uint lengthInBytes = Datum(chunk, kDatumTypeUint32_1).u.i;
-	debugC(5, kDebugLoading, "Function::Function(): id = 0x%x, size = 0x%x bytes", _id, lengthInBytes);
 	_code = new CodeChunk(chunk);
 }
 


Commit: b7c9e1a75ded0056d8fa6e2ea6f4d88291223db3
    https://github.com/scummvm/scummvm/commit/b7c9e1a75ded0056d8fa6e2ea6f4d88291223db3
Author: Nathanael Gentry (nathanael.gentrydb8 at gmail.com)
Date: 2025-04-11T14:54:23-04:00

Commit Message:
MEDIASTATION: Factor opcodes into their own functions

The original has them split out this way, and while it
could have gone either way, I decided to go along with this.

Changed paths:
    engines/mediastation/mediascript/codechunk.cpp
    engines/mediastation/mediascript/codechunk.h
    engines/mediastation/mediascript/collection.cpp
    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 8518d853a97..5a065d1ba9f 100644
--- a/engines/mediastation/mediascript/codechunk.cpp
+++ b/engines/mediastation/mediascript/codechunk.cpp
@@ -87,9 +87,8 @@ ScriptValue CodeChunk::evaluateExpression(ExpressionType expressionType) {
 
 	ScriptValue returnValue;
 	switch (expressionType) {
-	case kExpressionTypeEmpty: {
-		return returnValue;
-	}
+	case kExpressionTypeEmpty:
+		break;
 
 	case kExpressionTypeOperation:
 		returnValue = evaluateOperation();
@@ -107,7 +106,6 @@ ScriptValue CodeChunk::evaluateExpression(ExpressionType expressionType) {
 		error("Got unimplemented expression type %s (%d)",
 			expressionTypeToStr(expressionType), static_cast<uint>(expressionType));
 	}
-
 	return returnValue;
 }
 
@@ -117,231 +115,75 @@ ScriptValue CodeChunk::evaluateOperation() {
 
 	ScriptValue returnValue;
 	switch (opcode) {
-	case kOpcodeAssignVariable: {
-		evaluateAssign();
-		return returnValue;
-	}
-
-	case kOpcodeCallFunction: {
-		uint functionId = Datum(*_bytecode).u.i;
-		uint32 parameterCount = Datum(*_bytecode).u.i;
-		debugC(5, kDebugScript, "%d (%d params)", functionId, parameterCount);
-		return callFunction(functionId, parameterCount);
-	}
-
-	case kOpcodeCallMethod: {
-		// In Media Station, all methods seem be built-in - there don't
-		// seem to be custom objects or methods individual titles can
-		// define. Functions, however, CAN be title-defined.
-		// But here, we're only looking for built-in methods.
-		BuiltInMethod methodId = static_cast<BuiltInMethod>(Datum(*_bytecode).u.i);
-		uint32 parameterCount = Datum(*_bytecode).u.i;
-		debugC(5, kDebugScript, "%s (%d params)", builtInMethodToStr(methodId), parameterCount);
-		debugCN(5, kDebugScript, "  Self: ");
-		ScriptValue selfObject = evaluateExpression();
-		Common::Array<ScriptValue> args;
-		for (uint i = 0; i < parameterCount; i++) {
-			debugCN(5, kDebugScript, "  Param %d: ", i);
-			ScriptValue arg = evaluateExpression();
-			args.push_back(arg);
-		}
-		returnValue = callBuiltInMethod(methodId, selfObject, args);
-		return returnValue;
-	}
-
-	case kOpcodeDeclareVariables: {
-		uint32 localVariableCount = Datum(*_bytecode).u.i;
-		debugC(5, kDebugScript, "%d", localVariableCount);
-		assert(_locals.empty());
-		_locals = Common::Array<ScriptValue>(localVariableCount);
-		return returnValue;
-	}
-
-	case kOpcodeOr: {
-		debugCN(5, kDebugScript, "\n    lhs: ");
-		ScriptValue value1 = evaluateExpression();
-		debugCN(5, kDebugScript, "    rhs: ");
-		ScriptValue value2 = evaluateExpression();
-
-		returnValue.setToBool(value1 || value2);
-		return returnValue;
-	}
-
-	case kOpcodeXor: {
-		debugCN(5, kDebugScript, "\n    lhs: ");
-		ScriptValue value1 = evaluateExpression();
-		debugCN(5, kDebugScript, "    rhs: ");
-		ScriptValue value2 = evaluateExpression();
-
-		returnValue.setToBool(value1 ^ value2);
-		return returnValue;
-	}
-
-	case kOpcodeAnd: {
-		debugCN(5, kDebugScript, "\n    value: ");
-		ScriptValue value1 = evaluateExpression();
-		debugCN(5, kDebugScript, "    rhs: ");
-		ScriptValue value2 = evaluateExpression();
-
-		returnValue.setToBool(value1 && value2);
-		return returnValue;
-	}
-
-	case kOpcodeIfElse: {
-		debugCN(5, kDebugScript, "\n    condition: ");
-		ScriptValue condition = evaluateExpression();
-
-		if (condition.getType() != kScriptValueTypeBool) {
-			error("Expected bool, got %s", scriptValueTypeToStr(condition.getType()));
-		}
-
-		if (condition.asBool()) {
-			executeNextBlock();
-			skipNextBlock();
-		} else {
-			skipNextBlock();
-			executeNextBlock();
-		}
-
-		return returnValue;
-	}
-
-	case kOpcodeEquals: {
-		debugCN(5, kDebugScript, "\n    lhs: ");
-		ScriptValue value1 = evaluateExpression();
-		debugCN(5, kDebugScript, "    rhs: ");
-		ScriptValue value2 = evaluateExpression();
-
-		returnValue.setToBool(value1 == value2);
-		return returnValue;
-	}
-
-	case kOpcodeNotEquals: {
-		debugCN(5, kDebugScript, "\n    lhs: ");
-		ScriptValue value1 = evaluateExpression();
-		debugCN(5, kDebugScript, "    rhs: ");
-		ScriptValue value2 = evaluateExpression();
-
-		returnValue.setToBool(!(value1 == value2));
-		return returnValue;
-	}
-
-	case kOpcodeLessThan: {
-		debugCN(5, kDebugScript, "\n    lhs: ");
-		ScriptValue value1 = evaluateExpression();
-		debugCN(5, kDebugScript, "    rhs: ");
-		ScriptValue value2 = evaluateExpression();
-
-		returnValue.setToBool(value1 < value2);
-		return returnValue;
-	}
-
-	case kOpcodeGreaterThan: {
-		debugCN(5, kDebugScript, "\n    lhs: ");
-		ScriptValue value1 = evaluateExpression();
-		debugCN(5, kDebugScript, "    rhs: ");
-		ScriptValue value2 = evaluateExpression();
-
-		returnValue.setToBool(value1 > value2);
-		return returnValue;
-	}
-
-	case kOpcodeLessThanOrEqualTo: {
-		debugCN(5, kDebugScript, "\n    lhs: ");
-		ScriptValue value1 = evaluateExpression();
-		debugCN(5, kDebugScript, "    rhs: ");
-		ScriptValue value2 = evaluateExpression();
-
-		returnValue.setToBool((value1 < value2) || (value1 == value2));
-		return returnValue;
-	}
-
-	case kOpcodeGreaterThanOrEqualTo: {
-		debugCN(5, kDebugScript, "\n    lhs: ");
-		ScriptValue value1 = evaluateExpression();
-		debugCN(5, kDebugScript, "    rhs: ");
-		ScriptValue value2 = evaluateExpression();
-
-		returnValue.setToBool((value1 > value2) || (value1 == value2));
-		return returnValue;
-	}
-
-	case kOpcodeAdd: {
-		debugCN(5, kDebugScript, "\n    lhs: ");
-		ScriptValue value1 = evaluateExpression();
-		debugCN(5, kDebugScript, "    rhs: ");
-		ScriptValue value2 = evaluateExpression();
-
-		returnValue = value1 + value2;
-		return returnValue;
-	}
-
-	case kOpcodeSubtract: {
-		debugCN(5, kDebugScript, "\n    lhs: ");
-		ScriptValue value1 = evaluateExpression();
-		debugCN(5, kDebugScript, "    rhs: ");
-		ScriptValue value2 = evaluateExpression();
-
-		returnValue = value1 - value2;
-		return returnValue;
-	}
+	case kOpcodeIf:
+		evaluateIf();
+		break;
 
-	case kOpcodeMultiply: {
-		debugCN(5, kDebugScript, "\n    lhs: ");
-		ScriptValue value1 = evaluateExpression();
-		debugCN(5, kDebugScript, "    rhs: ");
-		ScriptValue value2 = evaluateExpression();
+	case kOpcodeIfElse:
+		evaluateIfElse();
+		break;
 
-		returnValue = value1 * value2;
-		return returnValue;
-	}
+	case kOpcodeAssignVariable:
+		evaluateAssign();
+		break;
 
-	case kOpcodeDivide: {
-		debugCN(5, kDebugScript, "\n    lhs: ");
-		ScriptValue value1 = evaluateExpression();
-		debugCN(5, kDebugScript, "    rhs: ");
-		ScriptValue value2 = evaluateExpression();
+	case kOpcodeOr:
+	case kOpcodeXor:
+	case kOpcodeAnd:
+	case kOpcodeEquals:
+	case kOpcodeNotEquals:
+	case kOpcodeLessThan:
+	case kOpcodeGreaterThan:
+	case kOpcodeLessThanOrEqualTo:
+	case kOpcodeGreaterThanOrEqualTo:
+	case kOpcodeAdd:
+	case kOpcodeSubtract:
+	case kOpcodeMultiply:
+	case kOpcodeDivide:
+	case kOpcodeModulo:
+		returnValue = evaluateBinaryOperation(opcode);
+		break;
 
-		returnValue = value1 / value2;
-		return returnValue;
-	}
+	case kOpcodeNegate:
+		returnValue = evaluateUnaryOperation();
+		break;
 
-	case kOpcodeModulo: {
-		debugCN(5, kDebugScript, "\n    lhs: ");
-		ScriptValue value1 = evaluateExpression();
-		debugCN(5, kDebugScript, "    rhs: ");
-		ScriptValue value2 = evaluateExpression();
+	case kOpcodeCallFunction:
+		returnValue = evaluateFunctionCall();
+		break;
 
-		returnValue = value1 % value2;
-		return returnValue;
-	}
+	case kOpcodeCallMethod:
+		returnValue = evaluateMethodCall();
+		break;
 
-	case kOpcodeNegate: {
-		ScriptValue value = evaluateExpression();
-		debugCN(5, kDebugScript, "    value: ");
+	case kOpcodeDeclareLocals:
+		evaluateDeclareLocals();
+		break;
 
-		return -value;
-	}
+	case kOpcodeReturn:
+		returnValue = evaluateReturn();
+		break;
 
-	case kOpcodeReturn: {
-		debugCN(5, kDebugScript, "    return: ");
-		ScriptValue value = evaluateExpression();
+	case kOpcodeReturnNoValue:
+		evaluateReturnNoValue();
+		break;
 
-		return value;
-	}
+	case kOpcodeWhile:
+		evaluateWhileLoop();
+		break;
 
-	case kOpcodeCallFunctionInVariable: {
-		uint parameterCount = Datum(*_bytecode).u.i;
-		ScriptValue variable = evaluateExpression();
-		uint functionId = variable.asFunctionId();
-		debugC(5, kDebugScript, "[Indirect function %d] (%d params)", functionId, parameterCount);
+	case kOpcodeCallFunctionInVariable:
+		returnValue = evaluateFunctionCall(true);
+		break;
 
-		return callFunction(functionId, parameterCount);
-	}
+	case kOpcodeCallMethodInVariable:
+		returnValue = evaluateMethodCall(true);
+		break;
 
 	default:
 		error("Got unimplemented opcode %s (%d)", opcodeToStr(opcode), static_cast<uint>(opcode));
 	}
+	return returnValue;
 }
 
 ScriptValue CodeChunk::evaluateValue() {
@@ -434,29 +276,6 @@ ScriptValue CodeChunk::evaluateVariable() {
 	return *variable;
 }
 
-ScriptValue CodeChunk::callFunction(uint functionId, uint parameterCount) {
-	Common::Array<ScriptValue> args;
-	for (uint i = 0; i < parameterCount; i++) {
-		debugCN(5, kDebugScript, "  Param %d: ", i);
-		ScriptValue arg = evaluateExpression();
-		args.push_back(arg);
-	}
-
-	ScriptValue returnValue;
-	Function *function = g_engine->getFunctionById(functionId);
-	if (function != nullptr) {
-		// This is a title-defined function.
-		returnValue = function->execute(args);
-	} else {
-		// This is a function built in (and global to) the engine.
-		BuiltInFunction builtInFunctionId = static_cast<BuiltInFunction>(functionId);
-		debugC(5, kDebugScript, "  Function Name: %s ", builtInFunctionToStr(builtInFunctionId));
-		returnValue = g_engine->callBuiltInFunction(builtInFunctionId, args);
-	}
-
-	return returnValue;
-}
-
 ScriptValue *CodeChunk::readAndReturnVariable() {
 	uint id = Datum(*_bytecode).u.i;
 	VariableScope scope = static_cast<VariableScope>(Datum(*_bytecode).u.i);
@@ -496,6 +315,36 @@ ScriptValue *CodeChunk::readAndReturnVariable() {
 	}
 }
 
+void CodeChunk::evaluateIf() {
+	debugCN(5, kDebugScript, "\n    condition: ");
+	ScriptValue condition = evaluateExpression();
+	if (condition.getType() != kScriptValueTypeBool) {
+		error("evaluateIf: Expected bool condition, got %s", scriptValueTypeToStr(condition.getType()));
+	}
+
+	if (condition.asBool()) {
+		executeNextBlock();
+	} else {
+		skipNextBlock();
+	}
+}
+
+void CodeChunk::evaluateIfElse() {
+	debugCN(5, kDebugScript, "\n    condition: ");
+	ScriptValue condition = evaluateExpression();
+	if (condition.getType() != kScriptValueTypeBool) {
+		error("evaluateIfElse: Expected bool condition, got %s", scriptValueTypeToStr(condition.getType()));
+	}
+
+	if (condition.asBool()) {
+		executeNextBlock();
+		skipNextBlock();
+	} else {
+		skipNextBlock();
+		executeNextBlock();
+	}
+}
+
 ScriptValue CodeChunk::evaluateAssign() {
 	debugCN(5, kDebugScript, "Variable ");
 	ScriptValue *targetVariable = readAndReturnVariable();
@@ -515,12 +364,157 @@ ScriptValue CodeChunk::evaluateAssign() {
 	}
 }
 
-ScriptValue CodeChunk::callBuiltInMethod(BuiltInMethod method, ScriptValue &self, Common::Array<ScriptValue> &args) {
+ScriptValue CodeChunk::evaluateBinaryOperation(Opcode op) {
+	debugCN(5, kDebugScript, "\n    lhs: ");
+	ScriptValue value1 = evaluateExpression();
+	debugCN(5, kDebugScript, "    rhs: ");
+	ScriptValue value2 = evaluateExpression();
+
+	ScriptValue returnValue;
+	switch (op) {
+	case kOpcodeOr:
+		returnValue.setToBool(value1 || value2);
+		break;
+
+	case kOpcodeXor:
+		returnValue.setToBool(value1 ^ value2);
+		break;
+
+	case kOpcodeAnd:
+		returnValue.setToBool(value1 && value2);
+		break;
+
+	case kOpcodeEquals:
+		returnValue.setToBool(value1 == value2);
+		break;
+
+	case kOpcodeNotEquals:
+		returnValue.setToBool(value1 != value2);
+		break;
+
+	case kOpcodeLessThan:
+		returnValue.setToBool(value1 < value2);
+		break;
+
+	case kOpcodeGreaterThan:
+		returnValue.setToBool(value1 > value2);
+		break;
+
+	case kOpcodeLessThanOrEqualTo:
+		returnValue.setToBool(value1 <= value2);
+		break;
+
+	case kOpcodeGreaterThanOrEqualTo:
+		returnValue.setToBool(value1 >= value2);
+		break;
+
+	case kOpcodeAdd:
+		returnValue = value1 + value2;
+		break;
+
+	case kOpcodeSubtract:
+		returnValue = value1 - value2;
+		break;
+
+	case kOpcodeMultiply:
+		returnValue = value1 * value2;
+		break;
+
+	case kOpcodeDivide:
+		returnValue = value1 / value2;
+		break;
+
+	case kOpcodeModulo:
+		returnValue = value1 % value2;
+		break;
+
+	default:
+		error("Got unknown binary operation opcode %s", opcodeToStr(op));
+	}
+	return returnValue;
+}
+
+ScriptValue CodeChunk::evaluateUnaryOperation() {
+	// The only supported unary operation seems to be negation.
+	ScriptValue value = evaluateExpression();
+	debugCN(5, kDebugScript, "    value: ");
+	return -value;
+}
+
+ScriptValue CodeChunk::evaluateFunctionCall(bool isIndirect) {
+	uint functionId, paramCount = 0;
+	if (isIndirect) {
+		paramCount = Datum(*_bytecode).u.i;
+		ScriptValue value = evaluateExpression();
+		functionId = value.asFunctionId();
+	} else {
+		functionId = Datum(*_bytecode).u.i;
+		paramCount = Datum(*_bytecode).u.i;
+	}
+
+	return evaluateFunctionCall(functionId, paramCount);
+}
+
+ScriptValue CodeChunk::evaluateFunctionCall(uint functionId, uint paramCount) {
+	debugC(5, kDebugScript, "%d (%d params)", functionId, paramCount);
+
+	Common::Array<ScriptValue> args;
+	for (uint i = 0; i < paramCount; i++) {
+		debugCN(5, kDebugScript, "  Param %d: ", i);
+		ScriptValue arg = evaluateExpression();
+		args.push_back(arg);
+	}
+
 	ScriptValue returnValue;
+	Function *function = g_engine->getFunctionById(functionId);
+	if (function != nullptr) {
+		// This is a title-defined function.
+		returnValue = function->execute(args);
+	} else {
+		// This is a function built in (and global to) the engine.
+		BuiltInFunction builtInFunctionId = static_cast<BuiltInFunction>(functionId);
+		debugC(5, kDebugScript, "  Function Name: %s ", builtInFunctionToStr(builtInFunctionId));
+		returnValue = g_engine->callBuiltInFunction(builtInFunctionId, args);
+	}
 
-	switch (self.getType()) {
+	return returnValue;
+}
+
+ScriptValue CodeChunk::evaluateMethodCall(bool isIndirect) {
+	BuiltInMethod method;
+	uint paramCount = 0;
+	if (isIndirect) {
+		paramCount = Datum(*_bytecode).u.i;
+		ScriptValue value = evaluateExpression();
+		method = value.asMethodId();
+	} else {
+		method = static_cast<BuiltInMethod>(Datum(*_bytecode).u.i);
+		paramCount = Datum(*_bytecode).u.i;
+	}
+
+	return evaluateMethodCall(method, paramCount);
+}
+
+ScriptValue CodeChunk::evaluateMethodCall(BuiltInMethod method, uint paramCount) {
+	// In Media Station, all methods are built-in - there aren't
+	// custom objects or methods individual titles can
+	// define. Functions, however, CAN be title-defined.
+	// But here, we're only looking for built-in methods.
+	debugC(5, kDebugScript, "%s (%d params)", builtInMethodToStr(method), paramCount);
+	debugCN(5, kDebugScript, "  Self: ");
+
+	ScriptValue target = evaluateExpression();
+	Common::Array<ScriptValue> args;
+	for (uint i = 0; i < paramCount; i++) {
+		debugCN(5, kDebugScript, "  Param %d: ", i);
+		ScriptValue arg = evaluateExpression();
+		args.push_back(arg);
+	}
+
+	ScriptValue returnValue;
+	switch (target.getType()) {
 	case kScriptValueTypeAssetId: {
-		if (self.asAssetId() == 1) {
+		if (target.asAssetId() == 1) {
 			// This is a "document" method that we need to handle specially.
 			// The document (@doc) accepts engine-level methods like changing the
 			// active screen.
@@ -528,32 +522,76 @@ ScriptValue CodeChunk::callBuiltInMethod(BuiltInMethod method, ScriptValue &self
 			// just to house these methods. Rather, we just call in the engine.
 			returnValue = g_engine->callMethod(method, args);
 			return returnValue;
-		} else if (self.asAssetId() == 0) {
+		} else if (target.asAssetId() == 0) {
 			// It seems to be valid to call a method on a null asset ID, in
 			// which case nothing happens. Still issue warning for traceability.
-			warning("CodeChunk::callBuiltInMethod(): Attempt to call method on a null asset ID");
+			warning("Attempt to call method on a null asset ID");
 			return returnValue;
 		} else {
 			// This is a regular asset that we can process directly.
-			uint assetId = self.asAssetId();
-			Asset *selfAsset = g_engine->getAssetById(assetId);
-			if (selfAsset == nullptr) {
-				error("CodeChunk::callBuiltInMethod(): Attempt to call method on asset ID %d, which isn't loaded", self.asAssetId());
+			uint assetId = target.asAssetId();
+			Asset *targetAsset = g_engine->getAssetById(assetId);
+			if (targetAsset == nullptr) {
+				error("Attempt to call method on asset ID %d, which isn't loaded", target.asAssetId());
 			}
-			returnValue = selfAsset->callMethod(method, args);
+			returnValue = targetAsset->callMethod(method, args);
 			return returnValue;
 		}
 	}
 
 	case kScriptValueTypeCollection: {
-		Common::SharedPtr<Collection> collection = self.asCollection();
+		Common::SharedPtr<Collection> collection = target.asCollection();
 		returnValue = collection->callMethod(method, args);
 		return returnValue;
 	}
 
 	default:
-		error("CodeChunk::callBuiltInMethod(): Attempt to call method on unimplemented ScriptValue type %s (%d)",
-			scriptValueTypeToStr(self.getType()), static_cast<uint>(self.getType()));
+		error("Attempt to call method on unimplemented value type %s (%d)",
+			scriptValueTypeToStr(target.getType()), static_cast<uint>(target.getType()));
+	}
+}
+
+void CodeChunk::evaluateDeclareLocals() {
+	uint localVariableCount = Datum(*_bytecode).u.i;
+	if (localVariableCount <= 0) {
+		error("Got non-positive local variable count");
+	}
+	debugC(5, kDebugScript, "%d", localVariableCount);
+	_locals = Common::Array<ScriptValue>(localVariableCount);
+}
+
+ScriptValue CodeChunk::evaluateReturn() {
+	ScriptValue returnValue = evaluateExpression();
+	_returnImmediately = true;
+	return returnValue;
+}
+
+void CodeChunk::evaluateReturnNoValue() {
+	_returnImmediately = true;
+}
+
+void CodeChunk::evaluateWhileLoop() {
+	uint loopStartPosition = _bytecode->pos();
+	uint iterationCount = 0;
+
+	while (true) {
+		// Seek to the top of the loop bytecode.
+		_bytecode->seek(loopStartPosition);
+		ScriptValue condition = evaluateExpression();
+		if (condition.getType() != kScriptValueTypeBool) {
+			error("Expected loop condition to be bool, not %s", scriptValueTypeToStr(condition.getType()));
+		}
+
+		if (++iterationCount >= MAX_LOOP_ITERATION_COUNT) {
+			error("Exceeded max loop iteration count");
+		}
+
+		if (condition.asBool()) {
+			executeNextBlock();
+		} else {
+			skipNextBlock();
+			break;
+		}
 	}
 }
 
diff --git a/engines/mediastation/mediascript/codechunk.h b/engines/mediastation/mediascript/codechunk.h
index 61d4c7cbc66..3d6b593ca67 100644
--- a/engines/mediastation/mediascript/codechunk.h
+++ b/engines/mediastation/mediascript/codechunk.h
@@ -39,8 +39,6 @@ public:
 	ScriptValue executeNextBlock();
 	ScriptValue execute(Common::Array<ScriptValue> *args = nullptr);
 
-	static ScriptValue callBuiltInMethod(BuiltInMethod method, ScriptValue &self, Common::Array<ScriptValue> &args);
-
 private:
 	void skipNextBlock();
 
@@ -50,10 +48,21 @@ private:
 	ScriptValue evaluateValue();
 	ScriptValue evaluateVariable();
 
-	ScriptValue callFunction(uint functionId, uint parameterCount);
 	ScriptValue *readAndReturnVariable();
 
+	void evaluateIf();
+	void evaluateIfElse();
 	ScriptValue evaluateAssign();
+	ScriptValue evaluateBinaryOperation(Opcode op);
+	ScriptValue evaluateUnaryOperation();
+	ScriptValue evaluateFunctionCall(bool isIndirect = false);
+	ScriptValue evaluateFunctionCall(uint functionId, uint paramCount);
+	ScriptValue evaluateMethodCall(bool isIndirect = false);
+	ScriptValue evaluateMethodCall(BuiltInMethod method, uint paramCount);
+	void evaluateDeclareLocals();
+	ScriptValue evaluateReturn();
+	void evaluateReturnNoValue();
+	void evaluateWhileLoop();
 
 	static const uint MAX_LOOP_ITERATION_COUNT = 1000;
 	bool _returnImmediately = false;
diff --git a/engines/mediastation/mediascript/collection.cpp b/engines/mediastation/mediascript/collection.cpp
index 5c591c239eb..3f586f0f15d 100644
--- a/engines/mediastation/mediascript/collection.cpp
+++ b/engines/mediastation/mediascript/collection.cpp
@@ -82,7 +82,13 @@ ScriptValue Collection::callMethod(BuiltInMethod method, Common::Array<ScriptVal
 		Common::Array<ScriptValue> sendArgs;
 		for (uint i = 0; i < size(); i++) {
 			ScriptValue self = operator[](i);
-			CodeChunk::callBuiltInMethod(methodToSend, self, sendArgs);
+
+			uint assetId = self.asAssetId();
+			Asset *selfAsset = g_engine->getAssetById(assetId);
+			if (selfAsset != nullptr) {
+				Common::Array<ScriptValue> emptyArgs;
+				returnValue = selfAsset->callMethod(methodToSend, emptyArgs);
+			}
 		}
 		return returnValue;
 	}
diff --git a/engines/mediastation/mediascript/scriptconstants.cpp b/engines/mediastation/mediascript/scriptconstants.cpp
index 20fbfc98757..f2ff59527e7 100644
--- a/engines/mediastation/mediascript/scriptconstants.cpp
+++ b/engines/mediastation/mediascript/scriptconstants.cpp
@@ -40,6 +40,8 @@ const char *expressionTypeToStr(ExpressionType type) {
 
 const char *opcodeToStr(Opcode opcode) {
 	switch (opcode) {
+	case kOpcodeIf:
+		return "If";
 	case kOpcodeIfElse:
 		return "IfElse";
 	case kOpcodeAssignVariable:
@@ -78,16 +80,18 @@ const char *opcodeToStr(Opcode opcode) {
 		return "CallFunction";
 	case kOpcodeCallMethod:
 		return "CallMethod";
-	case kOpcodeDeclareVariables:
-		return "DeclareVariables";
+	case kOpcodeDeclareLocals:
+		return "DeclareLocals";
 	case kOpcodeReturn:
 		return "Return";
-	case kOpcodeUnk1:
-		return "UNKNOWN (Unk1)";
-	case kOpcodeCallFunctionInVariable:
-		return "CallFunctionInVariable";
+	case kOpcodeReturnNoValue:
+		return "ReturnNoValue";
 	case kOpcodeWhile:
 		return "While";
+	case kOpcodeCallFunctionInVariable:
+		return "CallFunctionInVariable";
+	case kOpcodeCallMethodInVariable:
+		return "CallMethodInVariable";
 	default:
 		return "UNKNOWN";
 	}
diff --git a/engines/mediastation/mediascript/scriptconstants.h b/engines/mediastation/mediascript/scriptconstants.h
index 04c1d1be1e6..7a0b94f0d40 100644
--- a/engines/mediastation/mediascript/scriptconstants.h
+++ b/engines/mediastation/mediascript/scriptconstants.h
@@ -33,6 +33,7 @@ enum ExpressionType {
 const char *expressionTypeToStr(ExpressionType type);
 
 enum Opcode {
+	kOpcodeIf = 201,
 	kOpcodeIfElse = 202,
 	kOpcodeAssignVariable = 203,
 	kOpcodeOr = 204,
@@ -52,16 +53,12 @@ enum Opcode {
 	kOpcodeNegate = 218,
 	kOpcodeCallFunction = 219,
 	kOpcodeCallMethod = 220,
-	// This seems to appear at the start of a function to declare the number of
-	// local variables used in the function. It seems to be the `Declare`
-	// keyword. In the observed examples, the number of variables to create is
-	// given, then the next instructions are variable assignments for that number
-	// of variables.
-	kOpcodeDeclareVariables = 221,
-	kOpcodeWhile = 224,
+	kOpcodeDeclareLocals = 221,
 	kOpcodeReturn = 222,
-	kOpcodeUnk1 = 223,
-	kOpcodeCallFunctionInVariable = 225
+	kOpcodeReturnNoValue = 223,
+	kOpcodeWhile = 224,
+	kOpcodeCallFunctionInVariable = 225, // IndirectCall
+	kOpcodeCallMethodInVariable = 226 // IndirectMsg
 };
 const char *opcodeToStr(Opcode opcode);
 




More information about the Scummvm-git-logs mailing list