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

sluicebox noreply at scummvm.org
Tue Sep 17 03:29:43 UTC 2024


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

Summary:
090a93995e AGI: Cleanup `Words`
20b5faf9a3 AGI: Move Apple IIgs timing workarounds into functions
22a4111c57 AGI: Add Apple II speed controls, implement set.speed
fdaf660bbc AGI: Update Gold Rush clock workaround for all versions


Commit: 090a93995efdce0f3f67960651d94e00197de447
    https://github.com/scummvm/scummvm/commit/090a93995efdce0f3f67960651d94e00197de447
Author: sluicebox (22204938+sluicebox at users.noreply.github.com)
Date: 2024-09-16T20:27:39-07:00

Commit Message:
AGI: Cleanup `Words`

Changed paths:
    engines/agi/words.cpp
    engines/agi/words.h


diff --git a/engines/agi/words.cpp b/engines/agi/words.cpp
index 522d420f160..30590c30885 100644
--- a/engines/agi/words.cpp
+++ b/engines/agi/words.cpp
@@ -71,9 +71,9 @@ int Words::loadDictionary_v1(Common::SeekableReadStream &stream) {
 
 int Words::loadDictionary(const char *fname) {
 	Common::File fp;
-
 	if (!fp.open(fname)) {
 		warning("loadDictionary: can't open %s", fname);
+		// FIXME
 		return errOK; // err_BadFileOpen
 	}
 
@@ -135,12 +135,12 @@ int Words::loadExtendedDictionary(const char *sierraFname) {
 	const char *fname = fnameStr.c_str();
 
 	Common::File fp;
-
 	if (!fp.open(fname)) {
 		warning("loadWords: can't open %s", fname);
+		// FIXME
 		return errOK; // err_BadFileOpen
 	}
-	debug(0, "Loading dictionary: %s", fname);
+	debug(0, "Loading extended dictionary: %s", fname);
 
 	// skip the header
 	fp.readString('\n');
@@ -149,7 +149,7 @@ int Words::loadExtendedDictionary(const char *sierraFname) {
 		WordEntry *newWord = new WordEntry;
 		newWord->word = fp.readString();
 		newWord->id = atoi(fp.readString('\n').c_str());
-		if(!newWord->word.empty())
+		if (!newWord->word.empty())
 			_dictionaryWords[(byte)newWord->word[0] - 'a'].push_back(newWord);
 	}
 
@@ -194,11 +194,9 @@ static bool isCharSeparator(const char curChar) {
 	case '{':
 	case '}':
 		return true;
-		break;
 	default:
-		break;
+		return false;
 	}
-	return false;
 }
 
 static bool isCharInvalid(const char curChar) {
@@ -209,19 +207,15 @@ static bool isCharInvalid(const char curChar) {
 	case '\\':
 	case '"':
 		return true;
-		break;
 	default:
-		break;
+		return false;
 	}
-	return false;
 }
 
 void Words::cleanUpInput(const char *rawUserInput, Common::String &cleanInput) {
-	byte curChar = 0;
-
 	cleanInput.clear();
 
-	curChar = *rawUserInput;
+	byte curChar = *rawUserInput;
 	while (curChar) {
 		// skip separators / invalid characters
 		if (isCharSeparator(curChar) || isCharInvalid(curChar)) {
@@ -250,11 +244,11 @@ void Words::cleanUpInput(const char *rawUserInput, Common::String &cleanInput) {
 	}
 }
 
-int16 Words::findWordInDictionary(const Common::String &userInputLowcased, uint16 userInputLen, uint16 userInputPos, uint16 &foundWordLen) {
+int16 Words::findWordInDictionary(const Common::String &userInputLowercase, uint16 userInputLen, uint16 userInputPos, uint16 &foundWordLen) {
 	uint16 userInputLeft = userInputLen - userInputPos;
 	uint16 wordStartPos = userInputPos;
 	int16 wordId = DICTIONARY_RESULT_UNKNOWN;
-	byte  firstChar = userInputLowcased[userInputPos];
+	byte  firstChar = userInputLowercase[userInputPos];
 
 	foundWordLen = 0;
 
@@ -262,7 +256,7 @@ int16 Words::findWordInDictionary(const Common::String &userInputLowcased, uint1
 
 	if ((firstChar >= 'a') && (firstChar <= lastCharInAbc)) {
 		// word has to start with a letter
-		if (((userInputPos + 1) < userInputLen) && (userInputLowcased[userInputPos + 1] == ' ')) {
+		if (((userInputPos + 1) < userInputLen) && (userInputLowercase[userInputPos + 1] == ' ')) {
 			// current word is 1 char only?
 			if ((firstChar == 'a') || (firstChar == 'i')) {
 				// and it's "a" or "i"? -> then set current type to ignore
@@ -284,7 +278,7 @@ int16 Words::findWordInDictionary(const Common::String &userInputLowcased, uint1
 
 				userInputPos = wordStartPos;
 				while (curCompareLeft) {
-					byte curUserInputChar = userInputLowcased[userInputPos];
+					byte curUserInputChar = userInputLowercase[userInputPos];
 					byte curDictionaryChar = dictionaryEntry->word[dictionaryWordPos];
 
 					if (curUserInputChar != curDictionaryChar)
@@ -297,7 +291,7 @@ int16 Words::findWordInDictionary(const Common::String &userInputLowcased, uint1
 
 				if (!curCompareLeft) {
 					// check, if there is also nothing more of user input left or if a space the follow-up char?
-					if ((userInputPos >= userInputLen) || (userInputLowcased[userInputPos] == ' ')) {
+					if ((userInputPos >= userInputLen) || (userInputLowercase[userInputPos] == ' ')) {
 						// so fully matched, remember match
 						wordId = dictionaryEntry->id;
 						foundWordLen = dictionaryWordLen;
@@ -316,7 +310,7 @@ int16 Words::findWordInDictionary(const Common::String &userInputLowcased, uint1
 	if (foundWordLen == 0) {
 		userInputPos = wordStartPos;
 		while (userInputPos < userInputLen) {
-			if (userInputLowcased[userInputPos] == ' ') {
+			if (userInputLowercase[userInputPos] == ' ') {
 				break;
 			}
 			userInputPos++;
@@ -327,14 +321,6 @@ int16 Words::findWordInDictionary(const Common::String &userInputLowcased, uint1
 }
 
 void Words::parseUsingDictionary(const char *rawUserInput) {
-	Common::String userInput;
-	Common::String userInputLowcased;
-	const char *userInputPtr = nullptr;
-	uint16 userInputLen;
-	uint16 userInputPos = 0;
-	uint16 foundWordLen = 0;
-	uint16 wordCount = 0;
-
 	assert(rawUserInput);
 	debugC(2, kDebugLevelScripts, "parse: userinput = \"%s\"", rawUserInput);
 
@@ -342,70 +328,33 @@ void Words::parseUsingDictionary(const char *rawUserInput) {
 	clearEgoWords();
 
 	// clean up user input
+	Common::String userInput;
 	cleanUpInput(rawUserInput, userInput);
 
 	// Sierra compared independent of upper case and lower case
-	userInputLowcased = userInput;
-	userInputLowcased.toLowercase();
+	Common::String userInputLowercase = userInput;
+	userInputLowercase.toLowercase();
 
 	if (_vm->getLanguage() == Common::RU_RUS) {
-		const char *conv =
-			// АБВГДЕЖЗИЙКЛМНОП
-			  "abvgdewziiklmnop" // 80
-			// РСТУФХЦЧШЩЪЫЬЭЮЯ
-			  "rstufxcyhhjijeuq" // 90
-			// абвгдежзийклмноп
-			  "abvgdewziiklmnop" // a0
-			  "                " // b0
-			  "                " // c0
-			  "                " // d0
-			// рстуфхцчшщъыьэюя
-			  "rstufxcyhhjijeuq" // e0
-			// Ее
-			  "ee              ";// f0
-
-		Common::String tr;
-		for (uint i = 0; i < userInputLowcased.size(); i++) {
-			if ((byte)userInputLowcased[i] >= 0x80) {
-				tr += conv[(byte)userInputLowcased[i] - 0x80];
-			} else {
-				tr += (byte)userInputLowcased[i];
-			}
-		}
-		userInputLowcased = tr;
+		convertRussianUserInput(userInputLowercase);
 	}
 
-	userInputLen = userInput.size();
-	userInputPtr = userInput.c_str();
-
-	// WORKAROUND: For Apple II support speed changes
-	// some of the games hadn't this feature
-	// some (like PQ1) had it, but we override the speed that the game request
-	// with `timeDelayOverwrite`
-	// this mechanism works for all the games, and therefore, doesn't bother to search in the dictionary
-	if (_vm->getPlatform() == Common::kPlatformApple2GS) {
-		if (userInput.equals("fastest")) {
-			_vm->_game.setAppleIIgsSpeedLevel(0);
-			return;
-		} else if (userInput.equals("fast")) {
-			_vm->_game.setAppleIIgsSpeedLevel(1);
-			return;
-		} else if (userInput.equals("normal")) {
-			_vm->_game.setAppleIIgsSpeedLevel(2);
-			return;
-		} else if (userInput.equals("slow")) {
-			_vm->_game.setAppleIIgsSpeedLevel(3);
-			return;
-		}
+	if (handleSpeedCommands(userInputLowercase)) {
+		return;
 	}
 
+	uint16 wordCount = 0;
+	uint16 userInputPos = 0;
+	uint16 userInputLen = userInput.size();
+	const char *userInputPtr = userInput.c_str();
 	while (userInputPos < userInputLen) {
 		// Skip trailing space
 		if (userInput[userInputPos] == ' ')
 			userInputPos++;
 
 		uint16 foundWordPos = userInputPos;
-		int16 foundWordId = findWordInDictionary(userInputLowcased, userInputLen, userInputPos, foundWordLen);
+		uint16 foundWordLen = 0;
+		int16 foundWordId = findWordInDictionary(userInputLowercase, userInputLen, userInputPos, foundWordLen);
 
 		if (foundWordId != DICTIONARY_RESULT_IGNORE) {
 			// word not supposed to get ignored
@@ -440,16 +389,73 @@ void Words::parseUsingDictionary(const char *rawUserInput) {
 	_vm->setFlag(VM_FLAG_SAID_ACCEPTED_INPUT, false);
 }
 
-uint16 Words::getEgoWordCount() {
+uint16 Words::getEgoWordCount() const {
 	return _egoWordCount;
 }
-const char *Words::getEgoWord(int16 wordNr) {
+
+const char *Words::getEgoWord(int16 wordNr) const {
 	assert(wordNr >= 0 && wordNr < MAX_WORDS);
 	return _egoWords[wordNr].word.c_str();
 }
-uint16 Words::getEgoWordId(int16 wordNr) {
+
+uint16 Words::getEgoWordId(int16 wordNr) const {
 	assert(wordNr >= 0 && wordNr < MAX_WORDS);
 	return _egoWords[wordNr].id;
 }
 
+bool Words::handleSpeedCommands(const Common::String &userInputLowercase) {
+	// WORKAROUND: For Apple II support speed changes
+	// some of the games hadn't this feature
+	// some (like PQ1) had it, but we override the speed that the game request
+	// with `timeDelayOverwrite`
+	// this mechanism works for all the games, and therefore, doesn't bother to search in the dictionary
+	switch (_vm->getPlatform()) {
+	case Common::kPlatformApple2GS:
+		if (userInputLowercase == "fastest") {
+			_vm->_game.setAppleIIgsSpeedLevel(0);
+			return true;
+		} else if (userInputLowercase == "fast") {
+			_vm->_game.setAppleIIgsSpeedLevel(1);
+			return true;
+		} else if (userInputLowercase == "normal") {
+			_vm->_game.setAppleIIgsSpeedLevel(2);
+			return true;
+		} else if (userInputLowercase == "slow") {
+			_vm->_game.setAppleIIgsSpeedLevel(3);
+			return true;
+		}
+		break;
+	default:
+		break;
+	}
+	return false;
+}
+
+void Words::convertRussianUserInput(Common::String &userInputLowercase) {
+	const char *conv =
+		// АБВГДЕЖЗИЙКЛМНОП
+		  "abvgdewziiklmnop" // 80
+		// РСТУФХЦЧШЩЪЫЬЭЮЯ
+		  "rstufxcyhhjijeuq" // 90
+		// абвгдежзийклмноп
+		  "abvgdewziiklmnop" // a0
+		  "                " // b0
+		  "                " // c0
+		  "                " // d0
+		// рстуфхцчшщъыьэюя
+		  "rstufxcyhhjijeuq" // e0
+		// Ее
+		  "ee              ";// f0
+
+	Common::String tr;
+	for (uint i = 0; i < userInputLowercase.size(); i++) {
+		if ((byte)userInputLowercase[i] >= 0x80) {
+			tr += conv[(byte)userInputLowercase[i] - 0x80];
+		} else {
+			tr += (byte)userInputLowercase[i];
+		}
+	}
+	userInputLowercase = tr;
+}
+
 } // End of namespace Agi
diff --git a/engines/agi/words.h b/engines/agi/words.h
index 2626dc6fc44..f85eb45f96a 100644
--- a/engines/agi/words.h
+++ b/engines/agi/words.h
@@ -48,9 +48,9 @@ private:
 	uint16  _egoWordCount;
 
 public:
-	uint16 getEgoWordCount();
-	const char *getEgoWord(int16 wordNr);
-	uint16 getEgoWordId(int16 wordNr);
+	uint16 getEgoWordCount() const;
+	const char *getEgoWord(int16 wordNr) const;
+	uint16 getEgoWordId(int16 wordNr) const;
 
 	int  loadDictionary_v1(Common::SeekableReadStream &stream);
 	int  loadDictionary(const char *fname);
@@ -64,7 +64,10 @@ public:
 
 private:
 	void  cleanUpInput(const char *userInput, Common::String &cleanInput);
-	int16 findWordInDictionary(const Common::String &userInput, uint16 userInputLen, uint16 userInputPos, uint16 &foundWordLen);
+	int16 findWordInDictionary(const Common::String &userInputLowercase, uint16 userInputLen, uint16 userInputPos, uint16 &foundWordLen);
+
+	bool handleSpeedCommands(const Common::String &userInputLowercase);
+	static void convertRussianUserInput(Common::String &userInputLowercase);
 };
 
 } // End of namespace Agi


Commit: 20b5faf9a333ba458e73f408de354fb791df1b6e
    https://github.com/scummvm/scummvm/commit/20b5faf9a333ba458e73f408de354fb791df1b6e
Author: sluicebox (22204938+sluicebox at users.noreply.github.com)
Date: 2024-09-16T20:27:39-07:00

Commit Message:
AGI: Move Apple IIgs timing workarounds into functions

Changed paths:
    engines/agi/agi.h
    engines/agi/appleIIgs_timedelay_overwrite.h
    engines/agi/cycle.cpp


diff --git a/engines/agi/agi.h b/engines/agi/agi.h
index 1a1104357c6..544cd826594 100644
--- a/engines/agi/agi.h
+++ b/engines/agi/agi.h
@@ -359,6 +359,8 @@ enum CycleInnerLoopType {
 
 typedef Common::Array<int16> SavedGameSlotIdArray;
 
+struct AgiAppleIIgsDelayOverwriteGameEntry;
+
 /**
  * AGI game structure.
  * This structure contains all global data of an AGI game executed
@@ -933,6 +935,9 @@ public:
 
 	const Common::String getTargetName() const { return _targetName; }
 
+private:
+	byte getAppleIIgsTimeDelay(const AgiAppleIIgsDelayOverwriteGameEntry *appleIIgsDelayOverwrite, byte &newTimeDelay) const;
+
 	// Objects
 public:
 	int loadObjects(const char *fname);
diff --git a/engines/agi/appleIIgs_timedelay_overwrite.h b/engines/agi/appleIIgs_timedelay_overwrite.h
index 24133a84d7c..d474dd53653 100644
--- a/engines/agi/appleIIgs_timedelay_overwrite.h
+++ b/engines/agi/appleIIgs_timedelay_overwrite.h
@@ -30,7 +30,7 @@ struct AgiAppleIIgsDelayOverwriteRoomEntry {
 	int16 activePictureNr; // resource number of current active background picture
 	int16 timeDelayOverwrite; // time delay here is like on PC
 	// so 0 - unlimited, 1 - 20 cycles, 2 - 10 cycles, -1 means do not touch speed set by scripts
-	bool onlyWhenPlayerNotInControl; // only sets spee, when play is not in control
+	bool onlyWhenPlayerNotInControl; // only sets speed, when play is not in control
 };
 
 struct AgiAppleIIgsDelayOverwriteGameEntry {
@@ -95,6 +95,16 @@ static const AgiAppleIIgsDelayOverwriteGameEntry appleIIgsDelayOverwriteGameTabl
 	{ GID_AGIDEMO,   -1, nullptr }
 };
 
+static const AgiAppleIIgsDelayOverwriteGameEntry *getAppleIIgsDelayOverwriteGameEntry(uint32 gameId) {
+	const AgiAppleIIgsDelayOverwriteGameEntry *appleIIgsDelayOverwrite = appleIIgsDelayOverwriteGameTable;
+	while (appleIIgsDelayOverwrite->gameId != GID_AGIDEMO) {
+		if (appleIIgsDelayOverwrite->gameId == gameId)
+			break; // game found
+		appleIIgsDelayOverwrite++;
+	}
+	return appleIIgsDelayOverwrite;
+}
+
 } // End of namespace Agi
 
 #endif /* AGI_APPLEIIGS_DELAY_OVERWRITE_H */
diff --git a/engines/agi/cycle.cpp b/engines/agi/cycle.cpp
index cd2e8fa3e9e..6c52b9ac2df 100644
--- a/engines/agi/cycle.cpp
+++ b/engines/agi/cycle.cpp
@@ -331,9 +331,6 @@ uint16 AgiEngine::processAGIEvents() {
 }
 
 void AgiEngine::playGame() {
-	const AgiAppleIIgsDelayOverwriteGameEntry *appleIIgsDelayOverwrite = nullptr;
-	const AgiAppleIIgsDelayOverwriteRoomEntry *appleIIgsDelayRoomOverwrite = nullptr;
-
 	debugC(2, kDebugLevelMain, "initializing...");
 	debugC(2, kDebugLevelMain, "game version = 0x%x", getVersion());
 
@@ -375,14 +372,10 @@ void AgiEngine::playGame() {
 
 	artificialDelay_Reset();
 
+	const AgiAppleIIgsDelayOverwriteGameEntry *appleIIgsDelayOverwrite = nullptr;
 	if (getPlatform() == Common::kPlatformApple2GS) {
 		// Look up, if there is a time delay overwrite table for the current game
-		appleIIgsDelayOverwrite = appleIIgsDelayOverwriteGameTable;
-		while (appleIIgsDelayOverwrite->gameId != GID_AGIDEMO) {
-			if (appleIIgsDelayOverwrite->gameId == getGameID())
-				break; // game found
-			appleIIgsDelayOverwrite++;
-		}
+		appleIIgsDelayOverwrite = getAppleIIgsDelayOverwriteGameEntry(getGameID());
 	}
 
 	do {
@@ -390,63 +383,15 @@ void AgiEngine::playGame() {
 
 		inGameTimerUpdate();
 
-		uint8 timeDelay = getVar(VM_VAR_TIME_DELAY);
-
+		byte timeDelay;
 		if (getPlatform() == Common::kPlatformApple2GS) {
-			timeDelay++;
-			// It seems that either Apple IIgs ran very slowly or that the delay in its interpreter was not working as everywhere else
-			// Most games on that platform set the delay to 0, which means no delay in DOS
-			// Gold Rush! even "optimizes" itself when larger sprites are on the screen it sets TIME_DELAY to 0.
-			// Normally that game runs at TIME_DELAY 1.
-			// Maybe a script patch for this game would make sense.
-			// TODO: needs further investigation
-
-			int16 timeDelayOverwrite = -99;
-
-			// Now check, if we got a time delay overwrite entry for current room
-			if (appleIIgsDelayOverwrite->roomTable) {
-				byte curRoom = getVar(VM_VAR_CURRENT_ROOM);
-				int16 curPictureNr = _picture->getResourceNr();
-
-				appleIIgsDelayRoomOverwrite = appleIIgsDelayOverwrite->roomTable;
-				while (appleIIgsDelayRoomOverwrite->fromRoom >= 0) {
-					if ((appleIIgsDelayRoomOverwrite->fromRoom <= curRoom) && (appleIIgsDelayRoomOverwrite->toRoom >= curRoom)) {
-						if ((appleIIgsDelayRoomOverwrite->activePictureNr == curPictureNr) || (appleIIgsDelayRoomOverwrite->activePictureNr == -1)) {
-							if (appleIIgsDelayRoomOverwrite->onlyWhenPlayerNotInControl) {
-								if (_game.playerControl) {
-									// Player is actually currently in control? -> then skip this entry
-									appleIIgsDelayRoomOverwrite++;
-									continue;
-								}
-							}
-							timeDelayOverwrite = appleIIgsDelayRoomOverwrite->timeDelayOverwrite;
-							break;
-						}
-					}
-					appleIIgsDelayRoomOverwrite++;
-				}
-			}
-
-			if (timeDelayOverwrite == -99) {
-				// use default time delay in case no room specific one was found ...
-				if (_game.appleIIgsSpeedLevel == 2)
-					// ... and the user set the speed to "Normal" ...
-					timeDelayOverwrite = appleIIgsDelayOverwrite->defaultTimeDelayOverwrite;
-				else
-					// ... otherwise, use the speed the user requested (either from menu, or from text parser)
-					timeDelayOverwrite = _game.appleIIgsSpeedLevel;
-			}
-
-
-			if (timeDelayOverwrite >= 0) {
-				if (timeDelayOverwrite != timeDelay) {
-					// delayOverwrite is not the same as the delay taken from the scripts? overwrite it
-					//warning("AppleIIgs: time delay overwrite from %d to %d", timeDelay, timeDelayOverwrite);
-
-					setVar(VM_VAR_TIME_DELAY, timeDelayOverwrite - 1); // adjust for Apple IIgs
-					timeDelay = timeDelayOverwrite;
-				}
+			byte newTimeDelay = 0xff;
+			timeDelay = getAppleIIgsTimeDelay(appleIIgsDelayOverwrite, newTimeDelay);
+			if (newTimeDelay != 0xff) {
+				setVar(VM_VAR_TIME_DELAY, newTimeDelay);
 			}
+		} else {
+			timeDelay = getVar(VM_VAR_TIME_DELAY);
 		}
 
 		// Increment the delay value by one, so that we wait for at least 1 cycle
@@ -585,4 +530,72 @@ int AgiEngine::runGame() {
 	return ec;
 }
 
+/**
+ * Returns the time delay to use for an Apple IIgs interpreter cycle.
+ * Optionally returns a new value for the time delay variable (variable 10).
+ */
+byte AgiEngine::getAppleIIgsTimeDelay(
+	const AgiAppleIIgsDelayOverwriteGameEntry *appleIIgsDelayOverwrite,
+	byte &newTimeDelay) const {
+
+	byte timeDelay = _game.vars[VM_VAR_TIME_DELAY];
+	timeDelay++;
+	// It seems that either Apple IIgs ran very slowly or that the delay in its interpreter was not working as everywhere else
+	// Most games on that platform set the delay to 0, which means no delay in DOS
+	// Gold Rush! even "optimizes" itself when larger sprites are on the screen it sets TIME_DELAY to 0.
+	// Normally that game runs at TIME_DELAY 1.
+	// Maybe a script patch for this game would make sense.
+	// TODO: needs further investigation
+
+	int16 timeDelayOverwrite = -99;
+
+	// Now check, if we got a time delay overwrite entry for current room
+	if (appleIIgsDelayOverwrite->roomTable) {
+		byte curRoom = _game.vars[VM_VAR_CURRENT_ROOM];
+		int16 curPictureNr = _picture->getResourceNr();
+
+		const AgiAppleIIgsDelayOverwriteRoomEntry *appleIIgsDelayRoomOverwrite = nullptr;
+		appleIIgsDelayRoomOverwrite = appleIIgsDelayOverwrite->roomTable;
+		while (appleIIgsDelayRoomOverwrite->fromRoom >= 0) {
+			if ((appleIIgsDelayRoomOverwrite->fromRoom <= curRoom) && (appleIIgsDelayRoomOverwrite->toRoom >= curRoom)) {
+				if ((appleIIgsDelayRoomOverwrite->activePictureNr == curPictureNr) || (appleIIgsDelayRoomOverwrite->activePictureNr == -1)) {
+					if (appleIIgsDelayRoomOverwrite->onlyWhenPlayerNotInControl) {
+						if (_game.playerControl) {
+							// Player is actually currently in control? -> then skip this entry
+							appleIIgsDelayRoomOverwrite++;
+							continue;
+						}
+					}
+					timeDelayOverwrite = appleIIgsDelayRoomOverwrite->timeDelayOverwrite;
+					break;
+				}
+			}
+			appleIIgsDelayRoomOverwrite++;
+		}
+	}
+
+	if (timeDelayOverwrite == -99) {
+		// use default time delay in case no room specific one was found ...
+		if (_game.appleIIgsSpeedLevel == 2)
+			// ... and the user set the speed to "Normal" ...
+			timeDelayOverwrite = appleIIgsDelayOverwrite->defaultTimeDelayOverwrite;
+		else
+			// ... otherwise, use the speed the user requested (either from menu, or from text parser)
+			timeDelayOverwrite = _game.appleIIgsSpeedLevel;
+	}
+
+
+	if (timeDelayOverwrite >= 0) {
+		if (timeDelayOverwrite != timeDelay) {
+			// delayOverwrite is not the same as the delay taken from the scripts? overwrite it
+			//warning("AppleIIgs: time delay overwrite from %d to %d", timeDelay, timeDelayOverwrite);
+
+			newTimeDelay = timeDelayOverwrite - 1; // adjust for Apple IIgs
+			timeDelay = timeDelayOverwrite;
+		}
+	}
+
+	return timeDelay;
+}
+
 } // End of namespace Agi


Commit: 22a4111c57ce41d65dcb718e48df8398fed51aae
    https://github.com/scummvm/scummvm/commit/22a4111c57ce41d65dcb718e48df8398fed51aae
Author: sluicebox (22204938+sluicebox at users.noreply.github.com)
Date: 2024-09-16T20:27:39-07:00

Commit Message:
AGI: Add Apple II speed controls, implement set.speed

Changed paths:
    engines/agi/agi.cpp
    engines/agi/agi.h
    engines/agi/cycle.cpp
    engines/agi/op_cmd.cpp
    engines/agi/words.cpp


diff --git a/engines/agi/agi.cpp b/engines/agi/agi.cpp
index 19b442abfef..7e076bfecab 100644
--- a/engines/agi/agi.cpp
+++ b/engines/agi/agi.cpp
@@ -782,10 +782,10 @@ void AgiGame::setString(int number, const char *str) {
 	}
 }
 
-void AgiGame::setAppleIIgsSpeedLevel(int i) {
-	appleIIgsSpeedLevel = i;
+void AgiGame::setSpeedLevel(byte s) {
+	speedLevel = s;
 	_vm->setVar(VM_VAR_WINDOW_AUTO_CLOSE_TIMER, 6);
-	switch (i) {
+	switch (speedLevel) {
 	case 0:
 		_vm->_text->messageBox("Fastest speed.");
 		break;
@@ -796,6 +796,7 @@ void AgiGame::setAppleIIgsSpeedLevel(int i) {
 		_vm->_text->messageBox("Normal speed.");
 		break;
 	case 3:
+	case 4:
 		_vm->_text->messageBox("Slow speed.");
 		break;
 	}
diff --git a/engines/agi/agi.h b/engines/agi/agi.h
index 544cd826594..9b8ca2d8fab 100644
--- a/engines/agi/agi.h
+++ b/engines/agi/agi.h
@@ -452,12 +452,16 @@ struct AgiGame {
 
 	bool automaticRestoreGame;
 
+	byte speedLevel; /**< Current game speed for certain platforms/versions */
+
 	uint16 appleIIgsSpeedControllerSlot;
-	int appleIIgsSpeedLevel;
 
 	const char *getString(int number);
 	void setString(int number, const char *str);
-	void setAppleIIgsSpeedLevel(int appleIIgsSpeedLevel);
+	/**
+	 * Sets the speed level and displays a message box.
+	 */
+	void setSpeedLevel(byte s);
 
 	AgiGame() {
 		_vm = nullptr;
@@ -528,8 +532,9 @@ struct AgiGame {
 
 		automaticRestoreGame = false;
 
+		speedLevel = 2; // normal speed
+
 		appleIIgsSpeedControllerSlot = 0xffff;	// we didn't add yet speed menu
-		appleIIgsSpeedLevel = 2;  // normal speed
 	}
 };
 
diff --git a/engines/agi/cycle.cpp b/engines/agi/cycle.cpp
index 6c52b9ac2df..b56d5698c59 100644
--- a/engines/agi/cycle.cpp
+++ b/engines/agi/cycle.cpp
@@ -322,7 +322,7 @@ uint16 AgiEngine::processAGIEvents() {
 		for (int i = 0; i < 4; i++)
 			if (_game.controllerOccurred[_game.appleIIgsSpeedControllerSlot + i]) {
 				_game.controllerOccurred[_game.appleIIgsSpeedControllerSlot + i] = false;
-				_game.setAppleIIgsSpeedLevel(i);
+				_game.setSpeedLevel(i);
 			}
 
 	_gfx->updateScreen();
@@ -384,13 +384,24 @@ void AgiEngine::playGame() {
 		inGameTimerUpdate();
 
 		byte timeDelay;
-		if (getPlatform() == Common::kPlatformApple2GS) {
+		if (getPlatform() == Common::kPlatformApple2) {
+			// Apple II games did not have speed control. The interpreter ran as
+			// fast as it could, but it was still quite slow. Game scripts still
+			// set variable 10, but they do so inconsistently because they were
+			// ported from other platforms and it had no effect.
+			// We add speed control in `Words::handleSpeedCommands`.
+			timeDelay = _game.speedLevel;
+		} else if (getVersion() < 0x2000) {
+			// AGIv1 uses an internal speed level, set by the `set.speed` opcode
+			timeDelay = _game.speedLevel;
+		} else if (getPlatform() == Common::kPlatformApple2GS) {
 			byte newTimeDelay = 0xff;
 			timeDelay = getAppleIIgsTimeDelay(appleIIgsDelayOverwrite, newTimeDelay);
 			if (newTimeDelay != 0xff) {
 				setVar(VM_VAR_TIME_DELAY, newTimeDelay);
 			}
 		} else {
+			// AGIv2 and AGIv3 use the time delay variable set by game scripts
 			timeDelay = getVar(VM_VAR_TIME_DELAY);
 		}
 
@@ -576,12 +587,12 @@ byte AgiEngine::getAppleIIgsTimeDelay(
 
 	if (timeDelayOverwrite == -99) {
 		// use default time delay in case no room specific one was found ...
-		if (_game.appleIIgsSpeedLevel == 2)
+		if (_game.speedLevel == 2)
 			// ... and the user set the speed to "Normal" ...
 			timeDelayOverwrite = appleIIgsDelayOverwrite->defaultTimeDelayOverwrite;
 		else
 			// ... otherwise, use the speed the user requested (either from menu, or from text parser)
-			timeDelayOverwrite = _game.appleIIgsSpeedLevel;
+			timeDelayOverwrite = _game.speedLevel;
 	}
 
 
diff --git a/engines/agi/op_cmd.cpp b/engines/agi/op_cmd.cpp
index 22ec453e000..eb6efdb42fe 100644
--- a/engines/agi/op_cmd.cpp
+++ b/engines/agi/op_cmd.cpp
@@ -2297,10 +2297,10 @@ void cmdShakeScreen(AgiGame *state, AgiEngine *vm, uint8 *parameter) {
 }
 
 void cmdSetSpeed(AgiGame *state, AgiEngine *vm, uint8 *parameter) {
-	// V1 command
-	(void)state;
-	(void)parameter;
-	// speed = _v[p0];
+	byte varNr = parameter[0];
+	byte speed = vm->getVar(varNr);
+
+	vm->_game.speedLevel = speed;
 }
 
 void cmdSetItemView(AgiGame *state, AgiEngine *vm, uint8 *parameter) {
diff --git a/engines/agi/words.cpp b/engines/agi/words.cpp
index 30590c30885..df07b3cf6cb 100644
--- a/engines/agi/words.cpp
+++ b/engines/agi/words.cpp
@@ -404,24 +404,24 @@ uint16 Words::getEgoWordId(int16 wordNr) const {
 }
 
 bool Words::handleSpeedCommands(const Common::String &userInputLowercase) {
-	// WORKAROUND: For Apple II support speed changes
-	// some of the games hadn't this feature
-	// some (like PQ1) had it, but we override the speed that the game request
-	// with `timeDelayOverwrite`
-	// this mechanism works for all the games, and therefore, doesn't bother to search in the dictionary
+	// We add speed controls to games that didn't originally have them.
+	// Apple II games had no speed controls, the interpreter ran as fast as it could.
+	// Some Apple IIgs games had speed controls, others didn't. We override the
+	// the speed that the game requests with `timeDelayOverwrite`.
 	switch (_vm->getPlatform()) {
+	case Common::kPlatformApple2:
 	case Common::kPlatformApple2GS:
 		if (userInputLowercase == "fastest") {
-			_vm->_game.setAppleIIgsSpeedLevel(0);
+			_vm->_game.setSpeedLevel(0);
 			return true;
 		} else if (userInputLowercase == "fast") {
-			_vm->_game.setAppleIIgsSpeedLevel(1);
+			_vm->_game.setSpeedLevel(1);
 			return true;
 		} else if (userInputLowercase == "normal") {
-			_vm->_game.setAppleIIgsSpeedLevel(2);
+			_vm->_game.setSpeedLevel(2);
 			return true;
 		} else if (userInputLowercase == "slow") {
-			_vm->_game.setAppleIIgsSpeedLevel(3);
+			_vm->_game.setSpeedLevel(3);
 			return true;
 		}
 		break;


Commit: fdaf660bbc323074357bca73d54554a62415fe08
    https://github.com/scummvm/scummvm/commit/fdaf660bbc323074357bca73d54554a62415fe08
Author: sluicebox (22204938+sluicebox at users.noreply.github.com)
Date: 2024-09-16T20:28:38-07:00

Commit Message:
AGI: Update Gold Rush clock workaround for all versions

- Fixes game clock speed in Apple II, Apple IIgs
- Fixes save games from before the original fix

See: 77deb41c252305c6f95d4b929aa48deacf63890b

Changed paths:
    engines/agi/agi.h
    engines/agi/cycle.cpp
    engines/agi/global.cpp


diff --git a/engines/agi/agi.h b/engines/agi/agi.h
index 9b8ca2d8fab..e342978c9c8 100644
--- a/engines/agi/agi.h
+++ b/engines/agi/agi.h
@@ -1088,6 +1088,10 @@ private:
 
 public:
 	const AgiOpCodeEntry *getOpCodesTable() { return _opCodes; }
+
+private:
+	void goldRushClockTimeWorkaround_OnReadVar();
+	void goldRushClockTimeWorkaround_OnWriteVar(byte oldValue);
 };
 
 } // End of namespace Agi
diff --git a/engines/agi/cycle.cpp b/engines/agi/cycle.cpp
index b56d5698c59..d2d1beeef5a 100644
--- a/engines/agi/cycle.cpp
+++ b/engines/agi/cycle.cpp
@@ -136,23 +136,6 @@ void AgiEngine::newRoom(int16 newRoomNr) {
 		if (getGameID() == GID_LSL1) {
 			setFlag(36, 0); // clear "ignore special" flag on every room change
 		}
-		// WORKAROUND: Gold Rush runs a speed test to calculate how fast the in-game
-		// clock should advance at Fast and Fastest settings, based on CPU speed.
-		// The goal was to produce a real-time clock, even though it's really driven
-		// by game cycles. This test is incompatible with our speed throttling because
-		// it runs in Fastest mode and the results are based on running unthrottled.
-		// This causes in an artificially poor test result, resulting in the clock
-		// running much too fast at Fast and Fastest speeds. We fix this by overriding
-		// the test result with speeds that match ours. Fixes bugs #4147, #13910
-		if (getGameID() == GID_GOLDRUSH && newRoomNr == 1) {
-			setVar(165, 20); // Fast:	 20 game cycles => 1 Gold Rush second
-			setVar(167, 40); // Fastest: 40 game cycles => 1 Gold Rush second
-			// Gold Rush 3.0 (1998) disables Fastest if the speed test indicates
-			// a fast machine. That shouldn't happen, but make sure it doesn't.
-			if (getPlatform() == Common::kPlatformDOS) {
-				setFlag(172, 0); // Allow Fastest
-			}
-		}
 	}
 }
 
diff --git a/engines/agi/global.cpp b/engines/agi/global.cpp
index 7d4b9d147fd..b7de29c0b40 100644
--- a/engines/agi/global.cpp
+++ b/engines/agi/global.cpp
@@ -27,6 +27,9 @@
 
 namespace Agi {
 
+#define VM_VAR_GOLDRUSH_CYCLE_COUNT       156
+#define VM_VAR_GOLDRUSH_CYCLES_PER_SECOND 166
+
 bool AgiBase::getFlag(int16 flagNr) {
 	uint8 *flagPtr = _game.flags;
 
@@ -60,6 +63,7 @@ void AgiBase::setFlagOrVar(int16 flagNr, bool newState) {
 }
 
 void AgiEngine::setVar(int16 varNr, byte newValue) {
+	byte oldValue = _game.vars[varNr];
 	_game.vars[varNr] = newValue;
 
 	switch (varNr) {
@@ -69,6 +73,11 @@ void AgiEngine::setVar(int16 varNr, byte newValue) {
 	case VM_VAR_VOLUME:
 		applyVolumeToMixer();
 		break;
+	case VM_VAR_GOLDRUSH_CYCLES_PER_SECOND:
+		if (getGameID() == GID_GOLDRUSH) {
+			goldRushClockTimeWorkaround_OnWriteVar(oldValue);
+		}
+		break;
 	default:
 		break;
 	}
@@ -86,6 +95,11 @@ byte AgiEngine::getVar(int16 varNr) {
 		// Sierra AGI updated the timer via a timer procedure
 		inGameTimerUpdate();
 		break;
+	case VM_VAR_GOLDRUSH_CYCLES_PER_SECOND:
+		if (getGameID() == GID_GOLDRUSH) {
+			goldRushClockTimeWorkaround_OnReadVar();
+		}
+		break;
 	default:
 		break;
 	}
@@ -301,4 +315,54 @@ void AgiEngine::decrypt(uint8 *mem, int len) {
 		*(mem + i) ^= *(key + (i % 11));
 }
 
+// WORKAROUND: Gold Rush runs a speed test to calculate how fast the in-game
+// clock should advance at Fast and Fastest settings, based on CPU speed.
+// The goal was to produce a real-time clock, even though it's really driven
+// by game cycles. This test is incompatible with our speed throttling because
+// it runs in Fastest mode and the results are based on running unthrottled.
+// This causes an artificially poor test result, resulting in the clock running
+// much too fast at Fast and Fastest speeds. On Apple II and Apple IIgs, there
+// was no speed setting. GR ran unthrottled and the clock script was hard-coded
+// with values based on the expected CPU speed. We add speed settings to these
+// versions, so we need to replace these values and sync them with game speed.
+// We fix all of this by overriding the cycles-per-clock-second variable with
+// values that match the actual game speed. Fixes bugs #4147, #13910
+void AgiEngine::goldRushClockTimeWorkaround_OnReadVar() {
+	const byte grCyclesPerSecond[4] = {
+		40, // Fastest: 40 game cycles per clock second
+		20, // Fast:    20 game cycles per clock second
+		10, // Normal:  10 game cycles per clock second
+		6   // Slow:     6 game cycles per clock second
+	};
+	
+	// Determine the correct number of game cycles per clock second
+	byte cyclesPerSecond;
+	switch (getPlatform()) {
+	case Common::kPlatformApple2:
+	case Common::kPlatformApple2GS:
+		cyclesPerSecond = grCyclesPerSecond[MIN<byte>(_game.speedLevel, 3)];
+		break;
+	case Common::kPlatformDOS:
+		setFlag(172, 0); // allow Fastest speed in version 3.0
+		// fall through
+	default:
+		cyclesPerSecond = grCyclesPerSecond[MIN<byte>(getVar(VM_VAR_TIME_DELAY), 3)];
+		break;
+	}
+
+	// When the changing speed, reset the cycle counter. The script
+	// that resets the counter was removed from A2/A2GS versions.
+	if (cyclesPerSecond != _game.vars[VM_VAR_GOLDRUSH_CYCLES_PER_SECOND]) {
+		_game.vars[VM_VAR_GOLDRUSH_CYCLES_PER_SECOND] = cyclesPerSecond;
+		_game.vars[VM_VAR_GOLDRUSH_CYCLE_COUNT] = 0;
+	}
+}
+
+void AgiEngine::goldRushClockTimeWorkaround_OnWriteVar(byte oldValue) {
+	// Ignore attempts from game scripts to set the cycles per second.
+	// Apple II sets it to 10 on every cycle, and that would confuse
+	// the change detection in goldRushClockTimeWorkaround_OnReadVar.
+	_game.vars[VM_VAR_GOLDRUSH_CYCLES_PER_SECOND] = oldValue;
+}
+
 } // End of namespace Agi




More information about the Scummvm-git-logs mailing list