aboutsummaryrefslogtreecommitdiff
path: root/engines/sci
diff options
context:
space:
mode:
Diffstat (limited to 'engines/sci')
-rw-r--r--engines/sci/console.cpp20
-rw-r--r--engines/sci/console.h1
-rw-r--r--engines/sci/decompressor.cpp2
-rw-r--r--engines/sci/detection.cpp11
-rw-r--r--engines/sci/detection_tables.h58
-rw-r--r--engines/sci/engine/features.cpp8
-rw-r--r--engines/sci/engine/kernel.h10
-rw-r--r--engines/sci/engine/kernel_tables.h7
-rw-r--r--engines/sci/engine/kfile.cpp34
-rw-r--r--engines/sci/engine/kgraphics.cpp13
-rw-r--r--engines/sci/engine/kgraphics32.cpp55
-rw-r--r--engines/sci/engine/kmath.cpp82
-rw-r--r--engines/sci/engine/kpathing.cpp13
-rw-r--r--engines/sci/engine/ksound.cpp1
-rw-r--r--engines/sci/engine/kstring.cpp77
-rw-r--r--engines/sci/engine/message.cpp39
-rw-r--r--engines/sci/engine/savegame.cpp3
-rw-r--r--engines/sci/engine/script_patches.cpp63
-rw-r--r--engines/sci/engine/scriptdebug.cpp2
-rw-r--r--engines/sci/engine/workarounds.cpp12
-rw-r--r--engines/sci/event.cpp10
-rw-r--r--engines/sci/graphics/animate.h1
-rw-r--r--engines/sci/graphics/cursor.cpp3
-rw-r--r--engines/sci/graphics/cursor.h2
-rw-r--r--engines/sci/graphics/frameout.cpp75
-rw-r--r--engines/sci/graphics/frameout.h8
-rw-r--r--engines/sci/graphics/menu.cpp2
-rw-r--r--engines/sci/graphics/palette.cpp11
-rw-r--r--engines/sci/graphics/portrait.cpp2
-rw-r--r--engines/sci/graphics/ports.cpp2
-rw-r--r--engines/sci/parser/vocabulary.cpp2
-rw-r--r--engines/sci/resource.cpp1
-rw-r--r--engines/sci/sci.cpp3
-rw-r--r--engines/sci/sci.h1
-rw-r--r--engines/sci/sound/drivers/midi.cpp12
-rw-r--r--engines/sci/sound/midiparser_sci.cpp24
-rw-r--r--engines/sci/sound/music.cpp3
-rw-r--r--engines/sci/sound/soundcmd.cpp92
38 files changed, 587 insertions, 178 deletions
diff --git a/engines/sci/console.cpp b/engines/sci/console.cpp
index 5ae8245e5a..50fae61de0 100644
--- a/engines/sci/console.cpp
+++ b/engines/sci/console.cpp
@@ -284,6 +284,7 @@ void Console::postEnter() {
#endif
VideoState emptyState;
+ emptyState.reset();
emptyState.fileName = _videoFile;
emptyState.flags = kDoubled; // always allow the videos to be double sized
playVideo(videoDecoder, emptyState);
@@ -2226,6 +2227,7 @@ bool Console::cmdIsSample(int argc, const char **argv) {
DebugPrintf("Sample size: %d, sample rate: %d, channels: %d, digital channel number: %d\n",
track->digitalSampleSize, track->digitalSampleRate, track->channelCount, track->digitalChannelNr);
+ delete soundRes;
return true;
}
@@ -2349,8 +2351,7 @@ bool Console::cmdVMVarlist(int argc, const char **argv) {
for (int i = 0; i < 4; i++) {
DebugPrintf("%s vars at %04x:%04x ", varnames[i], PRINT_REG(make_reg(s->variablesSegment[i], s->variables[i] - s->variablesBase[i])));
- if (s->variablesMax)
- DebugPrintf(" total %d", s->variablesMax[i]);
+ DebugPrintf(" total %d", s->variablesMax[i]);
DebugPrintf("\n");
}
@@ -2407,7 +2408,7 @@ bool Console::cmdVMVars(int argc, const char **argv) {
return true;
}
- if ((s->variablesMax) && (s->variablesMax[varType] <= varIndex)) {
+ if (s->variablesMax[varType] <= varIndex) {
DebugPrintf("Maximum variable number for this type is %d (0x%x)\n", s->variablesMax[varType], s->variablesMax[varType]);
return true;
}
@@ -2507,6 +2508,7 @@ bool Console::cmdValueType(int argc, const char **argv) {
break;
case SIG_TYPE_INTEGER:
DebugPrintf("Integer");
+ break;
case SIG_TYPE_INTEGER | SIG_TYPE_NULL:
DebugPrintf("Null");
break;
@@ -3633,6 +3635,8 @@ bool Console::cmdAddresses(int argc, const char **argv) {
DebugPrintf(" - ?obj -- Looks up an object with the specified name, uses its address. This will abort if\n");
DebugPrintf(" the object name is ambiguous; in that case, a list of addresses and indices is provided.\n");
DebugPrintf(" ?obj.idx may be used to disambiguate 'obj' by the index 'idx'.\n");
+ DebugPrintf(" Underscores are used as substitute characters for spaces in object names.\n");
+ DebugPrintf(" For example, an object named \"Glass Jar\" can be accessed as \"Glass_Jar\".\n");
return true;
}
@@ -3764,6 +3768,8 @@ static int parse_reg_t(EngineState *s, const char *str, reg_t *dest, bool mayBeV
charsCountObject++;
if ((*strLoop >= 'I') && (*strLoop <= 'Z'))
charsCountObject++;
+ if (*strLoop == '_') // underscores are used as substitutes for spaces in object names
+ charsCountObject++;
}
strLoop++;
}
@@ -3836,10 +3842,16 @@ static int parse_reg_t(EngineState *s, const char *str, reg_t *dest, bool mayBeV
index = strtol(tmp + 1, &endptr, 16);
if (*endptr)
return -1;
- // Chop of the index
+ // Chop off the index
str_objname = Common::String(str_objname.c_str(), tmp);
}
+ // Replace all underscores in the name with spaces
+ for (uint i = 0; i < str_objname.size(); i++) {
+ if (str_objname[i] == '_')
+ str_objname.setChar(' ', i);
+ }
+
// Now all values are available; iterate over all objects.
*dest = s->_segMan->findObjectByName(str_objname, index);
if (dest->isNull())
diff --git a/engines/sci/console.h b/engines/sci/console.h
index 1c54748842..37cf35a471 100644
--- a/engines/sci/console.h
+++ b/engines/sci/console.h
@@ -175,7 +175,6 @@ private:
SciEngine *_engine;
DebugState &_debugState;
- bool _mouseVisible;
Common::String _videoFile;
int _videoFrameDelay;
};
diff --git a/engines/sci/decompressor.cpp b/engines/sci/decompressor.cpp
index 82af6eca43..306825008d 100644
--- a/engines/sci/decompressor.cpp
+++ b/engines/sci/decompressor.cpp
@@ -590,6 +590,8 @@ void DecompressorLZW::reorderView(byte *src, byte *dest) {
if (celindex < cel_total) {
warning("View decompression generated too few (%d / %d) headers", celindex, cel_total);
+ free(cc_pos);
+ free(cc_lengths);
return;
}
diff --git a/engines/sci/detection.cpp b/engines/sci/detection.cpp
index 58ac5f1fa6..ebad3d039a 100644
--- a/engines/sci/detection.cpp
+++ b/engines/sci/detection.cpp
@@ -103,6 +103,7 @@ static const PlainGameDescriptor s_sciGameTitles[] = {
{"pq4", "Police Quest IV: Open Season"}, // floppy is SCI2, CD SCI2.1
{"qfg4", "Quest for Glory IV: Shadows of Darkness"}, // floppy is SCI2, CD SCI2.1
// === SCI2.1 games ========================================================
+ {"chest", "Inside the Chest"}, // aka Behind the Developer's Shield
{"gk2", "The Beast Within: A Gabriel Knight Mystery"},
// TODO: Inside The Chest/Behind the Developer's Shield
{"kq7", "King's Quest VII: The Princeless Bride"},
@@ -132,6 +133,7 @@ static const GameIdStrToEnum s_gameIdStrToEnum[] = {
{ "astrochicken", GID_ASTROCHICKEN },
{ "camelot", GID_CAMELOT },
{ "castlebrain", GID_CASTLEBRAIN },
+ { "chest", GID_CHEST },
{ "christmas1988", GID_CHRISTMAS1988 },
{ "christmas1990", GID_CHRISTMAS1990 },
{ "christmas1992", GID_CHRISTMAS1992 },
@@ -208,6 +210,7 @@ struct OldNewIdTableEntry {
};
static const OldNewIdTableEntry s_oldNewTable[] = {
+ { "archive", "chest", SCI_VERSION_NONE },
{ "arthur", "camelot", SCI_VERSION_NONE },
{ "brain", "castlebrain", SCI_VERSION_1_MIDDLE }, // Amiga
{ "brain", "castlebrain", SCI_VERSION_1_LATE },
@@ -834,12 +837,16 @@ Common::Error SciEngine::saveGameState(int slot, const Common::String &desc) {
return Common::kNoError;
}
+// Before enabling the load option in the ScummVM menu, the main game loop must
+// have run at least once. When the game loop runs, kGameIsRestarting is invoked,
+// thus the speed throttler is initialized. Hopefully fixes bug #3565505.
+
bool SciEngine::canLoadGameStateCurrently() {
- return !_gamestate->executionStackBase;
+ return !_gamestate->executionStackBase && (_gamestate->_throttleLastTime > 0 || _gamestate->_throttleTrigger);
}
bool SciEngine::canSaveGameStateCurrently() {
- return !_gamestate->executionStackBase;
+ return !_gamestate->executionStackBase && (_gamestate->_throttleLastTime > 0 || _gamestate->_throttleTrigger);
}
} // End of namespace Sci
diff --git a/engines/sci/detection_tables.h b/engines/sci/detection_tables.h
index b978f40aba..c8137c8fb4 100644
--- a/engines/sci/detection_tables.h
+++ b/engines/sci/detection_tables.h
@@ -129,6 +129,16 @@ static const struct ADGameDescription SciGameDescriptions[] = {
AD_LISTEND},
Common::EN_ANY, Common::kPlatformPC, 0, GUIO4(GUIO_NOSPEECH, GAMEOPTION_PREFER_DIGITAL_SFX, GAMEOPTION_ORIGINAL_SAVELOAD, GAMEOPTION_FB01_MIDI) },
+ // Castle of Dr. Brain - English DOS 5.25" Floppy VGA 1.1 (from rnjacobs, bug report #3578286)
+ {"castlebrain", "", {
+ {"resource.map", 0, "a1deac2647ad09472c63656bfb950a4d", 2739},
+ {"resource.000", 0, "27ec5fa09cd12a7fd16e86d96a2ed245", 347071},
+ {"resource.001", 0, "13e81e1839cd7b216d2bb5615c1ca160", 356812},
+ {"resource.002", 0, "583d348c908f89f94f8551d7fe0a2eca", 991752},
+ {"resource.003", 0, "6c3d1bb26ad532c94046bc9ac49b5ff4", 728315},
+ AD_LISTEND},
+ Common::EN_ANY, Common::kPlatformPC, 0, GUIO4(GUIO_NOSPEECH, GAMEOPTION_PREFER_DIGITAL_SFX, GAMEOPTION_ORIGINAL_SAVELOAD, GAMEOPTION_FB01_MIDI) },
+
// Castle of Dr. Brain - English DOS Floppy 1.1
{"castlebrain", "", {
{"resource.map", 0, "f77728304c70017c54793eb6ca648174", 2745},
@@ -162,6 +172,16 @@ static const struct ADGameDescription SciGameDescriptions[] = {
AD_LISTEND},
Common::ES_ESP, Common::kPlatformPC, ADGF_ADDENGLISH, GUIO4(GUIO_NOSPEECH, GAMEOPTION_PREFER_DIGITAL_SFX, GAMEOPTION_ORIGINAL_SAVELOAD, GAMEOPTION_FB01_MIDI) },
+#ifdef ENABLE_SCI32
+ // Inside the Chest / Behind the Developer's Shield
+ // SCI interpreter version 2.000.000
+ {"chest", "", {
+ {"resource.map", 0, "9dd015e79cac4f91e7de805448f39775", 1912},
+ {"resource.000", 0, "e4efcd042f86679dd4e1834bb3a38edb", 3770943},
+ AD_LISTEND},
+ Common::EN_ANY, Common::kPlatformPC, ADGF_UNSTABLE, GUIO3(GUIO_NOSPEECH, GAMEOPTION_PREFER_DIGITAL_SFX, GAMEOPTION_FB01_MIDI) },
+#endif
+
// Christmas Card 1988 - English DOS
// SCI interpreter version 0.000.294
{"christmas1988", "", {
@@ -268,7 +288,7 @@ static const struct ADGameDescription SciGameDescriptions[] = {
{"resource.006", 0, "08050329aa113a9f14ed99cbfe3536ec", 232942},
{"resource.007", 0, "64f342463f6f35ba71b3509ef696ae3f", 267702},
AD_LISTEND},
- Common::EN_ANY, Common::kPlatformPC, 0, GUIO4(GUIO_NOSPEECH, GAMEOPTION_PREFER_DIGITAL_SFX, GAMEOPTION_ORIGINAL_SAVELOAD, GAMEOPTION_FB01_MIDI) },
+ Common::EN_ANY, Common::kPlatformPC, 0, GUIO5(GUIO_NOSPEECH, GAMEOPTION_EGA_UNDITHER, GAMEOPTION_PREFER_DIGITAL_SFX, GAMEOPTION_ORIGINAL_SAVELOAD, GAMEOPTION_FB01_MIDI) },
// Conquests of Camelot - English Amiga (from www.back2roots.org)
// Executable scanning reports "1.002.030"
@@ -561,6 +581,15 @@ static const struct ADGameDescription SciGameDescriptions[] = {
AD_LISTEND},
Common::EN_ANY, Common::kPlatformPC, 0, GUIO4(GUIO_NOSPEECH, GAMEOPTION_PREFER_DIGITAL_SFX, GAMEOPTION_ORIGINAL_SAVELOAD, GAMEOPTION_FB01_MIDI) },
+ // Freddy Pharkas - French DOS Floppy (supplied by misterhands in bug report #3589449)
+ // Executable scanning reports "1.cfs.081"
+ {"freddypharkas", "Floppy", {
+ {"resource.map", 0, "a32674e7fbf7b213b4a066c8037f16b6", 5816},
+ {"resource.000", 0, "fed4808fdb72486908ac7ad0044b14d8", 5233230},
+ {"resource.msg", 0, "4dc478f5c73b57e5d690bdfffdcf1c44", 816518},
+ AD_LISTEND},
+ Common::FR_FRA, Common::kPlatformPC, 0, GUIO4(GUIO_NOSPEECH, GAMEOPTION_PREFER_DIGITAL_SFX, GAMEOPTION_ORIGINAL_SAVELOAD, GAMEOPTION_FB01_MIDI) },
+
// Freddy Pharkas - Windows (supplied by abevi in bug report #2612718)
// Executable scanning reports "1.cfs.081"
// SCI interpreter version 1.001.132 (just a guess)
@@ -1493,6 +1522,14 @@ static const struct ADGameDescription SciGameDescriptions[] = {
AD_LISTEND},
Common::ES_ESP, Common::kPlatformPC, 0, GUIO4(GUIO_NOSPEECH, GAMEOPTION_PREFER_DIGITAL_SFX, GAMEOPTION_ORIGINAL_SAVELOAD, GAMEOPTION_FB01_MIDI) },
+ // King's Quest 6 - Italian DOS Floppy (supplied by guybrush79 in bug report #3606719)
+ {"kq6", "", {
+ {"resource.map", 0, "48c9fc8e96cbdac078ca7d3df274e29a", 8942},
+ {"resource.000", 0, "d3358ba7306378aed83d02b5c3f11311", 8531908},
+ {"resource.msg", 0, "b7e8220be596fd6a9287eae5a8fd354a", 279886},
+ AD_LISTEND},
+ Common::IT_ITA, Common::kPlatformPC, 0, GUIO4(GUIO_NOSPEECH, GAMEOPTION_PREFER_DIGITAL_SFX, GAMEOPTION_ORIGINAL_SAVELOAD, GAMEOPTION_FB01_MIDI) },
+
// King's Quest 6 - English DOS CD (from the King's Quest Collection)
// Executable scanning reports "1.cfs.158", VERSION file reports "1.034 9/11/94 - KQ6 version 1.000.00G"
// SCI interpreter version 1.001.054
@@ -2241,7 +2278,7 @@ static const struct ADGameDescription SciGameDescriptions[] = {
{"resmap.000", 0, "4407849fd52fe3efb0c30fba60cd5cd4", 8206},
{"ressci.000", 0, "dc37c3055fffbefb494ff22b145d377b", 66964472},
AD_LISTEND},
- Common::DE_DEU, Common::kPlatformPC, ADGF_UNSTABLE, GUIO5(GUIO_NOSPEECH, GUIO_NOASPECT, GAMEOPTION_PREFER_DIGITAL_SFX, GAMEOPTION_ORIGINAL_SAVELOAD, GAMEOPTION_FB01_MIDI) },
+ Common::FR_FRA, Common::kPlatformPC, ADGF_UNSTABLE, GUIO5(GUIO_NOSPEECH, GUIO_NOASPECT, GAMEOPTION_PREFER_DIGITAL_SFX, GAMEOPTION_ORIGINAL_SAVELOAD, GAMEOPTION_FB01_MIDI) },
// Larry 7 - Italian DOS CD (from glorifindel)
// SCI interpreter version 3.000.000
@@ -3101,6 +3138,15 @@ static const struct ADGameDescription SciGameDescriptions[] = {
AD_LISTEND},
Common::DE_DEU, Common::kPlatformPC, 0, GUIO4(GUIO_NOSPEECH, GAMEOPTION_PREFER_DIGITAL_SFX, GAMEOPTION_ORIGINAL_SAVELOAD, GAMEOPTION_FB01_MIDI) },
+ // Quest for Glory 3 - French DOS v1.1 (supplied by misterhands in bug report #3586214)
+ // Executable scanning reports "L.rry.083"
+ {"qfg3", "", {
+ {"resource.map", 0, "19e2bf9b693932b5e2bb59b9f9ab86c9", 5958},
+ {"resource.000", 0, "6178ad2e83e58e4671ca03315f7a6498", 5868000},
+ {"resource.msg", 0, "0fa1047002df904b8d1807bb7bab4fab", 267210},
+ AD_LISTEND},
+ Common::FR_FRA, Common::kPlatformPC, 0, GUIO3(GAMEOPTION_PREFER_DIGITAL_SFX, GAMEOPTION_ORIGINAL_SAVELOAD, GAMEOPTION_FB01_MIDI) },
+
// Quest for Glory 3 - Spanish DOS CD (from jvprat)
// Executable scanning reports "L.rry.083", VERSION file reports "1.000.000, June 30, 1994"
{"qfg3", "", {
@@ -3261,7 +3307,7 @@ static const struct ADGameDescription SciGameDescriptions[] = {
{"resource.msg", 0, "1aeafe2b495de288d002109650b66614", 1364},
{"resource.000", 0, "8e10d4f05c1fd9f883384fa38a898489", 377394},
AD_LISTEND},
- Common::EN_ANY, Common::kPlatformPC, ADGF_DEMO, GUIO4(GUIO_NOSPEECH, GAMEOPTION_PREFER_DIGITAL_SFX, GAMEOPTION_ORIGINAL_SAVELOAD, GAMEOPTION_FB01_MIDI) },
+ Common::EN_ANY, Common::kPlatformPC, ADGF_DEMO, GUIO3(GAMEOPTION_PREFER_DIGITAL_SFX, GAMEOPTION_ORIGINAL_SAVELOAD, GAMEOPTION_FB01_MIDI) },
// Slater & Charlie Go Camping - English DOS/Windows
{"slater", "", {
@@ -3269,7 +3315,7 @@ static const struct ADGameDescription SciGameDescriptions[] = {
{"resource.map", 0, "21f85414124dc23e54544a5536dc35cd", 4044},
{"resource.msg", 0, "c44f51fb955eae266fecf360ebcd5ad2", 1132},
AD_LISTEND},
- Common::EN_ANY, Common::kPlatformPC, 0, GUIO4(GUIO_NOSPEECH, GAMEOPTION_PREFER_DIGITAL_SFX, GAMEOPTION_ORIGINAL_SAVELOAD, GAMEOPTION_FB01_MIDI) },
+ Common::EN_ANY, Common::kPlatformPC, 0, GUIO3(GAMEOPTION_PREFER_DIGITAL_SFX, GAMEOPTION_ORIGINAL_SAVELOAD, GAMEOPTION_FB01_MIDI) },
// Slater & Charlie Go Camping - English DOS/Windows (Sierra Originals)
@@ -3278,7 +3324,7 @@ static const struct ADGameDescription SciGameDescriptions[] = {
{"resource.map", 0, "21f85414124dc23e54544a5536dc35cd", 4044},
{"resource.msg", 0, "c44f51fb955eae266fecf360ebcd5ad2", 1132},
AD_LISTEND},
- Common::EN_ANY, Common::kPlatformPC, 0, GUIO4(GUIO_NOSPEECH, GAMEOPTION_PREFER_DIGITAL_SFX, GAMEOPTION_ORIGINAL_SAVELOAD, GAMEOPTION_FB01_MIDI) },
+ Common::EN_ANY, Common::kPlatformPC, 0, GUIO3(GAMEOPTION_PREFER_DIGITAL_SFX, GAMEOPTION_ORIGINAL_SAVELOAD, GAMEOPTION_FB01_MIDI) },
// Slater & Charlie Go Camping - English Macintosh
{"slater", "", {
@@ -3608,7 +3654,7 @@ static const struct ADGameDescription SciGameDescriptions[] = {
{"resource.map", 0, "ed90a8e3ccc53af6633ff6ab58392bae", 7054},
{"resource.000", 0, "63247e3901ab8963d4eece73747832e0", 5157378},
AD_LISTEND},
- Common::EN_ANY, Common::kPlatformPC, ADGF_CD, GUIO5(GUIO_MIDIGM, GAMEOPTION_SQ4_SILVER_CURSORS, GAMEOPTION_PREFER_DIGITAL_SFX, GAMEOPTION_ORIGINAL_SAVELOAD, GAMEOPTION_FB01_MIDI) },
+ Common::EN_ANY, Common::kPlatformPC, ADGF_CD, GUIO4(GAMEOPTION_SQ4_SILVER_CURSORS, GAMEOPTION_PREFER_DIGITAL_SFX, GAMEOPTION_ORIGINAL_SAVELOAD, GAMEOPTION_FB01_MIDI) },
// Space Quest 4 - English Windows CD (from the Space Quest Collection)
// Executable scanning reports "1.001.064", VERSION file reports "1.0"
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
};
diff --git a/engines/sci/event.cpp b/engines/sci/event.cpp
index 14443db1e2..7318fe2f68 100644
--- a/engines/sci/event.cpp
+++ b/engines/sci/event.cpp
@@ -160,9 +160,15 @@ SciEvent EventManager::getScummVMEvent() {
noEvent.mousePos = input.mousePos = mousePos;
- if (!found || ev.type == Common::EVENT_MOUSEMOVE)
- return noEvent;
+ if (!found || ev.type == Common::EVENT_MOUSEMOVE) {
+ int modifiers = em->getModifierState();
+ noEvent.modifiers =
+ ((modifiers & Common::KBD_ALT) ? SCI_KEYMOD_ALT : 0) |
+ ((modifiers & Common::KBD_CTRL) ? SCI_KEYMOD_CTRL : 0) |
+ ((modifiers & Common::KBD_SHIFT) ? SCI_KEYMOD_LSHIFT | SCI_KEYMOD_RSHIFT : 0);
+ return noEvent;
+ }
if (ev.type == Common::EVENT_QUIT) {
input.type = SCI_EVENT_QUIT;
return input;
diff --git a/engines/sci/graphics/animate.h b/engines/sci/graphics/animate.h
index 5e2e39ea1a..52da7d6ec6 100644
--- a/engines/sci/graphics/animate.h
+++ b/engines/sci/graphics/animate.h
@@ -51,7 +51,6 @@ enum ViewScaleSignals {
kScaleSignalDoScaling = 0x0001, // enables scaling when drawing that cel (involves scaleX and scaleY)
kScaleSignalGlobalScaling = 0x0002, // means that global scaling shall get applied on that cel (sets scaleX/scaleY)
kScaleSignalHoyle4SpecialHandling = 0x0004 // HOYLE4-exclusive: special handling inside kAnimate, is used when giving out cards
-
};
struct AnimateEntry {
diff --git a/engines/sci/graphics/cursor.cpp b/engines/sci/graphics/cursor.cpp
index ce77cf6ed3..fe2aefd689 100644
--- a/engines/sci/graphics/cursor.cpp
+++ b/engines/sci/graphics/cursor.cpp
@@ -481,7 +481,7 @@ void GfxCursor::kernelMoveCursor(Common::Point pos) {
_event->getSciEvent(SCI_EVENT_PEEK);
}
-void GfxCursor::kernelSetMacCursor(GuiResourceId viewNum, int loopNum, int celNum, Common::Point *hotspot) {
+void GfxCursor::kernelSetMacCursor(GuiResourceId viewNum, int loopNum, int celNum) {
// Here we try to map the view number onto the cursor. What they did was keep the
// kSetCursor calls the same, but perform remapping on the cursors. They also took
// it a step further and added a new kPlatform sub-subop that handles remapping
@@ -532,6 +532,7 @@ void GfxCursor::kernelSetMacCursor(GuiResourceId viewNum, int loopNum, int celNu
if (!macCursor->readFromStream(resStream)) {
warning("Failed to load Mac cursor %d", viewNum);
+ delete macCursor;
return;
}
diff --git a/engines/sci/graphics/cursor.h b/engines/sci/graphics/cursor.h
index ac928f50bb..369bd22a0b 100644
--- a/engines/sci/graphics/cursor.h
+++ b/engines/sci/graphics/cursor.h
@@ -60,7 +60,7 @@ public:
bool isVisible();
void kernelSetShape(GuiResourceId resourceId);
void kernelSetView(GuiResourceId viewNum, int loopNum, int celNum, Common::Point *hotspot);
- void kernelSetMacCursor(GuiResourceId viewNum, int loopNum, int celNum, Common::Point *hotspot);
+ void kernelSetMacCursor(GuiResourceId viewNum, int loopNum, int celNum);
void setPosition(Common::Point pos);
Common::Point getPosition();
void refreshPosition();
diff --git a/engines/sci/graphics/frameout.cpp b/engines/sci/graphics/frameout.cpp
index 8b7fa2c384..5f65762685 100644
--- a/engines/sci/graphics/frameout.cpp
+++ b/engines/sci/graphics/frameout.cpp
@@ -349,6 +349,36 @@ void GfxFrameout::deletePlaneLine(reg_t object, reg_t hunkId) {
}
}
+// Adapted from GfxAnimate::applyGlobalScaling()
+void GfxFrameout::applyGlobalScaling(FrameoutEntry *itemEntry, Common::Rect planeRect, int16 celHeight) {
+ // Global scaling uses global var 2 and some other stuff to calculate scaleX/scaleY
+ int16 maxScale = readSelectorValue(_segMan, itemEntry->object, SELECTOR(maxScale));
+ int16 maxCelHeight = (maxScale * celHeight) >> 7;
+ reg_t globalVar2 = g_sci->getEngineState()->variables[VAR_GLOBAL][2]; // current room object
+ int16 vanishingY = readSelectorValue(_segMan, globalVar2, SELECTOR(vanishingY));
+
+ int16 fixedPortY = planeRect.bottom - vanishingY;
+ int16 fixedEntryY = itemEntry->y - vanishingY;
+ if (!fixedEntryY)
+ fixedEntryY = 1;
+
+ if ((celHeight == 0) || (fixedPortY == 0))
+ error("global scaling panic");
+
+ itemEntry->scaleY = (maxCelHeight * fixedEntryY) / fixedPortY;
+ itemEntry->scaleY = (itemEntry->scaleY * maxScale) / celHeight;
+
+ // Make sure that the calculated value is sane
+ if (itemEntry->scaleY < 1 /*|| itemEntry->scaleY > 128*/)
+ itemEntry->scaleY = 128;
+
+ itemEntry->scaleX = itemEntry->scaleY;
+
+ // and set objects scale selectors
+ //writeSelectorValue(_segMan, itemEntry->object, SELECTOR(scaleX), itemEntry->scaleX);
+ //writeSelectorValue(_segMan, itemEntry->object, SELECTOR(scaleY), itemEntry->scaleY);
+}
+
void GfxFrameout::kernelAddScreenItem(reg_t object) {
// Ignore invalid items
if (!_segMan->isObject(object)) {
@@ -390,8 +420,15 @@ void GfxFrameout::kernelUpdateScreenItem(reg_t object) {
itemEntry->priority = itemEntry->y;
itemEntry->signal = readSelectorValue(_segMan, object, SELECTOR(signal));
- itemEntry->scaleX = readSelectorValue(_segMan, object, SELECTOR(scaleX));
- itemEntry->scaleY = readSelectorValue(_segMan, object, SELECTOR(scaleY));
+ itemEntry->scaleSignal = readSelectorValue(_segMan, object, SELECTOR(scaleSignal));
+
+ if (itemEntry->scaleSignal & kScaleSignalDoScaling32) {
+ itemEntry->scaleX = readSelectorValue(_segMan, object, SELECTOR(scaleX));
+ itemEntry->scaleY = readSelectorValue(_segMan, object, SELECTOR(scaleY));
+ } else {
+ itemEntry->scaleX = 128;
+ itemEntry->scaleY = 128;
+ }
itemEntry->visible = true;
// Check if the entry can be hidden
@@ -650,7 +687,13 @@ void GfxFrameout::kernelFrameout() {
_paint32->fillRect(it->planeRect, it->planeBack);
_coordAdjuster->pictureSetDisplayArea(it->planeRect);
- _palette->drewPicture(it->pictureId);
+ // Invoking drewPicture() with an invalid picture ID in SCI32 results in
+ // invalidating the palVary palette when a palVary effect is active. This
+ // is quite obvious in QFG4, where the day time palette is incorrectly
+ // shown when exiting the caves, and the correct night time palette
+ // flashes briefly each time that kPalVaryInit is called.
+ if (it->pictureId != 0xFFFF)
+ _palette->drewPicture(it->pictureId);
FrameoutList itemList;
@@ -699,6 +742,14 @@ void GfxFrameout::kernelFrameout() {
// TODO: maybe we should clip the cels rect with this, i'm not sure
// the only currently known usage is game menu of gk1
} else if (view) {
+ // Process global scaling, if needed.
+ // TODO: Seems like SCI32 always processes global scaling for scaled objects
+ // TODO: We can only process symmetrical scaling for now (i.e. same value for scaleX/scaleY)
+ if ((itemEntry->scaleSignal & kScaleSignalDoScaling32) &&
+ !(itemEntry->scaleSignal & kScaleSignalDisableGlobalScaling32) &&
+ (itemEntry->scaleX == itemEntry->scaleY))
+ applyGlobalScaling(itemEntry, it->planeRect, view->getHeight(itemEntry->loopNo, itemEntry->celNo));
+
if ((itemEntry->scaleX == 128) && (itemEntry->scaleY == 128))
view->getCelRect(itemEntry->loopNo, itemEntry->celNo,
itemEntry->x, itemEntry->y, itemEntry->z, itemEntry->celRect);
@@ -711,14 +762,6 @@ void GfxFrameout::kernelFrameout() {
// Translate back to actual coordinate within scrollable plane
nsRect.translate(it->planeOffsetX, it->planeOffsetY);
- if (view && view->isSci2Hires()) {
- view->adjustBackUpscaledCoordinates(nsRect.top, nsRect.left);
- view->adjustBackUpscaledCoordinates(nsRect.bottom, nsRect.right);
- } else if (getSciVersion() >= SCI_VERSION_2_1) {
- _coordAdjuster->fromDisplayToScript(nsRect.top, nsRect.left);
- _coordAdjuster->fromDisplayToScript(nsRect.bottom, nsRect.right);
- }
-
if (g_sci->getGameId() == GID_PHANTASMAGORIA2) {
// HACK: Some (?) objects in Phantasmagoria 2 have no NS rect. Skip them for now.
// TODO: Remove once we figure out how Phantasmagoria 2 draws objects on screen.
@@ -726,7 +769,15 @@ void GfxFrameout::kernelFrameout() {
continue;
}
- g_sci->_gfxCompare->setNSRect(itemEntry->object, nsRect);
+ if (view && view->isSci2Hires()) {
+ view->adjustBackUpscaledCoordinates(nsRect.top, nsRect.left);
+ view->adjustBackUpscaledCoordinates(nsRect.bottom, nsRect.right);
+ g_sci->_gfxCompare->setNSRect(itemEntry->object, nsRect);
+ } else if (getSciVersion() >= SCI_VERSION_2_1 && _resMan->detectHires()) {
+ _coordAdjuster->fromDisplayToScript(nsRect.top, nsRect.left);
+ _coordAdjuster->fromDisplayToScript(nsRect.bottom, nsRect.right);
+ g_sci->_gfxCompare->setNSRect(itemEntry->object, nsRect);
+ }
}
// Don't attempt to draw sprites that are outside the visible
diff --git a/engines/sci/graphics/frameout.h b/engines/sci/graphics/frameout.h
index 5fd2824224..5ef770486f 100644
--- a/engines/sci/graphics/frameout.h
+++ b/engines/sci/graphics/frameout.h
@@ -97,11 +97,18 @@ struct ScrollTextEntry {
typedef Common::Array<ScrollTextEntry> ScrollTextList;
+enum ViewScaleSignals32 {
+ kScaleSignalDoScaling32 = 0x0001, // enables scaling when drawing that cel (involves scaleX and scaleY)
+ kScaleSignalUnk1 = 0x0002, // unknown
+ kScaleSignalDisableGlobalScaling32 = 0x0004
+};
+
class GfxCache;
class GfxCoordAdjuster32;
class GfxPaint32;
class GfxPalette;
class GfxScreen;
+
/**
* Frameout class, kFrameout and relevant functions for SCI32 games
*/
@@ -113,6 +120,7 @@ public:
void kernelAddPlane(reg_t object);
void kernelUpdatePlane(reg_t object);
void kernelDeletePlane(reg_t object);
+ void applyGlobalScaling(FrameoutEntry *itemEntry, Common::Rect planeRect, int16 celHeight);
void kernelAddScreenItem(reg_t object);
void kernelUpdateScreenItem(reg_t object);
void kernelDeleteScreenItem(reg_t object);
diff --git a/engines/sci/graphics/menu.cpp b/engines/sci/graphics/menu.cpp
index bfecc296a2..e5b734782c 100644
--- a/engines/sci/graphics/menu.cpp
+++ b/engines/sci/graphics/menu.cpp
@@ -314,7 +314,7 @@ void GfxMenu::kernelSetAttribute(uint16 menuId, uint16 itemId, uint16 attributeI
reg_t GfxMenu::kernelGetAttribute(uint16 menuId, uint16 itemId, uint16 attributeId) {
GuiMenuItemEntry *itemEntry = findItem(menuId, itemId);
if (!itemEntry)
- error("Tried to getAttribute() on non-existant menu-item %d:%d", menuId, itemId);
+ error("Tried to getAttribute() on non-existent menu-item %d:%d", menuId, itemId);
switch (attributeId) {
case SCI_MENU_ATTRIBUTE_ENABLED:
if (itemEntry->enabled)
diff --git a/engines/sci/graphics/palette.cpp b/engines/sci/graphics/palette.cpp
index 53d69cdcca..9b6eff6edc 100644
--- a/engines/sci/graphics/palette.cpp
+++ b/engines/sci/graphics/palette.cpp
@@ -386,9 +386,9 @@ void GfxPalette::setRemappingPercentGray(byte color, byte percent) {
// Note: This is not what the original does, but the results are the same visually
for (int i = 0; i < 256; i++) {
- byte rComponent = _sysPalette.colors[i].r * _remappingPercentToSet * 0.30 / 100;
- byte gComponent = _sysPalette.colors[i].g * _remappingPercentToSet * 0.59 / 100;
- byte bComponent = _sysPalette.colors[i].b * _remappingPercentToSet * 0.11 / 100;
+ byte rComponent = (byte)(_sysPalette.colors[i].r * _remappingPercentToSet * 0.30 / 100);
+ byte gComponent = (byte)(_sysPalette.colors[i].g * _remappingPercentToSet * 0.59 / 100);
+ byte bComponent = (byte)(_sysPalette.colors[i].b * _remappingPercentToSet * 0.11 / 100);
byte luminosity = rComponent + gComponent + bComponent;
_remappingByPercent[i] = kernelFindColor(luminosity, luminosity, luminosity);
}
@@ -722,11 +722,6 @@ void GfxPalette::kernelRestore(reg_t memoryHandle) {
}
void GfxPalette::kernelAssertPalette(GuiResourceId resourceId) {
- // Sometimes invalid viewIds are asked for, ignore those (e.g. qfg1vga)
- //if (!_resMan->testResource(ResourceId(kResourceTypeView, resourceId)))
- // return;
- // maybe we took the wrong parameter before, if this causes invalid view again, enable to commented out code again
-
GfxView *view = g_sci->_gfxCache->getView(resourceId);
Palette *viewPalette = view->getPalette();
if (viewPalette) {
diff --git a/engines/sci/graphics/portrait.cpp b/engines/sci/graphics/portrait.cpp
index 8cd8cdb033..d5227126e2 100644
--- a/engines/sci/graphics/portrait.cpp
+++ b/engines/sci/graphics/portrait.cpp
@@ -193,7 +193,7 @@ void Portrait::doit(Common::Point position, uint16 resourceId, uint16 noun, uint
drawBitmap(syncCue);
bitsShow();
} else {
- warning("kPortrait: sync information tried to draw non-existant %d", syncCue);
+ warning("kPortrait: sync information tried to draw non-existent %d", syncCue);
}
}
}
diff --git a/engines/sci/graphics/ports.cpp b/engines/sci/graphics/ports.cpp
index 8acdeed763..527e2ae973 100644
--- a/engines/sci/graphics/ports.cpp
+++ b/engines/sci/graphics/ports.cpp
@@ -537,7 +537,7 @@ void GfxPorts::freeWindow(Window *pWnd) {
if (!pWnd->hSaved1.isNull())
_segMan->freeHunkEntry(pWnd->hSaved1);
if (!pWnd->hSaved2.isNull())
- _segMan->freeHunkEntry(pWnd->hSaved1);
+ _segMan->freeHunkEntry(pWnd->hSaved2);
_windowsById[pWnd->id] = NULL;
delete pWnd;
}
diff --git a/engines/sci/parser/vocabulary.cpp b/engines/sci/parser/vocabulary.cpp
index e56158ecc1..f5b79d6c5e 100644
--- a/engines/sci/parser/vocabulary.cpp
+++ b/engines/sci/parser/vocabulary.cpp
@@ -494,8 +494,6 @@ void Vocabulary::debugDecipherSaidBlock(const byte *addr) {
case 0xf9:
debugN(">");
break;
- case 0xff:
- break;
}
}
} while (nextItem != 0xff);
diff --git a/engines/sci/resource.cpp b/engines/sci/resource.cpp
index c9c0d25bc6..1da0c5dccc 100644
--- a/engines/sci/resource.cpp
+++ b/engines/sci/resource.cpp
@@ -1152,7 +1152,6 @@ ResVersion ResourceManager::detectMapVersion() {
}
break;
} else if (rsrc->getSourceType() == kSourceMacResourceFork) {
- delete fileStream;
return kResVersionSci11Mac;
}
}
diff --git a/engines/sci/sci.cpp b/engines/sci/sci.cpp
index 15b18ce8e6..1f5c354d1f 100644
--- a/engines/sci/sci.cpp
+++ b/engines/sci/sci.cpp
@@ -699,9 +699,11 @@ void SciEngine::runGame() {
patchGameSaveRestore();
setLauncherLanguage();
_gamestate->gameIsRestarting = GAMEISRESTARTING_RESTART;
+ _gamestate->_throttleLastTime = 0;
if (_gfxMenu)
_gfxMenu->reset();
_gamestate->abortScriptProcessing = kAbortNone;
+ _gamestate->_syncedAudioOptions = false;
} else if (_gamestate->abortScriptProcessing == kAbortLoadGame) {
_gamestate->abortScriptProcessing = kAbortNone;
_gamestate->_executionStack.clear();
@@ -713,6 +715,7 @@ void SciEngine::runGame() {
syncSoundSettings();
syncIngameAudioOptions();
+ // Games do not set their audio settings when loading
} else {
break; // exit loop
}
diff --git a/engines/sci/sci.h b/engines/sci/sci.h
index 3441e26c01..3b9844b326 100644
--- a/engines/sci/sci.h
+++ b/engines/sci/sci.h
@@ -110,6 +110,7 @@ enum SciGameId {
GID_ASTROCHICKEN,
GID_CAMELOT,
GID_CASTLEBRAIN,
+ GID_CHEST,
GID_CHRISTMAS1988,
GID_CHRISTMAS1990,
GID_CHRISTMAS1992,
diff --git a/engines/sci/sound/drivers/midi.cpp b/engines/sci/sound/drivers/midi.cpp
index 37eb37b292..a31d5f9a81 100644
--- a/engines/sci/sound/drivers/midi.cpp
+++ b/engines/sci/sound/drivers/midi.cpp
@@ -234,15 +234,9 @@ void MidiPlayer_Midi::controlChange(int channel, int control, int value) {
}
break;
case 0x0a:
- if (_channels[channel].pan == value)
- return;
-
_channels[channel].pan = value;
break;
case 0x40:
- if (_channels[channel].hold == value)
- return;
-
_channels[channel].hold = value;
break;
case 0x4b: // voice mapping
@@ -250,9 +244,6 @@ void MidiPlayer_Midi::controlChange(int channel, int control, int value) {
case 0x4e: // velocity
break;
case 0x7b:
- if (!_channels[channel].playing)
- return;
-
_channels[channel].playing = false;
default:
break;
@@ -922,7 +913,8 @@ int MidiPlayer_Midi::open(ResourceManager *resMan) {
if (res) {
if (isMt32GmPatch(res->data, res->size)) {
readMt32GmPatch(res->data, res->size);
- strncpy((char *)_goodbyeMsg, " ScummVM ", 20);
+ // Note that _goodbyeMsg is not zero-terminated
+ memcpy(_goodbyeMsg, " ScummVM ", 20);
} else {
readMt32Patch(res->data, res->size);
}
diff --git a/engines/sci/sound/midiparser_sci.cpp b/engines/sci/sound/midiparser_sci.cpp
index 4e54797960..9546b1503f 100644
--- a/engines/sci/sound/midiparser_sci.cpp
+++ b/engines/sci/sound/midiparser_sci.cpp
@@ -251,15 +251,14 @@ byte *MidiParser_SCI::midiFilterChannels(int channelMask) {
if (curChannel != 0xF)
containsMidiData = true;
- if (command != kEndOfTrack) {
- // Write delta
- while (delta > 240) {
- *outData++ = 0xF8;
- delta -= 240;
- }
- *outData++ = (byte)delta;
- delta = 0;
+ // Write delta
+ while (delta > 240) {
+ *outData++ = 0xF8;
+ delta -= 240;
}
+ *outData++ = (byte)delta;
+ delta = 0;
+
// Write command
switch (command) {
case 0xF0: // sysEx
@@ -302,7 +301,7 @@ byte *MidiParser_SCI::midiFilterChannels(int channelMask) {
}
// Insert stop event
- *outData++ = 0; // Delta
+ // (Delta is already output above)
*outData++ = 0xFF; // Meta event
*outData++ = 0x2F; // End of track (EOT)
*outData++ = 0x00;
@@ -532,8 +531,11 @@ void MidiParser_SCI::parseNextEvent(EventInfo &info) {
// Check if the hold ID marker is the same as the hold ID
// marker set for that song by cmdSetSoundHold.
// If it is, loop back, but don't stop notes when jumping.
- if (info.basic.param2 == _pSnd->hold)
+ if (info.basic.param2 == _pSnd->hold) {
+ uint32 extraDelta = info.delta;
jumpToTick(_loopTick, false, false);
+ _nextEvent.delta += extraDelta;
+ }
break;
case kUpdateCue:
_dataincAdd = true;
@@ -636,7 +638,9 @@ void MidiParser_SCI::parseNextEvent(EventInfo &info) {
// treats this case as an infinite loop (bug #3311911).
if (_pSnd->loop || _pSnd->hold > 0) {
// We need to play it again...
+ uint32 extraDelta = info.delta;
jumpToTick(_loopTick);
+ _nextEvent.delta += extraDelta;
} else {
_pSnd->status = kSoundStopped;
_pSnd->setSignal(SIGNAL_OFFSET);
diff --git a/engines/sci/sound/music.cpp b/engines/sci/sound/music.cpp
index a8a65d2aa4..913ba32cba 100644
--- a/engines/sci/sound/music.cpp
+++ b/engines/sci/sound/music.cpp
@@ -485,6 +485,8 @@ void SciMusic::soundPlay(MusicEntry *pSnd) {
// Stop any in progress music fading, as that will reset the
// volume of the sound channels that the faded song occupies..
// Fixes bug #3266480 and partially fixes bug #3041738.
+ // CHECKME: Is this the right thing to do? Are these
+ // overlapping channels not a deeper underlying problem?
for (uint i = 0; i < playListCount; i++) {
// Is another MIDI song being faded down? If yes, stop it
// immediately instead
@@ -495,6 +497,7 @@ void SciMusic::soundPlay(MusicEntry *pSnd) {
_playList[i]->pMidiParser->stop();
freeChannels(_playList[i]);
_playList[i]->fadeStep = 0;
+ _playList[i]->fadeCompleted = true;
}
}
}
diff --git a/engines/sci/sound/soundcmd.cpp b/engines/sci/sound/soundcmd.cpp
index 5d32f40f18..873a16cc73 100644
--- a/engines/sci/sound/soundcmd.cpp
+++ b/engines/sci/sound/soundcmd.cpp
@@ -116,7 +116,7 @@ void SoundCommandParser::processInitSound(reg_t obj) {
newSound->resourceId = resourceId;
newSound->soundObj = obj;
newSound->loop = readSelectorValue(_segMan, obj, SELECTOR(loop));
- newSound->priority = readSelectorValue(_segMan, obj, SELECTOR(pri)) & 0xFF;
+ newSound->priority = readSelectorValue(_segMan, obj, SELECTOR(priority)) & 0xFF;
if (_soundVersion >= SCI_VERSION_1_EARLY)
newSound->volume = CLIP<int>(readSelectorValue(_segMan, obj, SELECTOR(vol)), 0, MUSIC_VOLUME_MAX);
newSound->reverb = -1; // initialize to SCI invalid, it'll be set correctly in soundInitSnd() below
@@ -177,6 +177,18 @@ void SoundCommandParser::processPlaySound(reg_t obj) {
writeSelectorValue(_segMan, obj, SELECTOR(state), kSoundPlaying);
}
+ // WORKAROUND: Songs 1840, 1843 and 1849 in the Windows version of KQ5CD
+ // are all missing their channel 15 (all played during its ending
+ // sequences, when fighting with Mordack). This makes the game scripts
+ // wait indefinitely for the missing signals in these songs. In the
+ // original interpreter, this bug manifests as an "Out of heap" error. We
+ // signal the game scripts to stop waiting forever by setting the song's
+ // dataInc selector to something other than 0. This causes Mordack's
+ // appearing animation to occur a bit earlier than expected, but at least
+ // the game doesn't freeze at that point. Fixes bug #3605269.
+ if (g_sci->getGameId() == GID_KQ5 && (resourceId == 1840 || resourceId == 1843 || resourceId == 1849))
+ musicSlot->dataInc = 1;
+
musicSlot->loop = readSelectorValue(_segMan, obj, SELECTOR(loop));
musicSlot->priority = readSelectorValue(_segMan, obj, SELECTOR(priority));
// Reset hold when starting a new song. kDoSoundSetHold is always called after
@@ -189,6 +201,10 @@ void SoundCommandParser::processPlaySound(reg_t obj) {
resourceId, musicSlot->loop, musicSlot->priority, musicSlot->volume);
_music->soundPlay(musicSlot);
+
+ // Reset any left-over signals
+ musicSlot->signal = 0;
+ musicSlot->fadeStep = 0;
}
reg_t SoundCommandParser::kDoSoundRestore(int argc, reg_t *argv, reg_t acc) {
@@ -255,7 +271,7 @@ void SoundCommandParser::processStopSound(reg_t obj, bool sampleFinishedPlaying)
writeSelectorValue(_segMan, obj, SELECTOR(signal), SIGNAL_OFFSET);
musicSlot->dataInc = 0;
- musicSlot->signal = 0;
+ musicSlot->signal = SIGNAL_OFFSET;
_music->soundStop(musicSlot);
}
@@ -340,6 +356,12 @@ reg_t SoundCommandParser::kDoSoundMasterVolume(int argc, reg_t *argv, reg_t acc)
reg_t SoundCommandParser::kDoSoundFade(int argc, reg_t *argv, reg_t acc) {
reg_t obj = argv[0];
+ // The object can be null in several SCI0 games (e.g. Camelot, KQ1, KQ4, MUMG).
+ // Check bugs #3035149, #3036942 and #3578335.
+ // In this case, we just ignore the call.
+ if (obj.isNull() && argc == 1)
+ return acc;
+
MusicEntry *musicSlot = _music->getSlot(obj);
if (!musicSlot) {
debugC(kDebugLevelSound, "kDoSound(fade): Slot not found (%04x:%04x)", PRINT_REG(obj));
@@ -367,44 +389,27 @@ reg_t SoundCommandParser::kDoSoundFade(int argc, reg_t *argv, reg_t acc) {
case 4: // SCI01+
case 5: // SCI1+ (SCI1 late sound scheme), with fade and continue
- if (argc == 5) {
- // TODO: We currently treat this argument as a boolean, but may
- // have to handle different non-zero values differently. (e.g.,
- // some KQ6 scripts pass 3 here).
- // There is a script bug in KQ6, room 460 (the room with the flying
- // books). An object is passed here, which should not be treated as
- // a true flag. Fixes bugs #3555404 and #3291115.
- musicSlot->stopAfterFading = (argv[4].isNumber() && argv[4].toUint16() != 0);
- } else {
- musicSlot->stopAfterFading = false;
- }
-
musicSlot->fadeTo = CLIP<uint16>(argv[1].toUint16(), 0, MUSIC_VOLUME_MAX);
// Check if the song is already at the requested volume. If it is, don't
// perform any fading. Happens for example during the intro of Longbow.
- if (musicSlot->fadeTo != musicSlot->volume) {
- // sometimes we get objects in that position, fix it up (ffs. workarounds)
- if (!argv[1].getSegment())
- musicSlot->fadeStep = volume > musicSlot->fadeTo ? -argv[3].toUint16() : argv[3].toUint16();
- else
- musicSlot->fadeStep = volume > musicSlot->fadeTo ? -5 : 5;
- musicSlot->fadeTickerStep = argv[2].toUint16() * 16667 / _music->soundGetTempo();
- } else {
- // Stop the music, if requested. Fixes bug #3555404.
- if (musicSlot->stopAfterFading)
- processStopSound(obj, false);
- }
+ if (musicSlot->fadeTo == musicSlot->volume)
+ return acc;
+ // sometimes we get objects in that position, fix it up (ffs. workarounds)
+ if (!argv[1].getSegment())
+ musicSlot->fadeStep = volume > musicSlot->fadeTo ? -argv[3].toUint16() : argv[3].toUint16();
+ else
+ musicSlot->fadeStep = volume > musicSlot->fadeTo ? -5 : 5;
+ musicSlot->fadeTickerStep = argv[2].toUint16() * 16667 / _music->soundGetTempo();
musicSlot->fadeTicker = 0;
- // WORKAROUND/HACK: In the labyrinth in KQ6, when falling in the pit and
- // lighting the lantern, the game scripts perform a fade in of the game
- // music, but set it to stop after fading. Remove that flag here. This is
- // marked as both a workaround and a hack because this issue could be a
- // problem with our fading code and an incorrect handling of that
- // parameter, or a script bug in that scene. Fixes bug #3267956.
- if (g_sci->getGameId() == GID_KQ6 && g_sci->getEngineState()->currentRoomNumber() == 406 &&
- musicSlot->resourceId == 400)
+ // argv[4] is a boolean. Scripts sometimes pass strange values,
+ // but SSCI only checks for zero/non-zero. (Verified in KQ6.)
+ // KQ6 room 460 even passes an object, but treating this as 'true'
+ // seems fine in that case.
+ if (argc == 5)
+ musicSlot->stopAfterFading = !argv[4].isNull();
+ else
musicSlot->stopAfterFading = false;
break;
@@ -435,7 +440,7 @@ reg_t SoundCommandParser::kDoSoundUpdate(int argc, reg_t *argv, reg_t acc) {
int16 objVol = CLIP<int>(readSelectorValue(_segMan, obj, SELECTOR(vol)), 0, 255);
if (objVol != musicSlot->volume)
_music->soundSetVolume(musicSlot, objVol);
- uint32 objPrio = readSelectorValue(_segMan, obj, SELECTOR(pri));
+ uint32 objPrio = readSelectorValue(_segMan, obj, SELECTOR(priority));
if (objPrio != musicSlot->priority)
_music->soundSetPriority(musicSlot, objPrio);
return acc;
@@ -495,12 +500,15 @@ void SoundCommandParser::processUpdateCues(reg_t obj) {
processStopSound(obj, false);
}
} else {
- // Slot actually has no data (which would mean that a sound-resource w/
- // unsupported data is used.
- // (example lsl5 - sound resource 744 - it's Roland exclusive
- writeSelectorValue(_segMan, obj, SELECTOR(signal), SIGNAL_OFFSET);
- // If we don't set signal here, at least the switch to the mud wrestling
- // room in lsl5 will not work.
+ // The sound slot has no data for the currently selected sound card.
+ // An example can be found during the mud wrestling scene in LSL5, room
+ // 730: sound 744 (a splat sound heard when Lana Luscious jumps in the
+ // mud) only contains MIDI channel data. If a non-MIDI sound card is
+ // selected (like Adlib), then the scene freezes. We also need to stop
+ // the sound at this point, otherwise KQ6 Mac breaks because the rest
+ // of the object needs to be reset to avoid a continuous stream of
+ // sound cues.
+ processStopSound(obj, true); // this also sets the signal selector
}
if (musicSlot->fadeCompleted) {
@@ -509,6 +517,8 @@ void SoundCommandParser::processUpdateCues(reg_t obj) {
// fireworks).
// It is also needed in other games, e.g. LSL6 when talking to the
// receptionist (bug #3192166).
+ // CHECKME: At least kq5cd/win and kq6 set signal to 0xFE here, but
+ // kq5cd/dos does not set signal at all. Needs more investigation.
writeSelectorValue(_segMan, obj, SELECTOR(signal), SIGNAL_OFFSET);
if (_soundVersion <= SCI_VERSION_0_LATE) {
processStopSound(obj, false);