diff options
Diffstat (limited to 'engines/sci/engine')
-rw-r--r-- | engines/sci/engine/features.cpp | 8 | ||||
-rw-r--r-- | engines/sci/engine/kernel.h | 10 | ||||
-rw-r--r-- | engines/sci/engine/kernel_tables.h | 7 | ||||
-rw-r--r-- | engines/sci/engine/kfile.cpp | 34 | ||||
-rw-r--r-- | engines/sci/engine/kgraphics.cpp | 13 | ||||
-rw-r--r-- | engines/sci/engine/kgraphics32.cpp | 55 | ||||
-rw-r--r-- | engines/sci/engine/kmath.cpp | 82 | ||||
-rw-r--r-- | engines/sci/engine/kpathing.cpp | 13 | ||||
-rw-r--r-- | engines/sci/engine/ksound.cpp | 1 | ||||
-rw-r--r-- | engines/sci/engine/kstring.cpp | 77 | ||||
-rw-r--r-- | engines/sci/engine/message.cpp | 39 | ||||
-rw-r--r-- | engines/sci/engine/savegame.cpp | 3 | ||||
-rw-r--r-- | engines/sci/engine/script_patches.cpp | 63 | ||||
-rw-r--r-- | engines/sci/engine/scriptdebug.cpp | 2 | ||||
-rw-r--r-- | engines/sci/engine/workarounds.cpp | 12 |
15 files changed, 346 insertions, 73 deletions
diff --git a/engines/sci/engine/features.cpp b/engines/sci/engine/features.cpp index 22c0a1479d..49e2bfc79f 100644 --- a/engines/sci/engine/features.cpp +++ b/engines/sci/engine/features.cpp @@ -466,6 +466,14 @@ bool GameFeatures::autoDetectSci21KernelType() { // This case doesn't occur in early SCI2.1 games, and we've only // seen it happen in the RAMA demo, thus we can assume that the // game is using a SCI2.1 table + + // HACK: The Inside the Chest Demo doesn't have sounds at all, but + // it's using a SCI2 kernel + if (g_sci->getGameId() == GID_CHEST) { + _sci21KernelType = SCI_VERSION_2; + return true; + } + warning("autoDetectSci21KernelType(): Sound object not loaded, assuming a SCI2.1 table"); _sci21KernelType = SCI_VERSION_2_1; return true; diff --git a/engines/sci/engine/kernel.h b/engines/sci/engine/kernel.h index f985a69ebc..e3ebce80fb 100644 --- a/engines/sci/engine/kernel.h +++ b/engines/sci/engine/kernel.h @@ -427,18 +427,23 @@ reg_t kUpdatePlane(EngineState *s, int argc, reg_t *argv); reg_t kSetShowStyle(EngineState *s, int argc, reg_t *argv); reg_t kGetHighPlanePri(EngineState *s, int argc, reg_t *argv); reg_t kFrameOut(EngineState *s, int argc, reg_t *argv); + reg_t kIsOnMe(EngineState *s, int argc, reg_t *argv); // kOnMe for SCI2, kIsOnMe for SCI2.1 +reg_t kInPolygon(EngineState *s, int argc, reg_t *argv); +reg_t kObjectIntersect(EngineState *s, int argc, reg_t *argv); + reg_t kListIndexOf(EngineState *s, int argc, reg_t *argv); reg_t kListEachElementDo(EngineState *s, int argc, reg_t *argv); reg_t kListFirstTrue(EngineState *s, int argc, reg_t *argv); reg_t kListAllTrue(EngineState *s, int argc, reg_t *argv); -reg_t kInPolygon(EngineState *s, int argc, reg_t *argv); -reg_t kObjectIntersect(EngineState *s, int argc, reg_t *argv); + reg_t kEditText(EngineState *s, int argc, reg_t *argv); reg_t kMakeSaveCatName(EngineState *s, int argc, reg_t *argv); reg_t kMakeSaveFileName(EngineState *s, int argc, reg_t *argv); reg_t kSetScroll(EngineState *s, int argc, reg_t *argv); reg_t kPalCycle(EngineState *s, int argc, reg_t *argv); +reg_t kPalVaryUnknown(EngineState *s, int argc, reg_t *argv); +reg_t kPalVaryUnknown2(EngineState *s, int argc, reg_t *argv); // SCI2.1 Kernel Functions reg_t kText(EngineState *s, int argc, reg_t *argv); @@ -513,7 +518,6 @@ reg_t kPalVaryDeinit(EngineState *s, int argc, reg_t *argv); reg_t kPalVaryChangeTarget(EngineState *s, int argc, reg_t *argv); reg_t kPalVaryChangeTicks(EngineState *s, int argc, reg_t *argv); reg_t kPalVaryPauseResume(EngineState *s, int argc, reg_t *argv); -reg_t kPalVaryUnknown(EngineState *s, int argc, reg_t *argv); reg_t kPaletteSetFromResource(EngineState *s, int argc, reg_t *argv); reg_t kPaletteSetFlag(EngineState *s, int argc, reg_t *argv); diff --git a/engines/sci/engine/kernel_tables.h b/engines/sci/engine/kernel_tables.h index b6b36c47e7..d7858180f1 100644 --- a/engines/sci/engine/kernel_tables.h +++ b/engines/sci/engine/kernel_tables.h @@ -94,7 +94,7 @@ static const SciKernelMapSubEntry kDoSound_subops[] = { { SIG_SOUNDSCI0, 7, MAP_CALL(DoSoundResumeAfterRestore), "", NULL }, { SIG_SOUNDSCI0, 8, MAP_CALL(DoSoundMasterVolume), "(i)", NULL }, { SIG_SOUNDSCI0, 9, MAP_CALL(DoSoundUpdate), "o", NULL }, - { SIG_SOUNDSCI0, 10, MAP_CALL(DoSoundFade), "o", kDoSoundFade_workarounds }, + { SIG_SOUNDSCI0, 10, MAP_CALL(DoSoundFade), "[o0]", kDoSoundFade_workarounds }, { SIG_SOUNDSCI0, 11, MAP_CALL(DoSoundGetPolyphony), "", NULL }, { SIG_SOUNDSCI0, 12, MAP_CALL(DoSoundStopAll), "", NULL }, { SIG_SOUNDSCI1EARLY, 0, MAP_CALL(DoSoundMasterVolume), NULL, NULL }, @@ -156,7 +156,7 @@ static const SciKernelMapSubEntry kDoSound_subops[] = { // signature for SCI21 should be "o" { SIG_SOUNDSCI21, 9, MAP_CALL(DoSoundStop), NULL, NULL }, { SIG_SOUNDSCI21, 10, MAP_CALL(DoSoundPause), NULL, NULL }, - { SIG_SOUNDSCI21, 11, MAP_CALL(DoSoundFade), NULL, NULL }, + { SIG_SOUNDSCI21, 11, MAP_CALL(DoSoundFade), NULL, kDoSoundFade_workarounds }, { SIG_SOUNDSCI21, 12, MAP_CALL(DoSoundSetHold), NULL, NULL }, { SIG_SOUNDSCI21, 13, MAP_CALL(DoSoundDummy), NULL, NULL }, { SIG_SOUNDSCI21, 14, MAP_CALL(DoSoundSetVolume), NULL, NULL }, @@ -202,7 +202,10 @@ static const SciKernelMapSubEntry kPalVary_subops[] = { { SIG_SCIALL, 4, MAP_CALL(PalVaryChangeTarget), "i", NULL }, { SIG_SCIALL, 5, MAP_CALL(PalVaryChangeTicks), "i", NULL }, { SIG_SCIALL, 6, MAP_CALL(PalVaryPauseResume), "i", NULL }, +#ifdef ENABLE_SCI32 { SIG_SCI32, 8, MAP_CALL(PalVaryUnknown), "i", NULL }, + { SIG_SCI32, 9, MAP_CALL(PalVaryUnknown2), "i", NULL }, +#endif SCI_SUBOPENTRY_TERMINATOR }; diff --git a/engines/sci/engine/kfile.cpp b/engines/sci/engine/kfile.cpp index e977f15c0c..09ea35e792 100644 --- a/engines/sci/engine/kfile.cpp +++ b/engines/sci/engine/kfile.cpp @@ -770,7 +770,10 @@ reg_t kSaveGame(EngineState *s, int argc, reg_t *argv) { return NULL_REG; } else if (virtualId < SAVEGAMEID_OFFICIALRANGE_START) { // virtualId is low, we assume that scripts expect us to create new slot - if (virtualId == s->_lastSaveVirtualId) { + if (g_sci->getGameId() == GID_JONES) { + // Jones has one save slot only + savegameId = 0; + } else if (virtualId == s->_lastSaveVirtualId) { // if last virtual id is the same as this one, we assume that caller wants to overwrite last save savegameId = s->_lastSaveNewId; } else { @@ -848,12 +851,17 @@ reg_t kRestoreGame(EngineState *s, int argc, reg_t *argv) { } else { if (argv[2].isNull()) error("kRestoreGame: called with parameter 2 being NULL"); - // Real call from script, we need to adjust ID - if ((savegameId < SAVEGAMEID_OFFICIALRANGE_START) || (savegameId > SAVEGAMEID_OFFICIALRANGE_END)) { - warning("Savegame ID %d is not allowed", savegameId); - return TRUE_REG; + if (g_sci->getGameId() == GID_JONES) { + // Jones has one save slot only + savegameId = 0; + } else { + // Real call from script, we need to adjust ID + if ((savegameId < SAVEGAMEID_OFFICIALRANGE_START) || (savegameId > SAVEGAMEID_OFFICIALRANGE_END)) { + warning("Savegame ID %d is not allowed", savegameId); + return TRUE_REG; + } + savegameId -= SAVEGAMEID_OFFICIALRANGE_START; } - savegameId -= SAVEGAMEID_OFFICIALRANGE_START; } s->r_acc = NULL_REG; // signals success @@ -922,10 +930,16 @@ reg_t kCheckSaveGame(EngineState *s, int argc, reg_t *argv) { if (virtualId == 0) return NULL_REG; - // Find saved-game - if ((virtualId < SAVEGAMEID_OFFICIALRANGE_START) || (virtualId > SAVEGAMEID_OFFICIALRANGE_END)) - error("kCheckSaveGame: called with invalid savegameId"); - uint savegameId = virtualId - SAVEGAMEID_OFFICIALRANGE_START; + uint savegameId = 0; + if (g_sci->getGameId() == GID_JONES) { + // Jones has one save slot only + } else { + // Find saved game + if ((virtualId < SAVEGAMEID_OFFICIALRANGE_START) || (virtualId > SAVEGAMEID_OFFICIALRANGE_END)) + error("kCheckSaveGame: called with invalid savegame ID (%d)", virtualId); + savegameId = virtualId - SAVEGAMEID_OFFICIALRANGE_START; + } + int savegameNr = findSavegame(saves, savegameId); if (savegameNr == -1) return NULL_REG; diff --git a/engines/sci/engine/kgraphics.cpp b/engines/sci/engine/kgraphics.cpp index da377319c0..e4b3028bcd 100644 --- a/engines/sci/engine/kgraphics.cpp +++ b/engines/sci/engine/kgraphics.cpp @@ -185,10 +185,12 @@ static reg_t kSetCursorSci11(EngineState *s, int argc, reg_t *argv) { hotspot = new Common::Point(argv[3].toSint16(), argv[4].toSint16()); // Fallthrough case 3: - if (g_sci->getPlatform() == Common::kPlatformMacintosh) - g_sci->_gfxCursor->kernelSetMacCursor(argv[0].toUint16(), argv[1].toUint16(), argv[2].toUint16(), hotspot); - else + if (g_sci->getPlatform() == Common::kPlatformMacintosh) { + delete hotspot; // Mac cursors have their own hotspot, so ignore any we get here + g_sci->_gfxCursor->kernelSetMacCursor(argv[0].toUint16(), argv[1].toUint16(), argv[2].toUint16()); + } else { g_sci->_gfxCursor->kernelSetView(argv[0].toUint16(), argv[1].toUint16(), argv[2].toUint16(), hotspot); + } break; case 10: // Freddy pharkas, when using the whiskey glass to read the prescription (bug #3034973) @@ -722,11 +724,6 @@ reg_t kPalVaryPauseResume(EngineState *s, int argc, reg_t *argv) { return NULL_REG; } -reg_t kPalVaryUnknown(EngineState *s, int argc, reg_t *argv) { - // Unknown (seems to be SCI32 exclusive) - return NULL_REG; -} - reg_t kAssertPalette(EngineState *s, int argc, reg_t *argv) { GuiResourceId paletteId = argv[0].toUint16(); diff --git a/engines/sci/engine/kgraphics32.cpp b/engines/sci/engine/kgraphics32.cpp index 8b3afeef99..cd735d1233 100644 --- a/engines/sci/engine/kgraphics32.cpp +++ b/engines/sci/engine/kgraphics32.cpp @@ -323,19 +323,29 @@ reg_t kScrollWindow(EngineState *s, int argc, reg_t *argv) { uint16 op = argv[0].toUint16(); switch (op) { case 0: // Init + // TODO: Init reads the nsLeft, nsTop, nsRight, nsBottom, + // borderColor, fore, back, mode, font, plane selectors + // from the window in argv[1]. g_sci->_gfxFrameout->initScrollText(argv[2].toUint16()); // maxItems g_sci->_gfxFrameout->clearScrollTexts(); return argv[1]; // kWindow case 1: // Show message, called by ScrollableWindow::addString case 14: // Modify message, called by ScrollableWindow::modifyString - // 5 or 6 parameters - // Seems to be called with 5 parameters when the narrator speaks, and - // with 6 when Roger speaks + // TODO: The parameters in Modify are shifted by one: the first + // argument is the handle of the text to modify. The others + // are as Add. { Common::String text = s->_segMan->getString(argv[2]); - uint16 x = 0;//argv[3].toUint16(); // TODO: can't be x (values are all wrong) - uint16 y = 0;//argv[4].toUint16(); // TODO: can't be y (values are all wrong) - // TODO: argv[5] is an optional unknown parameter (an integer set to 0) + uint16 x = 0; + uint16 y = 0; + // TODO: argv[3] is font + // TODO: argv[4] is color + // TODO: argv[5] is alignment (0 = left, 1 = center, 2 = right) + // font,color,alignment may also be -1. (Maybe same as previous?) + // TODO: argv[6] is an optional bool, defaulting to true if not present. + // If true, the old contents are scrolled out of view. + // TODO: Return a handle of the inserted text. (Used for modify/insert) + // This handle looks like it should also be usable by kString. g_sci->_gfxFrameout->addScrollTextEntry(text, kWindow, x, y, (op == 14)); } break; @@ -363,22 +373,27 @@ reg_t kScrollWindow(EngineState *s, int argc, reg_t *argv) { g_sci->_gfxFrameout->lastScrollText(); break; case 9: // Resize, called by ScrollableWindow::resize and ScrollerWindow::resize - // TODO + // TODO: This reads the nsLeft, nsTop, nsRight, nsBottom + // selectors from the SCI object passed in argv[2]. kStub(s, argc, argv); break; case 10: // Where, called by ScrollableWindow::where - // TODO - // argv[2] is an unknown integer + // TODO: + // Gives the current relative scroll location as a fraction + // with argv[2] as the denominator. (Return value is the numerator.) // Silenced the warnings because of the high amount of console spam //kStub(s, argc, argv); break; case 11: // Go, called by ScrollableWindow::scrollTo - // 2 extra parameters here - // TODO + // TODO: + // Two arguments provide a fraction: argv[2] is num., argv[3] is denom. + // Scrolls to the relative location given by the fraction. kStub(s, argc, argv); break; case 12: // Insert, called by ScrollableWindow::insertString - // 3 extra parameters here + // 5 extra parameters here: + // handle of insert location (new string takes that position). + // text, font, color, alignment // TODO kStub(s, argc, argv); break; @@ -668,6 +683,22 @@ reg_t kSetScroll(EngineState *s, int argc, reg_t *argv) { return kStub(s, argc, argv); } +reg_t kPalVaryUnknown(EngineState *s, int argc, reg_t *argv) { + // TODO: Unknown (seems to be SCI32 exclusive) + return kStub(s, argc, argv); +} + +reg_t kPalVaryUnknown2(EngineState *s, int argc, reg_t *argv) { + // TODO: Unknown (seems to be SCI32 exclusive) + // It seems to be related to the day/night palette effects in QFG4, and + // accepts a palette resource ID. It is triggered right when the night + // effect is initially applied (when exiting the caves). + // In QFG4, there are two scene palettes: 790 for night, and 791 for day. + // Initially, the game starts at night time, but this is called with the + // ID of the day time palette (i.e. 791). + return kStub(s, argc, argv); +} + reg_t kPalCycle(EngineState *s, int argc, reg_t *argv) { // Examples: GK1 room 480 (Bayou ritual), LSL6 room 100 (title screen) diff --git a/engines/sci/engine/kmath.cpp b/engines/sci/engine/kmath.cpp index 4b8fadbb84..b2aaa01b45 100644 --- a/engines/sci/engine/kmath.cpp +++ b/engines/sci/engine/kmath.cpp @@ -77,18 +77,7 @@ reg_t kSqrt(EngineState *s, int argc, reg_t *argv) { return make_reg(0, (int16) sqrt((float) ABS(argv[0].toSint16()))); } -/** - * Returns the angle (in degrees) between the two points determined by (x1, y1) - * and (x2, y2). The angle ranges from 0 to 359 degrees. - * What this function does is pretty simple but apparently the original is not - * accurate. - */ -uint16 kGetAngleWorker(int16 x1, int16 y1, int16 x2, int16 y2) { - // SCI1 games (QFG2 and newer) use a simple atan implementation. SCI0 games - // use a somewhat less accurate calculation (below). - if (getSciVersion() >= SCI_VERSION_1_EGA_ONLY) - return (int16)(360 - atan2((double)(x1 - x2), (double)(y1 - y2)) * 57.2958) % 360; - +uint16 kGetAngle_SCI0(int16 x1, int16 y1, int16 x2, int16 y2) { int16 xRel = x2 - x1; int16 yRel = y1 - y2; // y-axis is mirrored. int16 angle; @@ -118,6 +107,75 @@ uint16 kGetAngleWorker(int16 x1, int16 y1, int16 x2, int16 y2) { return angle; } +// atan2 for first octant, x >= y >= 0. Returns [0,45] (inclusive) +int kGetAngle_SCI1_atan2_base(int y, int x) { + if (x == 0) + return 0; + + // fixed point tan(a) + int tan_fp = 10000 * y / x; + + if ( tan_fp >= 1000 ) { + // For tan(a) >= 0.1, interpolate between multiples of 5 degrees + + // 10000 * tan([5, 10, 15, 20, 25, 30, 35, 40, 45]) + const int tan_table[] = { 875, 1763, 2679, 3640, 4663, 5774, + 7002, 8391, 10000 }; + + // Look up tan(a) in our table + int i = 1; + while (tan_fp > tan_table[i]) ++i; + + // The angle a is between 5*i and 5*(i+1). We linearly interpolate. + int dist = tan_table[i] - tan_table[i-1]; + int interp = (5 * (tan_fp - tan_table[i-1]) + dist/2) / dist; + return 5*i + interp; + } else { + // for tan(a) < 0.1, tan(a) is approximately linear in a. + // tan'(0) = 1, so in degrees the slope of atan is 180/pi = 57.29... + return (57 * y + x/2) / x; + } +} + +int kGetAngle_SCI1_atan2(int y, int x) { + if (y < 0) { + int a = kGetAngle_SCI1_atan2(-y, -x); + if (a == 180) + return 0; + else + return 180 + a; + } + if (x < 0) + return 90 + kGetAngle_SCI1_atan2(-x, y); + if (y > x) + return 90 - kGetAngle_SCI1_atan2_base(x, y); + else + return kGetAngle_SCI1_atan2_base(y, x); + +} + +uint16 kGetAngle_SCI1(int16 x1, int16 y1, int16 x2, int16 y2) { + // We flip things around to get into the standard atan2 coordinate system + return kGetAngle_SCI1_atan2(x2 - x1, y1 - y2); + +} + +/** + * Returns the angle (in degrees) between the two points determined by (x1, y1) + * and (x2, y2). The angle ranges from 0 to 359 degrees. + * What this function does is pretty simple but apparently the original is not + * accurate. + */ + +uint16 kGetAngleWorker(int16 x1, int16 y1, int16 x2, int16 y2) { + if (getSciVersion() >= SCI_VERSION_1_EGA_ONLY) + return kGetAngle_SCI1(x1, y1, x2, y2); + else + return kGetAngle_SCI0(x1, y1, x2, y2); +} + + + reg_t kGetAngle(EngineState *s, int argc, reg_t *argv) { // Based on behavior observed with a test program created with // SCI Studio. diff --git a/engines/sci/engine/kpathing.cpp b/engines/sci/engine/kpathing.cpp index 4061795f82..64793efa6c 100644 --- a/engines/sci/engine/kpathing.cpp +++ b/engines/sci/engine/kpathing.cpp @@ -1366,7 +1366,16 @@ static void AStar(PathfindingState *s) { // other, while we apply a penalty to paths traversing it. // This difference might lead to problems, but none are // known at the time of writing. - if (s->pointOnScreenBorder(vertex->v)) + + // WORKAROUND: This check fails in QFG1VGA, room 81 (bug report #3568452). + // However, it is needed in other SCI1.1 games, such as LB2. Therefore, we + // add this workaround for that scene in QFG1VGA, until our algorithm matches + // better what SSCI is doing. With this workaround, QFG1VGA no longer freezes + // in that scene. + bool qfg1VgaWorkaround = (g_sci->getGameId() == GID_QFG1VGA && + g_sci->getEngineState()->currentRoomNumber() == 81); + + if (s->pointOnScreenBorder(vertex->v) && !qfg1VgaWorkaround) new_dist += 10000; if (new_dist < vertex->costG) { @@ -2379,6 +2388,8 @@ reg_t kMergePoly(EngineState *s, int argc, reg_t *argv) { debugN("\n"); #endif } + + delete polygon; } node = s->_segMan->lookupNode(node->succ); diff --git a/engines/sci/engine/ksound.cpp b/engines/sci/engine/ksound.cpp index 0633267db4..b803e123f1 100644 --- a/engines/sci/engine/ksound.cpp +++ b/engines/sci/engine/ksound.cpp @@ -193,6 +193,7 @@ reg_t kDoAudio(EngineState *s, int argc, reg_t *argv) { } else #endif mixer->setVolumeForSoundType(Audio::Mixer::kSpeechSoundType, volume * 2); + break; } case kSciAudioLanguage: // In SCI1.1: tests for digital audio support diff --git a/engines/sci/engine/kstring.cpp b/engines/sci/engine/kstring.cpp index c4db0b891c..65e139e1ee 100644 --- a/engines/sci/engine/kstring.cpp +++ b/engines/sci/engine/kstring.cpp @@ -155,30 +155,47 @@ reg_t kReadNumber(EngineState *s, int argc, reg_t *argv) { source++; /* Skip whitespace */ int16 result = 0; + int16 sign = 1; + if (*source == '-') { + sign = -1; + source++; + } if (*source == '$') { // Hexadecimal input - result = (int16)strtol(source + 1, NULL, 16); + source++; + char c; + while ((c = *source++) != 0) { + int16 x = 0; + if ((c >= '0') && (c <= '9')) + x = c - '0'; + else if ((c >= 'a') && (c <= 'f')) + x = c - 'a' + 10; + else if ((c >= 'A') && (c <= 'F')) + x = c - 'A' + 10; + else + // Stop if we encounter anything other than a digit (like atoi) + break; + result *= 16; + result += x; + } } else { // Decimal input. We can not use strtol/atoi in here, because while // Sierra used atoi, it was a non standard compliant atoi, that didn't // do clipping. In SQ4 we get the door code in here and that's even // larger than uint32! - if (*source == '-') { - // FIXME: Setting result to -1 does _not_ negate the output. - result = -1; - source++; - } - while (*source) { - if ((*source < '0') || (*source > '9')) + char c; + while ((c = *source++) != 0) { + if ((c < '0') || (c > '9')) // Stop if we encounter anything other than a digit (like atoi) break; result *= 10; - result += *source - 0x30; - source++; + result += c - '0'; } } + result *= sign; + return make_reg(0, result); } @@ -489,6 +506,7 @@ reg_t kGetMessage(EngineState *s, int argc, reg_t *argv) { reg_t kMessage(EngineState *s, int argc, reg_t *argv) { uint func = argv[0].toUint16(); + uint16 module = (argc >= 2) ? argv[1].toUint16() : 0; #ifdef ENABLE_SCI32 if (getSciVersion() >= SCI_VERSION_2) { @@ -518,19 +536,44 @@ reg_t kMessage(EngineState *s, int argc, reg_t *argv) { if (argc >= 6) tuple = MessageTuple(argv[2].toUint16(), argv[3].toUint16(), argv[4].toUint16(), argv[5].toUint16()); + // WORKAROUND for a script bug in Pepper. When using objects together, + // there is code inside script 894 that shows appropriate messages. + // In the case of the jar of cabbage (noun 26), the relevant message + // shown when using any object with it is missing. This leads to the + // script code being triggered, which modifies the jar's noun and + // message selectors, and renders it useless. Thus, when using any + // object with the jar of cabbage, it's effectively corrupted, and + // can't be used on the goat to empty it, therefore the game reaches + // an unsolvable state. It's almost impossible to patch the offending + // script, as it is used in many cases. But we can prevent the + // corruption of the jar here: if the message is found, the offending + // code is never reached and the jar is never corrupted. To do this, + // we substitute all verbs on the cabbage jar with the default verb, + // which shows the "Cannot use this object with the jar" message, and + // never triggers the offending script code that corrupts the object. + // This only affects the jar of cabbage - any other object, including + // the empty jar has a different noun, thus it's unaffected. + // Fixes bug #3601090. + // NOTE: To fix a corrupted jar object, type "send Glass_Jar message 52" + // in the debugger. + if (g_sci->getGameId() == GID_PEPPER && func == 0 && argc >= 6 && module == 894 && + tuple.noun == 26 && tuple.cond == 0 && tuple.seq == 1 && + !s->_msgState->getMessage(module, tuple, NULL_REG)) + tuple.verb = 0; + switch (func) { case K_MESSAGE_GET: - return make_reg(0, s->_msgState->getMessage(argv[1].toUint16(), tuple, (argc == 7 ? argv[6] : NULL_REG))); + return make_reg(0, s->_msgState->getMessage(module, tuple, (argc == 7 ? argv[6] : NULL_REG))); case K_MESSAGE_NEXT: return make_reg(0, s->_msgState->nextMessage((argc == 2 ? argv[1] : NULL_REG))); case K_MESSAGE_SIZE: - return make_reg(0, s->_msgState->messageSize(argv[1].toUint16(), tuple)); + return make_reg(0, s->_msgState->messageSize(module, tuple)); case K_MESSAGE_REFCOND: case K_MESSAGE_REFVERB: case K_MESSAGE_REFNOUN: { MessageTuple t; - if (s->_msgState->messageRef(argv[1].toUint16(), tuple, t)) { + if (s->_msgState->messageRef(module, tuple, t)) { switch (func) { case K_MESSAGE_REFCOND: return make_reg(0, t.cond); @@ -545,9 +588,9 @@ reg_t kMessage(EngineState *s, int argc, reg_t *argv) { } case K_MESSAGE_LASTMESSAGE: { MessageTuple msg; - int module; + int lastModule; - s->_msgState->lastQuery(module, msg); + s->_msgState->lastQuery(lastModule, msg); bool ok = false; @@ -556,7 +599,7 @@ reg_t kMessage(EngineState *s, int argc, reg_t *argv) { if (buffer) { ok = true; - WRITE_LE_UINT16(buffer, module); + WRITE_LE_UINT16(buffer, lastModule); WRITE_LE_UINT16(buffer + 2, msg.noun); WRITE_LE_UINT16(buffer + 4, msg.verb); WRITE_LE_UINT16(buffer + 6, msg.cond); @@ -567,7 +610,7 @@ reg_t kMessage(EngineState *s, int argc, reg_t *argv) { if (buffer) { ok = true; - buffer[0] = make_reg(0, module); + buffer[0] = make_reg(0, lastModule); buffer[1] = make_reg(0, msg.noun); buffer[2] = make_reg(0, msg.verb); buffer[3] = make_reg(0, msg.cond); diff --git a/engines/sci/engine/message.cpp b/engines/sci/engine/message.cpp index a92d572d35..49be25d7f1 100644 --- a/engines/sci/engine/message.cpp +++ b/engines/sci/engine/message.cpp @@ -203,6 +203,45 @@ bool MessageState::getRecord(CursorStack &stack, bool recurse, MessageRecord &re while (1) { MessageTuple &t = stack.top(); + // Fix known incorrect message tuples + if (g_sci->getGameId() == GID_QFG1VGA && stack.getModule() == 322 && + t.noun == 14 && t.verb == 1 && t.cond == 19 && t.seq == 1) { + // Talking to Kaspar the shopkeeper - bug #3604944 + t.verb = 2; + } + + if (g_sci->getGameId() == GID_PQ1 && stack.getModule() == 38 && + t.noun == 10 && t.verb == 4 && t.cond == 8 && t.seq == 1) { + // Using the hand icon on Keith in the Blue Room - bug #3605654 + t.cond = 9; + } + + if (g_sci->getGameId() == GID_PQ1 && stack.getModule() == 38 && + t.noun == 10 && t.verb == 1 && t.cond == 0 && t.seq == 1) { + // Using the eye icon on Keith in the Blue Room - bug #3605654 + t.cond = 13; + } + + // Fill in known missing message tuples + if (g_sci->getGameId() == GID_SQ4 && stack.getModule() == 16 && + t.noun == 7 && t.verb == 0 && t.cond == 3 && t.seq == 1) { + // This fixes the error message shown when speech and subtitles are + // enabled simultaneously in SQ4 - the (very) long dialog when Roger + // is talking with the aliens is missing - bug #3538416. + record.tuple = t; + record.refTuple = MessageTuple(); + record.talker = 7; // Roger + // The missing text is just too big to fit in one speech bubble, and + // if it's added here manually and drawn on screen, it's painted over + // the entrance in the back where the Sequel Police enters, so it + // looks very ugly. Perhaps this is why this particular text is missing, + // as the text shown in this screen is very short (one-liners). + // Just output an empty string here instead of showing an error. + record.string = ""; + delete reader; + return true; + } + if (!reader->findRecord(t, record)) { // Tuple not found if (recurse && (stack.size() > 1)) { diff --git a/engines/sci/engine/savegame.cpp b/engines/sci/engine/savegame.cpp index ff3f19b53d..b2d95c599e 100644 --- a/engines/sci/engine/savegame.cpp +++ b/engines/sci/engine/savegame.cpp @@ -696,8 +696,9 @@ void GfxPalette::saveLoadWithSerializer(Common::Serializer &s) { s.syncAsSint32LE(_palVaryPaused); } + _palVarySignal = 0; + if (s.isLoading() && _palVaryResourceId != -1) { - _palVarySignal = 0; palVaryInstallTimer(); } } diff --git a/engines/sci/engine/script_patches.cpp b/engines/sci/engine/script_patches.cpp index 8454be514a..c928cf3569 100644 --- a/engines/sci/engine/script_patches.cpp +++ b/engines/sci/engine/script_patches.cpp @@ -848,10 +848,73 @@ const uint16 qfg1vgaPatchFightEvents[] = { PATCH_END }; +// Script 814 of QFG1VGA is responsible for showing dialogs. However, the death +// screen message shown when the hero dies in room 64 (ghost room) is too large +// (254 chars long). Since the window header and main text are both stored in +// temp space, this is an issue, as the scripts read the window header, then the +// window text, which erases the window header text because of its length. To +// fix that, we allocate more temp space and move the pointer used for the +// window header a little bit, wherever it's used in script 814. +// Fixes bug #3568431. + +// Patch 1: Increase temp space +const byte qfg1vgaSignatureTempSpace[] = { + 4, + 0x3f, 0xba, // link 0xba + 0x87, 0x00, // lap 0 + 0 +}; + +const uint16 qfg1vgaPatchTempSpace[] = { + 0x3f, 0xca, // link 0xca + PATCH_END +}; + +// Patch 2: Move the pointer used for the window header a little bit +const byte qfg1vgaSignatureDialogHeader[] = { + 4, + 0x5b, 0x04, 0x80, // lea temp[0x80] + 0x36, // push + 0 +}; + +const uint16 qfg1vgaPatchDialogHeader[] = { + 0x5b, 0x04, 0x90, // lea temp[0x90] + PATCH_END +}; + +// When clicking on the crusher in room 331, Ego approaches him to talk to him, +// an action that is handled by moveToCrusher::changeState in script 331. The +// scripts set Ego to move close to the crusher, but when Ego is running instead +// of walking, the target coordinates specified by script 331 are never reached, +// as Ego is making larger steps, and never reaches the required spot. This is an +// edge case that can occur when Ego is set to run. Normally, when clicking on +// the crusher, ego is supposed to move close to position 79, 165. We change it +// to 85, 165, which is not an edge case thus the freeze is avoided. +// Fixes bug #3585189. +const byte qfg1vgaSignatureMoveToCrusher[] = { + 9, + 0x51, 0x1f, // class Motion + 0x36, // push + 0x39, 0x4f, // pushi 4f (79 - x) + 0x38, 0xa5, 0x00, // pushi 00a5 (165 - y) + 0x7c, // pushSelf + 0 +}; + +const uint16 qfg1vgaPatchMoveToCrusher[] = { + PATCH_ADDTOOFFSET | +3, + 0x39, 0x55, // pushi 55 (85 - x) + PATCH_END +}; + // script, description, magic DWORD, adjust const SciScriptSignature qfg1vgaSignatures[] = { { 215, "fight event issue", 1, PATCH_MAGICDWORD(0x6d, 0x76, 0x51, 0x07), -1, qfg1vgaSignatureFightEvents, qfg1vgaPatchFightEvents }, { 216, "weapon master event issue", 1, PATCH_MAGICDWORD(0x6d, 0x76, 0x51, 0x07), -1, qfg1vgaSignatureFightEvents, qfg1vgaPatchFightEvents }, + { 814, "window text temp space", 1, PATCH_MAGICDWORD(0x3f, 0xba, 0x87, 0x00), 0, qfg1vgaSignatureTempSpace, qfg1vgaPatchTempSpace }, + { 814, "dialog header offset", 3, PATCH_MAGICDWORD(0x5b, 0x04, 0x80, 0x36), 0, qfg1vgaSignatureDialogHeader, qfg1vgaPatchDialogHeader }, + { 331, "moving to crusher", 1, PATCH_MAGICDWORD(0x51, 0x1f, 0x36, 0x39), 0, qfg1vgaSignatureMoveToCrusher, qfg1vgaPatchMoveToCrusher }, SCI_SIGNATUREENTRY_TERMINATOR }; diff --git a/engines/sci/engine/scriptdebug.cpp b/engines/sci/engine/scriptdebug.cpp index b2f22aa985..277437109c 100644 --- a/engines/sci/engine/scriptdebug.cpp +++ b/engines/sci/engine/scriptdebug.cpp @@ -79,7 +79,7 @@ reg_t disassemble(EngineState *s, reg32_t pos, bool printBWTag, bool printByteco Kernel *kernel = g_sci->getKernel(); if (!mobj) { - warning("Disassembly failed: Segment %04x non-existant or not a script", pos.getSegment()); + warning("Disassembly failed: Segment %04x non-existent or not a script", pos.getSegment()); return retval; } else script_entity = (Script *)mobj; diff --git a/engines/sci/engine/workarounds.cpp b/engines/sci/engine/workarounds.cpp index 9fa0368784..6af6326042 100644 --- a/engines/sci/engine/workarounds.cpp +++ b/engines/sci/engine/workarounds.cpp @@ -51,8 +51,8 @@ const SciWorkaroundEntry arithmeticWorkarounds[] = { // gameID, room,script,lvl, object-name, method-name, call,index, workaround const SciWorkaroundEntry uninitializedReadWorkarounds[] = { { GID_CASTLEBRAIN, 280, 280, 0, "programmer", "dispatchEvent", -1, 0, { WORKAROUND_FAKE, 0xf } }, // pressing 'q' on the computer screen in the robot room, and closing the help dialog that pops up (bug #3039656). Moves the cursor to the view with the ID returned (in this case, the robot hand) - { GID_CNICK_KQ, 200, 0, 1, "Character", "say", -1, -1, { WORKAROUND_FAKE, 0 } }, // checkers, like in hoyle 3 - temps 504 and 505 - { GID_CNICK_KQ, -1, 700, 0, "gcWindow", "open", -1, -1, { WORKAROUND_FAKE, 0 } }, // when entering control menu, like in hoyle 3 + { GID_CNICK_KQ, -1, 0, 1, "Character", "say", -1, -1, { WORKAROUND_FAKE, 0 } }, // checkers/backgammon, like in hoyle 3 - temps 504 and 505 - bug #3606025 + { GID_CNICK_KQ, -1, 700, 0, "gcWindow", "open", -1, -1, { WORKAROUND_FAKE, 0 } }, // when entering the control menu, like in hoyle 3 { GID_CNICK_LONGBOW, 0, 0, 0, "RH Budget", "init", -1, 1, { WORKAROUND_FAKE, 0 } }, // when starting the game { GID_ECOQUEST, -1, -1, 0, NULL, "doVerb", -1, 0, { WORKAROUND_FAKE, 0 } }, // almost clicking anywhere triggers this in almost all rooms { GID_FANMADE, 516, 979, 0, "", "export 0", -1, 20, { WORKAROUND_FAKE, 0 } }, // Happens in Grotesteing after the logos @@ -140,7 +140,7 @@ const SciWorkaroundEntry uninitializedReadWorkarounds[] = { { GID_QFG2, 260, 260, 0, "jabbarS", "changeState",0x2d22, -1, { WORKAROUND_FAKE, 0 } }, // During the thief's first mission (in the house), just before Jabbar is about to enter the house (where you have to hide in the wardrobe), bug #3040469, temps 1 and 2 { GID_QFG2, 500, 500, 0, "lightNextCandleS", "changeState", -1, -1, { WORKAROUND_FAKE, 0 } }, // Inside the last room, while Ad Avis performs the ritual to summon the genie - bug #3148418 { GID_QFG2, -1, 700, 0, NULL, "showSign", -1, 10, { WORKAROUND_FAKE, 0 } }, // Occurs sometimes when reading a sign in Raseir, Shapeir et al - bugs #3272735, #3275413 - { GID_QFG3, 510, 510, 0, "awardPrize", "changeState", -1, 0, { WORKAROUND_FAKE, 0 } }, // Simbani warrior challenge, after throwing the spears and retrieving the ring - bug #3049435 + { GID_QFG3, 510, 510, 0, "awardPrize", "changeState", -1, 0, { WORKAROUND_FAKE, 1 } }, // Simbani warrior challenge, after throwing the spears and retrieving the ring - bug #3049435. Must be non-zero, otherwise the prize is awarded twice - bug #3575570. { GID_QFG3, 140, 140, 0, "rm140", "init", 0x1008, 0, { WORKAROUND_FAKE, 0 } }, // when importing a character and selecting the previous profession - bug #3040460 { GID_QFG3, 330, 330, -1, "Teller", "doChild", -1, -1, { WORKAROUND_FAKE, 0 } }, // when talking to King Rajah about "Rajah" (bug #3036390, temp 1) or "Tarna" (temp 0), or when clicking on yourself and saying "Greet" (bug #3039774, temp 1) { GID_QFG3, 700, 700, -1, "monsterIsDead", "changeState", -1, 0, { WORKAROUND_FAKE, 0 } }, // in the jungle, after winning any fight, bug #3040624 @@ -151,6 +151,8 @@ const SciWorkaroundEntry uninitializedReadWorkarounds[] = { { GID_QFG4, -1, 15, -1, "charInitScreen", "dispatchEvent", -1, 5, { WORKAROUND_FAKE, 0 } }, // floppy version, when viewing the character screen { GID_QFG4, -1, 64917, -1, "controlPlane", "setBitmap", -1, 3, { WORKAROUND_FAKE, 0 } }, // floppy version, when entering the game menu { GID_QFG4, -1, 64917, -1, "Plane", "setBitmap", -1, 3, { WORKAROUND_FAKE, 0 } }, // floppy version, happens sometimes in fight scenes + { GID_QFG4, 520, 64950, 0, "fLake2", "handleEvent", -1, 0, { WORKAROUND_FAKE, 0 } }, // CD version, at the lake, when meeting the Rusalka and attempting to leave + { GID_QFG4, 800, 64950, 0, "View", "handleEvent", -1, 0, { WORKAROUND_FAKE, 0 } }, // CD version, in the room with the spider pillar, when climbing on the pillar { GID_RAMA, 12, 64950, -1, "InterfaceFeature", "handleEvent", -1, 0, { WORKAROUND_FAKE, 0 } }, // Demo, right when it starts { GID_RAMA, 12, 64950, -1, "hiliteOptText", "handleEvent", -1, 0, { WORKAROUND_FAKE, 0 } }, // Demo, right when it starts { GID_RAMA, 12, 64950, -1, "View", "handleEvent", -1, 0, { WORKAROUND_FAKE, 0 } }, // Demo, right when it starts @@ -243,12 +245,10 @@ const SciWorkaroundEntry kDisposeScript_workarounds[] = { // gameID, room,script,lvl, object-name, method-name, call,index, workaround const SciWorkaroundEntry kDoSoundFade_workarounds[] = { - { GID_CAMELOT, -1, 989, 0, "rmMusic", "fade", -1, 0, { WORKAROUND_IGNORE, 0 } }, // gets called frequently with a NULL reference (i.e. 0:0) - bug #3035149 - { GID_KQ1, -1, 989, 0, "gameSound", "fade", -1, 0, { WORKAROUND_IGNORE, 0 } }, // gets called in several scenes (e.g. graham cracker) with 0:0 - { GID_KQ4, -1, 989, 0, "mySound", "", -1, 0, { WORKAROUND_IGNORE, 0 } }, // gets called in the demo when trying to open the non-existent menu with 0:0 - bug #3036942 { GID_KQ5, 213, 989, 0, "globalSound3", "fade", -1, 0, { WORKAROUND_STILLCALL, 0 } }, // english floppy: when bandits leave the secret temple, parameter 4 is an object - bug #3037594 { GID_KQ6, 105, 989, 0, "globalSound", "fade", -1, 0, { WORKAROUND_STILLCALL, 0 } }, // floppy: during intro, parameter 4 is an object { GID_KQ6, 460, 989, 0, "globalSound2", "fade", -1, 0, { WORKAROUND_STILLCALL, 0 } }, // after pulling the black widow's web on the isle of wonder, parameter 4 is an object - bug #3034567 + { GID_QFG4, -1, 64989, 0, "longSong", "fade", -1, 0, { WORKAROUND_STILLCALL, 0 } }, // CD version: many places, parameter 4 is an object (longSong) SCI_WORKAROUNDENTRY_TERMINATOR }; |