[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