aboutsummaryrefslogtreecommitdiff
path: root/engines/sci
diff options
context:
space:
mode:
Diffstat (limited to 'engines/sci')
-rw-r--r--engines/sci/console.cpp18
-rw-r--r--engines/sci/detection_tables.h73
-rw-r--r--engines/sci/engine/file.cpp4
-rw-r--r--engines/sci/engine/kernel.h1
-rw-r--r--engines/sci/engine/kernel_tables.h7
-rw-r--r--engines/sci/engine/kfile.cpp17
-rw-r--r--engines/sci/engine/kgraphics.cpp72
-rw-r--r--engines/sci/engine/kgraphics32.cpp86
-rw-r--r--engines/sci/engine/kmath.cpp25
-rw-r--r--engines/sci/engine/kpathing.cpp636
-rw-r--r--engines/sci/engine/ksound.cpp11
-rw-r--r--engines/sci/engine/kstring.cpp1
-rw-r--r--engines/sci/engine/kvideo.cpp22
-rw-r--r--engines/sci/engine/script.cpp2
-rw-r--r--engines/sci/engine/script_patches.cpp44
-rw-r--r--engines/sci/engine/scriptdebug.cpp5
-rw-r--r--engines/sci/engine/seg_manager.cpp2
-rw-r--r--engines/sci/engine/vm.cpp3
-rw-r--r--engines/sci/engine/workarounds.cpp7
-rw-r--r--engines/sci/graphics/controls32.cpp6
-rw-r--r--engines/sci/graphics/font.cpp2
-rw-r--r--engines/sci/graphics/frameout.cpp74
-rw-r--r--engines/sci/graphics/frameout.h4
-rw-r--r--engines/sci/graphics/paint16.cpp4
-rw-r--r--engines/sci/graphics/palette.cpp92
-rw-r--r--engines/sci/graphics/palette.h21
-rw-r--r--engines/sci/graphics/ports.cpp41
-rw-r--r--engines/sci/graphics/screen.cpp55
-rw-r--r--engines/sci/graphics/screen.h1
-rw-r--r--engines/sci/graphics/view.cpp17
-rw-r--r--engines/sci/sci.cpp3
-rw-r--r--engines/sci/sci.h20
-rw-r--r--engines/sci/sound/audio.cpp3
-rw-r--r--engines/sci/sound/drivers/adlib.cpp3
-rw-r--r--engines/sci/sound/drivers/fmtowns.cpp12
-rw-r--r--engines/sci/sound/midiparser_sci.cpp80
-rw-r--r--engines/sci/sound/midiparser_sci.h2
-rw-r--r--engines/sci/sound/music.cpp8
-rw-r--r--engines/sci/sound/soundcmd.cpp41
-rw-r--r--engines/sci/video/robot_decoder.cpp373
-rw-r--r--engines/sci/video/robot_decoder.h125
-rw-r--r--engines/sci/video/seq_decoder.cpp61
-rw-r--r--engines/sci/video/seq_decoder.h67
43 files changed, 1559 insertions, 592 deletions
diff --git a/engines/sci/console.cpp b/engines/sci/console.cpp
index 564bbbbd79..5ae8245e5a 100644
--- a/engines/sci/console.cpp
+++ b/engines/sci/console.cpp
@@ -250,20 +250,18 @@ void Console::postEnter() {
#endif
if (_videoFile.hasSuffix(".seq")) {
- SeqDecoder *seqDecoder = new SeqDecoder();
- seqDecoder->setFrameDelay(_videoFrameDelay);
- videoDecoder = seqDecoder;
+ videoDecoder = new SEQDecoder(_videoFrameDelay);
#ifdef ENABLE_SCI32
} else if (_videoFile.hasSuffix(".vmd")) {
- videoDecoder = new Video::VMDDecoder(g_system->getMixer());
+ videoDecoder = new Video::AdvancedVMDDecoder();
} else if (_videoFile.hasSuffix(".rbt")) {
- videoDecoder = new RobotDecoder(g_system->getMixer(), _engine->getPlatform() == Common::kPlatformMacintosh);
+ videoDecoder = new RobotDecoder(_engine->getPlatform() == Common::kPlatformMacintosh);
} else if (_videoFile.hasSuffix(".duk")) {
duckMode = true;
- videoDecoder = new Video::AviDecoder(g_system->getMixer());
+ videoDecoder = new Video::AVIDecoder();
#endif
} else if (_videoFile.hasSuffix(".avi")) {
- videoDecoder = new Video::AviDecoder(g_system->getMixer());
+ videoDecoder = new Video::AVIDecoder();
} else {
warning("Unrecognized video type");
}
@@ -2884,17 +2882,17 @@ bool Console::cmdDisassemble(int argc, const char **argv) {
reg_t addr = NULL_REG;
if (!obj) {
- DebugPrintf("Not an object.");
+ DebugPrintf("Not an object.\n");
return true;
}
if (selectorId < 0) {
- DebugPrintf("Not a valid selector name.");
+ DebugPrintf("Not a valid selector name.\n");
return true;
}
if (lookupSelector(_engine->_gamestate->_segMan, objAddr, selectorId, NULL, &addr) != kSelectorMethod) {
- DebugPrintf("Not a method.");
+ DebugPrintf("Not a method.\n");
return true;
}
diff --git a/engines/sci/detection_tables.h b/engines/sci/detection_tables.h
index 268914edba..b978f40aba 100644
--- a/engines/sci/detection_tables.h
+++ b/engines/sci/detection_tables.h
@@ -1326,6 +1326,21 @@ static const struct ADGameDescription SciGameDescriptions[] = {
AD_LISTEND},
Common::EN_ANY, Common::kPlatformPC, 0, GUIO5(GUIO_NOSPEECH, GAMEOPTION_EGA_UNDITHER, GAMEOPTION_PREFER_DIGITAL_SFX, GAMEOPTION_ORIGINAL_SAVELOAD, GAMEOPTION_FB01_MIDI) },
+ // King's Quest 5 DOS Spanish Floppy 0.000.062 VGA (5 x 3.5" disks)
+ // Supplied by dianiu in bug report #3555646
+ {"kq5", "", {
+ {"resource.map", 0, "c09896a2a30c9b002c5cbbc62f5a5c3a", 8169},
+ {"resource.000", 0, "1f1d03aead44da46362ff40c0074a3ec", 335871},
+ {"resource.001", 0, "d1803ad904127ae091edb274ee8c047f", 1180637},
+ {"resource.002", 0, "d9cd5972016f650cc31fb7c2a2b0953a", 1102207},
+ {"resource.003", 0, "829c8caeff793f3cfcea2cb01aaa4150", 965586},
+ {"resource.004", 0, "0bd9e570ee04b025e43d3075998fae5b", 1117965},
+ {"resource.005", 0, "4aaa2e9a69089b9afbaaccbbf2c4e647", 1202936},
+ {"resource.006", 0, "65b520e60c4217e6a6572d9edf77193b", 1141985},
+ {"resource.007", 0, "f42b0100f0a1c30806814f8648b6bc28", 1145583},
+ AD_LISTEND},
+ Common::ES_ESP, Common::kPlatformPC, ADGF_ADDENGLISH, GUIO4(GUIO_NOSPEECH, GAMEOPTION_PREFER_DIGITAL_SFX, GAMEOPTION_ORIGINAL_SAVELOAD, GAMEOPTION_FB01_MIDI) },
+
// King's Quest 5 - German DOS Floppy (supplied by markcoolio in bug report #2727101, also includes english language)
// SCI interpreter version 1.000.060
{"kq5", "", {
@@ -2681,6 +2696,13 @@ static const struct ADGameDescription SciGameDescriptions[] = {
AD_LISTEND},
Common::DE_DEU, Common::kPlatformPC, ADGF_ADDENGLISH, GUIO4(GUIO_NOSPEECH, GAMEOPTION_PREFER_DIGITAL_SFX, GAMEOPTION_ORIGINAL_SAVELOAD, GAMEOPTION_FB01_MIDI) },
+ // Police Quest 3 - Spanish DOS v1.000 - Supplied by dianiu in bug report #3555647
+ {"pq3", "", {
+ {"resource.map", 0, "ffa0b4631c4e36d69631256d19ba29e7", 5421},
+ {"resource.000", 0, "5ee460af3d70c06a745cc482b6c783ba", 5410263},
+ AD_LISTEND},
+ Common::ES_ESP, Common::kPlatformPC, ADGF_ADDENGLISH, GUIO4(GUIO_NOSPEECH, GAMEOPTION_PREFER_DIGITAL_SFX, GAMEOPTION_ORIGINAL_SAVELOAD, GAMEOPTION_FB01_MIDI) },
+
// Police Quest 3 EGA
// Reported by musiclyinspired in bug report #3046573
{"pq3", "", {
@@ -2798,6 +2820,31 @@ static const struct ADGameDescription SciGameDescriptions[] = {
AD_LISTEND},
Common::EN_ANY, Common::kPlatformPC, 0, GUIO5(GUIO_NOSPEECH, GAMEOPTION_EGA_UNDITHER, GAMEOPTION_PREFER_DIGITAL_SFX, GAMEOPTION_ORIGINAL_SAVELOAD, GAMEOPTION_FB01_MIDI) },
+ // Quest for Glory 1 / Hero's Quest - English DOS 3.5" Floppy v1.102 Int#0.000.629 (suppled by digitoxin1 in bug report #3554611)
+ {"qfg1", "", {
+ {"resource.map", 0, "b162dbd4632250d4d83bed46d0783c10", 6396},
+ {"resource.000", 0, "40332d3ebfc70a4b6a6a0443c2763287", 78800},
+ {"resource.001", 0, "a270012fa74445d74c044d1b65a9ff8c", 459835},
+ {"resource.002", 0, "e64004e020fdf1813be52b639b08be89", 635561},
+ {"resource.003", 0, "f0af87c60ec869946da442833aa5afa8", 640502},
+ {"resource.004", 0, "f0af87c60ec869946da442833aa5afa8", 644575},
+ AD_LISTEND},
+ Common::EN_ANY, Common::kPlatformPC, 0, GUIO5(GUIO_NOSPEECH, GAMEOPTION_EGA_UNDITHER, GAMEOPTION_PREFER_DIGITAL_SFX, GAMEOPTION_ORIGINAL_SAVELOAD, GAMEOPTION_FB01_MIDI) },
+
+ // Quest for Glory 1 / Hero's Quest - English DOS 5.25" Floppy v1.102 Int#0.000.629 (suppled by digitoxin1 in bug report #3554611)
+ {"qfg1", "", {
+ {"resource.map", 0, "5772a2c1bfae46f26582582c9901121e", 6858},
+ {"resource.000", 0, "40332d3ebfc70a4b6a6a0443c2763287", 78800},
+ {"resource.001", 0, "a270012fa74445d74c044d1b65a9ff8c", 75090},
+ {"resource.002", 0, "d22695c53835dfdece056d86f26c251e", 271354},
+ {"resource.003", 0, "3cd085e27078f269b3ece5838812ff41", 258084},
+ {"resource.004", 0, "8927c7a04a78f1e76f342db3ccc9d879", 267835},
+ {"resource.005", 0, "13d16cc9b90b51e2c8643cdf52a62957", 268807},
+ {"resource.006", 0, "48b2b3c964dcbeccb68e984e6d4e97db", 278473},
+ {"resource.007", 0, "f0af87c60ec869946da442833aa5afa8", 269237},
+ AD_LISTEND},
+ Common::EN_ANY, Common::kPlatformPC, 0, GUIO5(GUIO_NOSPEECH, GAMEOPTION_EGA_UNDITHER, GAMEOPTION_PREFER_DIGITAL_SFX, GAMEOPTION_ORIGINAL_SAVELOAD, GAMEOPTION_FB01_MIDI) },
+
// Quest for Glory 1 / Hero's Quest - English DOS 5.25" Floppy (supplied by markcoolio in bug report #2723843)
// Executable scanning reports "0.000.566"
{"qfg1", "", {
@@ -2885,17 +2932,6 @@ static const struct ADGameDescription SciGameDescriptions[] = {
AD_LISTEND},
Common::EN_ANY, Common::kPlatformAmiga, 0, GUIO5(GUIO_NOSPEECH, GAMEOPTION_EGA_UNDITHER, GAMEOPTION_PREFER_DIGITAL_SFX, GAMEOPTION_ORIGINAL_SAVELOAD, GAMEOPTION_FB01_MIDI) },
- // Quest for Glory 1 (from abevi, bug report #2612718)
- {"qfg1", "", {
- {"resource.map", 0, "b162dbd4632250d4d83bed46d0783c10", 6396},
- {"resource.000", 0, "40332d3ebfc70a4b6a6a0443c2763287", 78800},
- {"resource.001", 0, "a270012fa74445d74c044d1b65a9ff8c", 459835},
- {"resource.002", 0, "e64004e020fdf1813be52b639b08be89", 635561},
- {"resource.003", 0, "f0af87c60ec869946da442833aa5afa8", 640502},
- {"resource.004", 0, "f0af87c60ec869946da442833aa5afa8", 644575},
- AD_LISTEND},
- Common::EN_ANY, Common::kPlatformAmiga, 0, GUIO5(GUIO_NOSPEECH, GAMEOPTION_EGA_UNDITHER, GAMEOPTION_PREFER_DIGITAL_SFX, GAMEOPTION_ORIGINAL_SAVELOAD, GAMEOPTION_FB01_MIDI) },
-
// Quest for Glory 1 - English DOS
// SCI interpreter version 0.000.629
{"qfg1", "", {
@@ -3002,6 +3038,21 @@ static const struct ADGameDescription SciGameDescriptions[] = {
AD_LISTEND},
Common::EN_ANY, Common::kPlatformPC, 0, GUIO5(GUIO_NOSPEECH, GAMEOPTION_EGA_UNDITHER, GAMEOPTION_PREFER_DIGITAL_SFX, GAMEOPTION_ORIGINAL_SAVELOAD, GAMEOPTION_FB01_MIDI) },
+ // Quest for Glory 2 - English DOS (supplied by digitoxin1 in bug report #3554614)
+ // v1.102 9x3.5" (label: Int#11.20.90)
+ {"qfg2", "", {
+ {"resource.map", 0, "367023314ea33e3156297402f6c1da49", 8166},
+ {"resource.000", 0, "a17e374c4d33b81208c862bc0ffc1a38", 212119},
+ {"resource.001", 0, "e08d7887e30b12008c40f9570447711a", 331995},
+ {"resource.002", 0, "df137dc7869cab07e1149ba2333c815c", 467461},
+ {"resource.003", 0, "df137dc7869cab07e1149ba2333c815c", 502560},
+ {"resource.004", 0, "df137dc7869cab07e1149ba2333c815c", 488532},
+ {"resource.005", 0, "df137dc7869cab07e1149ba2333c815c", 478574},
+ {"resource.006", 0, "b1944bd664ddbd2859cdaa0c4a0d6281", 507489},
+ {"resource.007", 0, "cd2de58e27665d5853530de93fae7cd6", 490794},
+ AD_LISTEND},
+ Common::EN_ANY, Common::kPlatformPC, 0, GUIO5(GUIO_NOSPEECH, GAMEOPTION_EGA_UNDITHER, GAMEOPTION_PREFER_DIGITAL_SFX, GAMEOPTION_ORIGINAL_SAVELOAD, GAMEOPTION_FB01_MIDI) },
+
// Quest for Glory 2 - English DOS Non-Interactive Demo
// Executable scanning reports "1.000.046"
{"qfg2", "Demo", {
diff --git a/engines/sci/engine/file.cpp b/engines/sci/engine/file.cpp
index a0f7ebf4a2..3dc042389e 100644
--- a/engines/sci/engine/file.cpp
+++ b/engines/sci/engine/file.cpp
@@ -95,7 +95,7 @@ reg_t file_open(EngineState *s, const Common::String &filename, int mode, bool u
outFile = saveFileMan->openForSaving(wrappedName, isCompressed);
if (!outFile)
debugC(kDebugLevelFile, " -> file_open(_K_FILE_MODE_CREATE): failed to create file '%s'", englishName.c_str());
-
+
// QfG1 opens the character export file with _K_FILE_MODE_CREATE first,
// closes it immediately and opens it again with this here. Perhaps
// other games use this for read access as well. I guess changing this
@@ -387,7 +387,7 @@ uint32 VirtualIndexFile::read(char *buffer, uint32 size) {
uint32 VirtualIndexFile::write(const char *buffer, uint32 size) {
_changed = true;
uint32 curPos = _ptr - _buffer;
-
+
// Check if the buffer needs to be resized
if (curPos + size >= _bufferSize) {
_bufferSize = curPos + size + 1;
diff --git a/engines/sci/engine/kernel.h b/engines/sci/engine/kernel.h
index 441ea2624f..f985a69ebc 100644
--- a/engines/sci/engine/kernel.h
+++ b/engines/sci/engine/kernel.h
@@ -412,6 +412,7 @@ reg_t kListAt(EngineState *s, int argc, reg_t *argv);
reg_t kString(EngineState *s, int argc, reg_t *argv);
reg_t kMulDiv(EngineState *s, int argc, reg_t *argv);
reg_t kCantBeHere32(EngineState *s, int argc, reg_t *argv);
+reg_t kRemapColors32(EngineState *s, int argc, reg_t *argv);
// "Screen items" in SCI32 are views
reg_t kAddScreenItem(EngineState *s, int argc, reg_t *argv);
reg_t kUpdateScreenItem(EngineState *s, int argc, reg_t *argv);
diff --git a/engines/sci/engine/kernel_tables.h b/engines/sci/engine/kernel_tables.h
index 825ec90fa9..b6b36c47e7 100644
--- a/engines/sci/engine/kernel_tables.h
+++ b/engines/sci/engine/kernel_tables.h
@@ -419,7 +419,10 @@ static SciKernelMapEntry s_kernelMap[] = {
{ MAP_CALL(PriCoord), SIG_EVERYWHERE, "i", NULL, NULL },
{ MAP_CALL(Random), SIG_EVERYWHERE, "i(i)(i)", NULL, NULL },
{ MAP_CALL(ReadNumber), SIG_EVERYWHERE, "r", NULL, NULL },
- { MAP_CALL(RemapColors), SIG_EVERYWHERE, "i(i)(i)(i)(i)(i)", NULL, NULL },
+ { MAP_CALL(RemapColors), SIG_SCI11, SIGFOR_ALL, "i(i)(i)(i)(i)", NULL, NULL },
+#ifdef ENABLE_SCI32
+ { "RemapColors", kRemapColors32, SIG_SCI32, SIGFOR_ALL, "i(i)(i)(i)(i)(i)", NULL, NULL },
+#endif
{ MAP_CALL(ResCheck), SIG_EVERYWHERE, "ii(iiii)", NULL, NULL },
{ MAP_CALL(RespondsTo), SIG_EVERYWHERE, ".i", NULL, NULL },
{ MAP_CALL(RestartGame), SIG_EVERYWHERE, "", NULL, NULL },
@@ -521,7 +524,7 @@ static SciKernelMapEntry s_kernelMap[] = {
{ MAP_CALL(PalCycle), SIG_EVERYWHERE, "i(.*)", NULL, NULL },
// SCI2 Empty functions
-
+
// Debug function used to track resources
{ MAP_EMPTY(ResourceTrack), SIG_EVERYWHERE, "(.*)", NULL, NULL },
// Future TODO: This call is used in the floppy version of QFG4 to add
diff --git a/engines/sci/engine/kfile.cpp b/engines/sci/engine/kfile.cpp
index 786276221c..e977f15c0c 100644
--- a/engines/sci/engine/kfile.cpp
+++ b/engines/sci/engine/kfile.cpp
@@ -197,8 +197,15 @@ reg_t kCD(EngineState *s, int argc, reg_t *argv) {
// TODO: Stub
switch (argv[0].toUint16()) {
case 0:
- // Return whether the contents of disc argv[1] is available.
- return TRUE_REG;
+ if (argc == 1) {
+ // Check if a disc is in the drive
+ return TRUE_REG;
+ } else {
+ // Check if the specified disc is in the drive
+ // and return the current disc number. We just
+ // return the requested disc number.
+ return argv[1];
+ }
case 1:
// Return the current CD number
return make_reg(0, 1);
@@ -333,7 +340,7 @@ reg_t kFileIOClose(EngineState *s, int argc, reg_t *argv) {
if (argv[0] == SIGNAL_REG)
return s->r_acc;
-
+
uint16 handle = argv[0].toUint16();
#ifdef ENABLE_SCI32
@@ -617,7 +624,7 @@ reg_t kFileIOExists(EngineState *s, int argc, reg_t *argv) {
// Special case for KQ6 Mac: The game checks for two video files to see
// if they exist before it plays them. Since we support multiple naming
// schemes for resource fork files, we also need to support that here in
- // case someone has a "HalfDome.bin" file, etc.
+ // case someone has a "HalfDome.bin" file, etc.
if (!exists && g_sci->getGameId() == GID_KQ6 && g_sci->getPlatform() == Common::kPlatformMacintosh &&
(name == "HalfDome" || name == "Kq6Movie"))
exists = Common::MacResManager::exists(name);
@@ -991,7 +998,7 @@ reg_t kMakeSaveFileName(EngineState *s, int argc, reg_t *argv) {
if ((virtualId < SAVEGAMEID_OFFICIALRANGE_START) || (virtualId > SAVEGAMEID_OFFICIALRANGE_END))
error("kMakeSaveFileName: invalid savegame ID specified");
uint saveSlot = virtualId - SAVEGAMEID_OFFICIALRANGE_START;
-
+
Common::Array<SavegameDesc> saves;
listSavegames(saves);
diff --git a/engines/sci/engine/kgraphics.cpp b/engines/sci/engine/kgraphics.cpp
index 6d938b6d22..da377319c0 100644
--- a/engines/sci/engine/kgraphics.cpp
+++ b/engines/sci/engine/kgraphics.cpp
@@ -1221,73 +1221,27 @@ reg_t kShow(EngineState *s, int argc, reg_t *argv) {
return s->r_acc;
}
+// Early variant of the SCI32 kRemapColors kernel function, used in the demo of QFG4
reg_t kRemapColors(EngineState *s, int argc, reg_t *argv) {
uint16 operation = argv[0].toUint16();
switch (operation) {
- case 0: { // Set remapping to base. 0 turns remapping off.
- int16 base = (argc >= 2) ? argv[1].toSint16() : 0;
- if (base != 0) // 0 is the default behavior when changing rooms in GK1, thus silencing the warning
- warning("kRemapColors: Set remapping to base %d", base);
+ case 0: { // remap by percent
+ uint16 percent = argv[1].toUint16();
+ g_sci->_gfxPalette->resetRemapping();
+ g_sci->_gfxPalette->setRemappingPercent(254, percent);
}
break;
- case 1: { // unknown
- // The demo of QFG4 calls this with 1+3 parameters, thus there are differences here
- //int16 unk1 = argv[1].toSint16();
- //int16 unk2 = argv[2].toSint16();
- //int16 unk3 = argv[3].toSint16();
- //uint16 unk4 = argv[4].toUint16();
- //uint16 unk5 = (argc >= 6) ? argv[5].toUint16() : 0;
- kStub(s, argc, argv);
+ case 1: { // remap by range
+ uint16 from = argv[1].toUint16();
+ uint16 to = argv[2].toUint16();
+ uint16 base = argv[3].toUint16();
+ g_sci->_gfxPalette->resetRemapping();
+ g_sci->_gfxPalette->setRemappingRange(254, from, to, base);
}
break;
- case 2: { // remap by percent
- // This adjusts the alpha value of a specific color, and it operates on
- // an RGBA palette. Since we're operating on an RGB palette, we just
- // modify the color intensity instead
- // TODO: From what I understand, palette remapping should be placed
- // separately, so that it can be reset by case 0 above. Thus, we
- // should adjust the functionality of the Palette class accordingly.
- int16 color = argv[1].toSint16();
- if (color >= 10)
- color -= 10;
- uint16 percent = argv[2].toUint16(); // 0 - 100
- if (argc >= 4)
- warning("RemapByPercent called with 4 parameters, unknown parameter is %d", argv[3].toUint16());
- warning("kRemapColors: RemapByPercent color %d by %d percent", color, percent);
- // TODO: It's not correct to set intensity here
- //g_sci->_gfxPalette->kernelSetIntensity(color, 255, percent, false);
- }
- break;
- case 3: { // remap to gray
- // NOTE: This adjusts the alpha value of a specific color, and it operates on
- // an RGBA palette
- int16 color = argv[1].toSint16(); // this is subtracted from a maximum color value, and can be offset by 10
- int16 percent = argv[2].toSint16(); // 0 - 100
- uint16 unk3 = (argc >= 4) ? argv[3].toUint16() : 0;
- warning("kRemapColors: RemapToGray color %d by %d percent (unk3 = %d)", color, percent, unk3);
- }
- break;
- case 4: { // unknown
- //int16 unk1 = argv[1].toSint16();
- //uint16 unk2 = argv[2].toUint16();
- //uint16 unk3 = argv[3].toUint16();
- //uint16 unk4 = (argc >= 5) ? argv[4].toUint16() : 0;
- kStub(s, argc, argv);
- }
- break;
- case 5: { // set color intensity
- // TODO: This isn't right, it should be setting a mapping table instead.
- // For PQ4, we can emulate this with kernelSetIntensity(). In QFG4, this
- // won't do.
- //int16 mapping = argv[1].toSint16();
- uint16 intensity = argv[2].toUint16();
- // HACK for PQ4
- if (g_sci->getGameId() == GID_PQ4)
- g_sci->_gfxPalette->kernelSetIntensity(0, 255, intensity, true);
-
- kStub(s, argc, argv);
- }
+ case 2: // turn remapping off (unused)
+ error("Unused subop kRemapColors(2) has been called");
break;
default:
break;
diff --git a/engines/sci/engine/kgraphics32.cpp b/engines/sci/engine/kgraphics32.cpp
index 16e54a5429..8b3afeef99 100644
--- a/engines/sci/engine/kgraphics32.cpp
+++ b/engines/sci/engine/kgraphics32.cpp
@@ -39,6 +39,7 @@
#include "sci/graphics/cache.h"
#include "sci/graphics/compare.h"
#include "sci/graphics/controls16.h"
+#include "sci/graphics/coordadjuster.h"
#include "sci/graphics/cursor.h"
#include "sci/graphics/palette.h"
#include "sci/graphics/paint16.h"
@@ -171,8 +172,13 @@ reg_t kCreateTextBitmap(EngineState *s, int argc, reg_t *argv) {
debugC(kDebugLevelStrings, "kCreateTextBitmap case 0 (%04x:%04x, %04x:%04x, %04x:%04x)",
PRINT_REG(argv[1]), PRINT_REG(argv[2]), PRINT_REG(argv[3]));
debugC(kDebugLevelStrings, "%s", text.c_str());
- uint16 maxWidth = argv[1].toUint16(); // nsRight - nsLeft + 1
- uint16 maxHeight = argv[2].toUint16(); // nsBottom - nsTop + 1
+ int16 maxWidth = argv[1].toUint16();
+ int16 maxHeight = argv[2].toUint16();
+ g_sci->_gfxCoordAdjuster->fromScriptToDisplay(maxHeight, maxWidth);
+ // These values can be larger than the screen in the SQ6 demo, room 100
+ // TODO: Find out why. For now, don't show any text in that room.
+ if (g_sci->getGameId() == GID_SQ6 && g_sci->isDemo() && s->currentRoomNumber() == 100)
+ return NULL_REG;
return g_sci->_gfxText32->createTextBitmap(object, maxWidth, maxHeight);
}
case 1: {
@@ -363,7 +369,8 @@ reg_t kScrollWindow(EngineState *s, int argc, reg_t *argv) {
case 10: // Where, called by ScrollableWindow::where
// TODO
// argv[2] is an unknown integer
- kStub(s, argc, argv);
+ // 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
@@ -727,6 +734,79 @@ reg_t kPalCycle(EngineState *s, int argc, reg_t *argv) {
return s->r_acc;
}
+reg_t kRemapColors32(EngineState *s, int argc, reg_t *argv) {
+ uint16 operation = argv[0].toUint16();
+
+ switch (operation) {
+ case 0: { // turn remapping off
+ // WORKAROUND: Game scripts in QFG4 erroneously turn remapping off in room
+ // 140 (the character point allocation screen) and never turn it back on,
+ // even if it's clearly used in that screen.
+ if (g_sci->getGameId() == GID_QFG4 && s->currentRoomNumber() == 140)
+ return s->r_acc;
+
+ int16 base = (argc >= 2) ? argv[1].toSint16() : 0;
+ if (base > 0)
+ warning("kRemapColors(0) called with base %d", base);
+ g_sci->_gfxPalette->resetRemapping();
+ }
+ break;
+ case 1: { // remap by range
+ uint16 color = argv[1].toUint16();
+ uint16 from = argv[2].toUint16();
+ uint16 to = argv[3].toUint16();
+ uint16 base = argv[4].toUint16();
+ uint16 unk5 = (argc >= 6) ? argv[5].toUint16() : 0;
+ if (unk5 > 0)
+ warning("kRemapColors(1) called with 6 parameters, unknown parameter is %d", unk5);
+ g_sci->_gfxPalette->setRemappingRange(color, from, to, base);
+ }
+ break;
+ case 2: { // remap by percent
+ uint16 color = argv[1].toUint16();
+ uint16 percent = argv[2].toUint16(); // 0 - 100
+ if (argc >= 4)
+ warning("RemapByPercent called with 4 parameters, unknown parameter is %d", argv[3].toUint16());
+ g_sci->_gfxPalette->setRemappingPercent(color, percent);
+ }
+ break;
+ case 3: { // remap to gray
+ // Example call: QFG4 room 490 (Baba Yaga's hut) - params are color 253, 75% and 0.
+ // In this room, it's used for the cloud before Baba Yaga appears.
+ int16 color = argv[1].toSint16();
+ int16 percent = argv[2].toSint16(); // 0 - 100
+ if (argc >= 4)
+ warning("RemapToGray called with 4 parameters, unknown parameter is %d", argv[3].toUint16());
+ g_sci->_gfxPalette->setRemappingPercentGray(color, percent);
+ }
+ break;
+ case 4: { // remap to percent gray
+ // Example call: QFG4 rooms 530/535 (swamp) - params are 253, 100%, 200
+ int16 color = argv[1].toSint16();
+ int16 percent = argv[2].toSint16(); // 0 - 100
+ // argv[3] is unknown (a number, e.g. 200) - start color, perhaps?
+ if (argc >= 5)
+ warning("RemapToGrayPercent called with 5 parameters, unknown parameter is %d", argv[4].toUint16());
+ g_sci->_gfxPalette->setRemappingPercentGray(color, percent);
+ }
+ break;
+ case 5: { // don't map to range
+ //int16 mapping = argv[1].toSint16();
+ uint16 intensity = argv[2].toUint16();
+ // HACK for PQ4
+ if (g_sci->getGameId() == GID_PQ4)
+ g_sci->_gfxPalette->kernelSetIntensity(0, 255, intensity, true);
+
+ kStub(s, argc, argv);
+ }
+ break;
+ default:
+ break;
+ }
+
+ return s->r_acc;
+}
+
#endif
} // End of namespace Sci
diff --git a/engines/sci/engine/kmath.cpp b/engines/sci/engine/kmath.cpp
index a643fbe37a..4b8fadbb84 100644
--- a/engines/sci/engine/kmath.cpp
+++ b/engines/sci/engine/kmath.cpp
@@ -84,27 +84,10 @@ reg_t kSqrt(EngineState *s, int argc, reg_t *argv) {
* accurate.
*/
uint16 kGetAngleWorker(int16 x1, int16 y1, int16 x2, int16 y2) {
- // TODO: This has been implemented based on behavior observed with a test
- // program created with SCI Studio. However, the return values have subtle
- // differences from the original, which uses custom implementation of atan().
- // The differences in the return values are the cause of bug #3540976
- // and perhaps bug #3037267 as well.
- // The results of this function match the expected results of SCI0, but not
- // SCI1 (hence the bug in Longbow). We need to find the point in history
- // when this function was changed.
-
- // HACK: Return the expected value for Longbow, scene 150 (bug #3540976).
- // This is a temporary solution, till the function returns the expected
- // results.
- if (g_sci->getGameId() == GID_LONGBOW && g_sci->getEngineState()->currentRoomNumber() == 150) {
- if (x1 == 207 && y1 == 88 && x2 == 107 && y2 == 184)
- return 226;
- }
-
-#if 0
- // A simpler atan2-based implementation
- return (int16)(360 - atan2((double)(x1 - x2), (double)(y1 - y2)) * 57.2958) % 360;
-#endif
+ // 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;
int16 xRel = x2 - x1;
int16 yRel = y1 - y2; // y-axis is mirrored.
diff --git a/engines/sci/engine/kpathing.cpp b/engines/sci/engine/kpathing.cpp
index 002ef1ff07..4061795f82 100644
--- a/engines/sci/engine/kpathing.cpp
+++ b/engines/sci/engine/kpathing.cpp
@@ -31,6 +31,9 @@
#include "common/debug-channels.h"
#include "common/list.h"
#include "common/system.h"
+#include "common/math.h"
+
+//#define DEBUG_MERGEPOLY
namespace Sci {
@@ -71,11 +74,25 @@ enum {
struct FloatPoint {
FloatPoint() : x(0), y(0) {}
FloatPoint(float x_, float y_) : x(x_), y(y_) {}
+ FloatPoint(Common::Point p) : x(p.x), y(p.y) {}
Common::Point toPoint() {
return Common::Point((int16)(x + 0.5), (int16)(y + 0.5));
}
+ float operator*(const FloatPoint &p) const {
+ return x*p.x + y*p.y;
+ }
+ FloatPoint operator*(float l) const {
+ return FloatPoint(l*x, l*y);
+ }
+ FloatPoint operator-(const FloatPoint &p) const {
+ return FloatPoint(x-p.x, y-p.y);
+ }
+ float norm() const {
+ return x*x+y*y;
+ }
+
float x, y;
};
@@ -135,15 +152,20 @@ public:
return _head;
}
- void insertHead(Vertex *elm) {
+ void insertAtEnd(Vertex *elm) {
if (_head == NULL) {
elm->_next = elm->_prev = elm;
+ _head = elm;
} else {
elm->_next = _head;
elm->_prev = _head->_prev;
_head->_prev = elm;
elm->_prev->_next = elm;
}
+ }
+
+ void insertHead(Vertex *elm) {
+ insertAtEnd(elm);
_head = elm;
}
@@ -788,10 +810,10 @@ int PathfindingState::findNearPoint(const Common::Point &p, Polygon *polygon, Co
* including the vertices themselves)
* Parameters: (const Common::Point &) a, b: The line segment (a, b)
* (Vertex *) vertex: The first vertex of the edge
- * Returns : (int) FP_OK on success, PF_ERROR otherwise
+ * Returns : (int) PF_OK on success, PF_ERROR otherwise
* (FloatPoint) *ret: The intersection point
*/
-static int intersection(const Common::Point &a, const Common::Point &b, Vertex *vertex, FloatPoint *ret) {
+static int intersection(const Common::Point &a, const Common::Point &b, const Vertex *vertex, FloatPoint *ret) {
// Parameters of parametric equations
float s, t;
// Numerator and denominator of equations
@@ -1783,39 +1805,619 @@ reg_t kIntersections(EngineState *s, int argc, reg_t *argv) {
}
}
+// ==========================================================================
+// kMergePoly utility functions
+
+// Compute square of the distance of p to the segment a-b.
+static float pointSegDistance(const Common::Point &a, const Common::Point &b,
+ const Common::Point &p) {
+ FloatPoint ba(b-a);
+ FloatPoint pa(p-a);
+ FloatPoint bp(b-p);
+
+ // Check if the projection of p on the line a-b lies between a and b
+ if (ba*pa >= 0.0f && ba*bp >= 0.0f) {
+ // If yes, return the (squared) distance of p to the line a-b:
+ // translate a to origin, project p and subtract
+ float linedist = (ba*((ba*pa)/(ba*ba)) - pa).norm();
+
+ return linedist;
+ } else {
+ // If no, return the (squared) distance to either a or b, whichever
+ // is closest.
+
+ // distance to a:
+ float adist = pa.norm();
+ // distance to b:
+ float bdist = FloatPoint(p-b).norm();
+
+ return MIN(adist, bdist);
+ }
+}
+
+// find intersection between edges of two polygons.
+// endpoints count, except v2->_next
+static bool segSegIntersect(const Vertex *v1, const Vertex *v2, Common::Point &intp) {
+ const Common::Point &a = v1->v;
+ const Common::Point &b = v1->_next->v;
+ const Common::Point &c = v2->v;
+ const Common::Point &d = v2->_next->v;
+
+ // First handle the endpoint cases manually
+
+ if (collinear(a, b, c) && collinear(a, b, d))
+ return false;
+
+ if (collinear(a, b, c)) {
+ // a, b, c collinear
+ // return true/c if c is between a and b
+ intp = c;
+ if (a.x != b.x) {
+ if ((a.x <= c.x && c.x <= b.x) || (b.x <= c.x && c.x <= a.x))
+ return true;
+ } else {
+ if ((a.y <= c.y && c.y <= b.y) || (b.y <= c.y && c.y <= a.y))
+ return true;
+ }
+ }
+
+ if (collinear(a, b, d)) {
+ intp = d;
+ // a, b, d collinear
+ // return false/d if d is between a and b
+ if (a.x != b.x) {
+ if ((a.x <= d.x && d.x <= b.x) || (b.x <= d.x && d.x <= a.x))
+ return false;
+ } else {
+ if ((a.y <= d.y && d.y <= b.y) || (b.y <= d.y && d.y <= a.y))
+ return false;
+ }
+ }
+
+ int len_dc = c.sqrDist(d);
+
+ if (!len_dc) error("zero length edge in polygon");
+
+ if (pointSegDistance(c, d, a) <= 2.0f) {
+ intp = a;
+ return true;
+ }
+
+ if (pointSegDistance(c, d, b) <= 2.0f) {
+ intp = b;
+ return true;
+ }
+
+ // If not an endpoint, call the generic intersection function
+
+ FloatPoint p;
+ if (intersection(a, b, v2, &p) == PF_OK) {
+ intp = p.toPoint();
+ return true;
+ } else {
+ return false;
+ }
+}
+
+// For intersecting polygon segments, determine if
+// * the v2 edge enters polygon 1 at this intersection: positive return value
+// * the v2 edge and the v1 edges are parallel: zero return value
+// * the v2 edge exits polygon 1 at this intersection: negative return value
+static int intersectDir(const Vertex *v1, const Vertex *v2) {
+ Common::Point p1 = v1->_next->v - v1->v;
+ Common::Point p2 = v2->_next->v - v2->v;
+ return (p1.x*p2.y - p2.x*p1.y);
+}
+
+// Direction of edge in degrees from pos. x-axis, between -180 and 180
+static int edgeDir(const Vertex *v) {
+ Common::Point p = v->_next->v - v->v;
+ int deg = (int)Common::rad2deg(atan2((double)p.y, (double)p.x));
+ if (deg < -180) deg += 360;
+ if (deg > 180) deg -= 360;
+ return deg;
+}
+
+// For points p1, p2 on the polygon segment v, determine if
+// * p1 lies before p2: negative return value
+// * p1 and p2 are the same: zero return value
+// * p1 lies after p2: positive return value
+static int liesBefore(const Vertex *v, const Common::Point &p1, const Common::Point &p2) {
+ return v->v.sqrDist(p1) - v->v.sqrDist(p2);
+}
+
+// Structure describing an "extension" to the work polygon following edges
+// of the polygon being merged.
+
+// The patch begins on the point intersection1, being the intersection
+// of the edges starting at indexw1/vertexw1 on the work polygon, and at
+// indexp1/vertexp1 on the polygon being merged.
+// It ends with the point intersection2, being the analogous intersection.
+struct Patch {
+ unsigned int indexw1;
+ unsigned int indexp1;
+ const Vertex *vertexw1;
+ const Vertex *vertexp1;
+ Common::Point intersection1;
+
+ unsigned int indexw2;
+ unsigned int indexp2;
+ const Vertex *vertexw2;
+ const Vertex *vertexp2;
+ Common::Point intersection2;
+
+ bool disabled; // If true, this Patch was made superfluous by another Patch
+};
+
+
+// Check if the given vertex on the work polygon is bypassed by this patch.
+static bool isVertexCovered(const Patch &p, unsigned int wi) {
+
+ // / v (outside)
+ // ---w1--1----p----w2--2----
+ // ^ \ (inside)
+ if (wi > p.indexw1 && wi <= p.indexw2)
+ return true;
+
+ // v / (outside)
+ // ---w2--2----p----w1--1----
+ // \ ^ (inside)
+ if (p.indexw1 > p.indexw2 && (wi <= p.indexw2 || wi > p.indexw1))
+ return true;
+
+ // v / (outside)
+ // ---w1--2--1-------p-----
+ // w2 \ ^ (inside)
+ if (p.indexw1 == p.indexw2 && liesBefore(p.vertexw1, p.intersection1, p.intersection2) > 0)
+ return true; // This patch actually covers _all_ vertices on work
+
+ return false;
+}
+
+// Check if patch p1 makes patch p2 superfluous.
+static bool isPatchCovered(const Patch &p1, const Patch &p2) {
+
+ // Same exit and entry points
+ if (p1.intersection1 == p2.intersection1 && p1.intersection2 == p2.intersection2)
+ return true;
+
+ // / * v (outside)
+ // ---p1w1--1----p2w1-1---p1w2--2----
+ // ^ * \ (inside)
+ if (p1.indexw1 < p2.indexw1 && p2.indexw1 < p1.indexw2)
+ return true;
+ if (p1.indexw1 > p1.indexw2 && (p2.indexw1 > p1.indexw1 || p2.indexw1 < p1.indexw2))
+ return true;
+
+
+ // / * v (outside)
+ // ---p1w1--11----p2w2-2---p1w2--12----
+ // ^ * \ (inside)
+ if (p1.indexw1 < p2.indexw2 && p2.indexw2 < p1.indexw2)
+ return true;
+ if (p1.indexw1 > p1.indexw2 && (p2.indexw2 > p1.indexw1 || p2.indexw2 < p1.indexw2))
+ return true;
+
+ // Opposite of two above situations
+ if (p2.indexw1 < p1.indexw1 && p1.indexw1 < p2.indexw2)
+ return false;
+ if (p2.indexw1 > p2.indexw2 && (p1.indexw1 > p2.indexw1 || p1.indexw1 < p2.indexw2))
+ return false;
+
+ if (p2.indexw1 < p1.indexw2 && p1.indexw2 < p2.indexw2)
+ return false;
+ if (p2.indexw1 > p2.indexw2 && (p1.indexw2 > p2.indexw1 || p1.indexw2 < p2.indexw2))
+ return false;
+
+
+ // The above checks covered the cases where one patch covers the other and
+ // the intersections of the patches are on different edges.
+
+ // So, if we passed the above checks, we have to check the order of
+ // intersections on edges.
+
+
+ if (p1.indexw1 != p1.indexw2) {
+
+ // / * v (outside)
+ // ---p1w1--11---21--------p1w2--2----
+ // p2w1 ^ * \ (inside)
+ if (p1.indexw1 == p2.indexw1)
+ return (liesBefore(p1.vertexw1, p1.intersection1, p2.intersection1) < 0);
+
+ // / * v (outside)
+ // ---p1w1--11---------p1w2--21---12----
+ // ^ p2w1 * \ (inside)
+ if (p1.indexw2 == p2.indexw1)
+ return (liesBefore(p1.vertexw2, p1.intersection2, p2.intersection1) > 0);
+
+ // If neither of the above, then the intervals of the polygon
+ // covered by patch1 and patch2 are disjoint
+ return false;
+ }
+
+ // p1w1 == p1w2
+ // Also, p1w1/p1w2 isn't strictly between p2
+
+
+ // v / * (outside)
+ // ---p1w1--12--11-------p2w1-21----
+ // p1w2 \ ^ * (inside)
+
+ // v / / (outside)
+ // ---p1w1--12--21--11---------
+ // p1w2 \ ^ ^ (inside)
+ // p2w1
+ if (liesBefore(p1.vertexw1, p1.intersection1, p1.intersection2) > 0)
+ return (p1.indexw1 != p2.indexw1);
+
+ // CHECKME: This is meaningless if p2w1 != p2w2 ??
+ if (liesBefore(p2.vertexw1, p2.intersection1, p2.intersection2) > 0)
+ return false;
+
+ // CHECKME: This is meaningless if p1w1 != p2w1 ??
+ if (liesBefore(p2.vertexw1, p2.intersection1, p1.intersection1) <= 0)
+ return false;
+
+ // CHECKME: This is meaningless if p1w2 != p2w1 ??
+ if (liesBefore(p2.vertexw1, p2.intersection1, p1.intersection2) >= 0)
+ return false;
+
+ return true;
+}
+
+// Merge a single polygon into the work polygon.
+// If there is an intersection between work and polygon, this function
+// returns true, and replaces the vertex list of work by an extended version,
+// that covers polygon.
+//
+// NOTE: The strategy used matches qfg1new closely, and is a bit error-prone.
+// A more robust strategy would be inserting all intersection points directly
+// into both vertex lists as a first pass. This would make finding the merged
+// polygon a much more straightforward edge-walk, and avoid cases where SSCI's
+// algorithm mixes up the order of multiple intersections on a single edge.
+bool mergeSinglePolygon(Polygon &work, const Polygon &polygon) {
+#ifdef DEBUG_MERGEPOLY
+ const Vertex *vertex;
+ debugN("work:");
+ CLIST_FOREACH(vertex, &(work.vertices)) {
+ debugN(" (%d,%d) ", vertex->v.x, vertex->v.y);
+ }
+ debugN("\n");
+ debugN("poly:");
+ CLIST_FOREACH(vertex, &(polygon.vertices)) {
+ debugN(" (%d,%d) ", vertex->v.x, vertex->v.y);
+ }
+ debugN("\n");
+#endif
+ uint workSize = work.vertices.size();
+ uint polygonSize = polygon.vertices.size();
+
+ int patchCount = 0;
+ Patch patchList[8];
+
+ const Vertex *workv = work.vertices._head;
+ const Vertex *polyv = polygon.vertices._head;
+ for (uint wi = 0; wi < workSize; ++wi, workv = workv->_next) {
+ for (uint pi = 0; pi < polygonSize; ++pi, polyv = polyv->_next) {
+ Common::Point intersection1;
+ Common::Point intersection2;
+
+ bool intersects = segSegIntersect(workv, polyv, intersection1);
+ if (!intersects)
+ continue;
+
+#ifdef DEBUG_MERGEPOLY
+ debug("mergePoly: intersection at work %d, poly %d", wi, pi);
+#endif
+
+ if (intersectDir(workv, polyv) >= 0)
+ continue;
+
+#ifdef DEBUG_MERGEPOLY
+ debug("mergePoly: intersection in right direction");
+#endif
+
+ int angle = 0;
+ int baseAngle = edgeDir(workv);
+
+ // We now found the point where an edge of 'polygon' left 'work'.
+ // Now find the re-entry point.
+
+ // NOTE: The order in which this searches does not always work
+ // properly if the correct patch would only use a single partial
+ // edge of poly. Because it starts at polyv->_next, it will skip
+ // the correct re-entry and proceed to the next.
+
+ const Vertex *workv2;
+ const Vertex *polyv2 = polyv->_next;
+
+ intersects = false;
+
+ uint pi2, wi2;
+ for (pi2 = 0; pi2 < polygonSize; ++pi2, polyv2 = polyv2->_next) {
+
+ int newAngle = edgeDir(polyv2);
+
+ int relAngle = newAngle - baseAngle;
+ if (relAngle > 180) relAngle -= 360;
+ if (relAngle < -180) relAngle += 360;
+
+ angle += relAngle;
+ baseAngle = newAngle;
+
+ workv2 = workv;
+ for (wi2 = 0; wi2 < workSize; ++wi2, workv2 = workv2->_next) {
+ intersects = segSegIntersect(workv2, polyv2, intersection2);
+ if (!intersects)
+ continue;
+#ifdef DEBUG_MERGEPOLY
+ debug("mergePoly: re-entry intersection at work %d, poly %d", (wi + wi2) % workSize, (pi + 1 + pi2) % polygonSize);
+#endif
+
+ if (intersectDir(workv2, polyv2) > 0) {
+#ifdef DEBUG_MERGEPOLY
+ debug("mergePoly: re-entry intersection in right direction, angle = %d", angle);
+#endif
+ break; // found re-entry point
+ }
+
+ }
+
+ if (intersects)
+ break;
+
+ }
+
+ if (!intersects || angle < 0)
+ continue;
+
+
+ if (patchCount >= 8)
+ error("kMergePoly: Too many patches");
+
+ // convert relative to absolute vertex indices
+ pi2 = (pi + 1 + pi2) % polygonSize;
+ wi2 = (wi + wi2) % workSize;
+
+ Patch &newPatch = patchList[patchCount];
+ newPatch.indexw1 = wi;
+ newPatch.vertexw1 = workv;
+ newPatch.indexp1 = pi;
+ newPatch.vertexp1 = polyv;
+ newPatch.intersection1 = intersection1;
+
+ newPatch.indexw2 = wi2;
+ newPatch.vertexw2 = workv2;
+ newPatch.indexp2 = pi2;
+ newPatch.vertexp2 = polyv2;
+ newPatch.intersection2 = intersection2;
+ newPatch.disabled = false;
+
+#ifdef DEBUG_MERGEPOLY
+ debug("mergePoly: adding patch at work %d, poly %d", wi, pi);
+#endif
+
+ if (patchCount == 0) {
+ patchCount++;
+ continue;
+ }
+
+ bool necessary = true;
+ for (int i = 0; i < patchCount; ++i) {
+ if (isPatchCovered(patchList[i], newPatch)) {
+ necessary = false;
+ break;
+ }
+ }
+
+ if (!necessary)
+ continue;
+
+ patchCount++;
+
+ if (patchCount > 1) {
+ // check if this patch makes other patches superfluous
+ for (int i = 0; i < patchCount-1; ++i)
+ if (isPatchCovered(newPatch, patchList[i]))
+ patchList[i].disabled = true;
+ }
+ }
+ }
+
+
+ if (patchCount == 0)
+ return false; // nothing changed
+
+
+ // Determine merged work by doing a walk over the edges
+ // of work, crossing over to polygon when encountering a patch.
+
+ Polygon output(0);
+
+ workv = work.vertices._head;
+ for (uint wi = 0; wi < workSize; ++wi, workv = workv->_next) {
+
+ bool covered = false;
+ for (int p = 0; p < patchCount; ++p) {
+ if (patchList[p].disabled) continue;
+ if (isVertexCovered(patchList[p], wi)) {
+ covered = true;
+ break;
+ }
+ }
+
+ if (!covered) {
+ // Add vertex to output
+ output.vertices.insertAtEnd(new Vertex(workv->v));
+ }
+
+
+ // CHECKME: Why is this the correct order in which to process
+ // the patches? (What if two of them start on this line segment
+ // in the opposite order?)
+
+ for (int p = 0; p < patchCount; ++p) {
+
+ const Patch &patch = patchList[p];
+ if (patch.disabled) continue;
+ if (patch.indexw1 != wi) continue;
+ if (patch.intersection1 != workv->v) {
+ // Add intersection point to output
+ output.vertices.insertAtEnd(new Vertex(patch.intersection1));
+ }
+
+ // Add vertices from polygon between vertexp1 (excl) and vertexp2 (incl)
+ for (polyv = patch.vertexp1->_next; polyv != patch.vertexp2; polyv = polyv->_next)
+ output.vertices.insertAtEnd(new Vertex(polyv->v));
+
+ output.vertices.insertAtEnd(new Vertex(patch.vertexp2->v));
+
+ if (patch.intersection2 != patch.vertexp2->v) {
+ // Add intersection point to output
+ output.vertices.insertAtEnd(new Vertex(patch.intersection2));
+ }
+
+ // TODO: We could continue after the re-entry point here?
+ }
+ }
+ // Remove last vertex if it's the same as the first vertex
+ if (output.vertices._head->v == output.vertices._head->_prev->v)
+ output.vertices.remove(output.vertices._head->_prev);
+
+
+ // Slight hack: swap vertex lists of output and work polygons.
+ SWAP(output.vertices._head, work.vertices._head);
+
+ return true;
+}
+
+
/**
* This is a quite rare kernel function. An example of when it's called
* is in QFG1VGA, after killing any monster.
+ *
+ * It takes a polygon, and extends it to also cover any polygons from the
+ * input list with which it intersects. Any of those polygons so covered
+ * from the input list are marked by adding 0x10 to their type field.
*/
reg_t kMergePoly(EngineState *s, int argc, reg_t *argv) {
-#if 0
// 3 parameters: raw polygon data, polygon list, list size
reg_t polygonData = argv[0];
List *list = s->_segMan->lookupList(argv[1]);
- Node *node = s->_segMan->lookupNode(list->first);
- // List size is not needed
- Polygon *polygon;
- int count = 0;
+ // The size of the "work" point list SSCI uses. We use a dynamic one instead
+ //reg_t listSize = argv[2];
+
+ SegmentRef pointList = s->_segMan->dereference(polygonData);
+ if (!pointList.isValid() || pointList.skipByte) {
+ warning("kMergePoly: Polygon data pointer is invalid");
+ return make_reg(0, 0);
+ }
+
+ Node *node;
+
+#ifdef DEBUG_MERGEPOLY
+ node = s->_segMan->lookupNode(list->first);
+ while (node) {
+ draw_polygon(s, node->value, 320, 190);
+ node = s->_segMan->lookupNode(node->succ);
+ }
+ Common::Point prev, first;
+ prev = first = readPoint(pointList, 0);
+ for (int i = 1; readPoint(pointList, i).x != 0x7777; i++) {
+ Common::Point point = readPoint(pointList, i);
+ draw_line(s, prev, point, 1, 320, 190);
+ prev = point;
+ }
+ draw_line(s, prev, first, 1, 320, 190);
+ // Update the whole screen
+ g_sci->_gfxScreen->copyToScreen();
+ g_system->updateScreen();
+ g_system->delayMillis(1000);
+#endif
+
+ // The work polygon which we're going to merge with the polygons in list
+ Polygon work(0);
+
+ for (int i = 0; true; ++i) {
+ Common::Point p = readPoint(pointList, i);
+ if (p.x == POLY_LAST_POINT)
+ break;
+ Vertex *vertex = new Vertex(p);
+ work.vertices.insertAtEnd(vertex);
+ }
+
+ // TODO: Check behaviour for single-vertex polygons
+ node = s->_segMan->lookupNode(list->first);
while (node) {
- polygon = convert_polygon(s, node->value);
+ Polygon *polygon = convert_polygon(s, node->value);
if (polygon) {
- count += readSelectorValue(s->_segMan, node->value, SELECTOR(size));
+ // CHECKME: Confirm vertex order that convert_polygon and
+ // fix_vertex_order output. For now, we re-reverse the order since
+ // convert_polygon reads the vertices reversed, and fix up head.
+ polygon->vertices.reverse();
+ polygon->vertices._head = polygon->vertices._head->_next;
+
+ // Merge this polygon into the work polygon if there is an
+ // intersection.
+ bool intersected = mergeSinglePolygon(work, *polygon);
+
+ // If so, flag it
+ if (intersected) {
+ writeSelectorValue(s->_segMan, node->value,
+ SELECTOR(type), polygon->type + 0x10);
+#ifdef DEBUG_MERGEPOLY
+ debugN("Merged polygon: ");
+ // Iterate over edges
+ Vertex *vertex;
+ CLIST_FOREACH(vertex, &(work.vertices)) {
+ debugN(" (%d,%d) ", vertex->v.x, vertex->v.y);
+ }
+ debugN("\n");
+#endif
+ }
}
node = s->_segMan->lookupNode(node->succ);
}
-#endif
- // TODO: actually merge the polygon. We return an empty polygon for now.
- // In QFG1VGA, you can walk over enemy bodies after killing them, since
- // this is a stub.
- reg_t output = allocateOutputArray(s->_segMan, 1);
+
+ // Allocate output array
+ reg_t output = allocateOutputArray(s->_segMan, work.vertices.size()+1);
SegmentRef arrayRef = s->_segMan->dereference(output);
- writePoint(arrayRef, 0, Common::Point(POLY_LAST_POINT, POLY_LAST_POINT));
- warning("Stub: kMergePoly");
+
+ // Copy work.vertices into arrayRef
+ Vertex *vertex;
+ unsigned int n = 0;
+ CLIST_FOREACH(vertex, &work.vertices) {
+ if (vertex == work.vertices._head || vertex->v != vertex->_prev->v)
+ writePoint(arrayRef, n++, vertex->v);
+ }
+
+ writePoint(arrayRef, n, Common::Point(POLY_LAST_POINT, POLY_LAST_POINT));
+
+#ifdef DEBUG_MERGEPOLY
+ prev = first = readPoint(arrayRef, 0);
+ for (int i = 1; readPoint(arrayRef, i).x != 0x7777; i++) {
+ Common::Point point = readPoint(arrayRef, i);
+ draw_line(s, prev, point, 3, 320, 190);
+ prev = point;
+ }
+
+ draw_line(s, prev, first, 3, 320, 190);
+
+ // Update the whole screen
+ g_sci->_gfxScreen->copyToScreen();
+ g_system->updateScreen();
+ if (!g_sci->_gfxPaint16)
+ g_system->delayMillis(1000);
+
+ debug("kMergePoly done");
+#endif
+
return output;
}
diff --git a/engines/sci/engine/ksound.cpp b/engines/sci/engine/ksound.cpp
index b378b4d58b..0633267db4 100644
--- a/engines/sci/engine/ksound.cpp
+++ b/engines/sci/engine/ksound.cpp
@@ -140,12 +140,14 @@ reg_t kDoAudio(EngineState *s, int argc, reg_t *argv) {
((argv[3].toUint16() & 0xff) << 16) |
((argv[4].toUint16() & 0xff) << 8) |
(argv[5].toUint16() & 0xff);
- if (argc == 8) {
+ // Removed warning because of the high amount of console spam
+ /*if (argc == 8) {
+ // TODO: Handle the extra 2 SCI21 params
// argv[6] is always 1
// argv[7] is the contents of global 229 (0xE5)
warning("kDoAudio: Play called with SCI2.1 extra parameters: %04x:%04x and %04x:%04x",
PRINT_REG(argv[6]), PRINT_REG(argv[7]));
- }
+ }*/
} else {
warning("kDoAudio: Play called with an unknown number of parameters (%d)", argc);
return NULL_REG;
@@ -244,6 +246,11 @@ reg_t kDoAudio(EngineState *s, int argc, reg_t *argv) {
// Used in Pharkas whenever a speech sample starts (takes no params)
//warning("kDoAudio: Unhandled case 13, %d extra arguments passed", argc - 1);
break;
+ case 17:
+ // Seems to be some sort of audio sync, used in SQ6. Silenced the
+ // warning due to the high level of spam it produces. (takes no params)
+ //warning("kDoAudio: Unhandled case 17, %d extra arguments passed", argc - 1);
+ break;
default:
warning("kDoAudio: Unhandled case %d, %d extra arguments passed", argv[0].toUint16(), argc - 1);
}
diff --git a/engines/sci/engine/kstring.cpp b/engines/sci/engine/kstring.cpp
index c22d7c7b1e..c4db0b891c 100644
--- a/engines/sci/engine/kstring.cpp
+++ b/engines/sci/engine/kstring.cpp
@@ -165,6 +165,7 @@ reg_t kReadNumber(EngineState *s, int argc, reg_t *argv) {
// 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++;
}
diff --git a/engines/sci/engine/kvideo.cpp b/engines/sci/engine/kvideo.cpp
index cb2a763da9..9b0cb38f51 100644
--- a/engines/sci/engine/kvideo.cpp
+++ b/engines/sci/engine/kvideo.cpp
@@ -50,6 +50,8 @@ void playVideo(Video::VideoDecoder *videoDecoder, VideoState videoState) {
if (!videoDecoder)
return;
+ videoDecoder->start();
+
byte *scaleBuffer = 0;
byte bytesPerPixel = videoDecoder->getPixelFormat().bytesPerPixel;
uint16 width = videoDecoder->getWidth();
@@ -162,9 +164,8 @@ reg_t kShowMovie(EngineState *s, int argc, reg_t *argv) {
} else {
// DOS SEQ
// SEQ's are called with no subops, just the string and delay
- SeqDecoder *seqDecoder = new SeqDecoder();
- seqDecoder->setFrameDelay(argv[1].toUint16()); // Time between frames in ticks
- videoDecoder = seqDecoder;
+ // Time is specified as ticks
+ videoDecoder = new SEQDecoder(argv[1].toUint16());
if (!videoDecoder->loadFile(filename)) {
warning("Failed to open movie file %s", filename.c_str());
@@ -190,7 +191,7 @@ reg_t kShowMovie(EngineState *s, int argc, reg_t *argv) {
switch (argv[0].toUint16()) {
case 0: {
Common::String filename = s->_segMan->getString(argv[1]);
- videoDecoder = new Video::AviDecoder(g_system->getMixer());
+ videoDecoder = new Video::AVIDecoder();
if (filename.equalsIgnoreCase("gk2a.avi")) {
// HACK: Switch to 16bpp graphics for Indeo3.
@@ -252,6 +253,7 @@ reg_t kRobot(EngineState *s, int argc, reg_t *argv) {
int16 y = argv[5].toUint16();
warning("kRobot(init), id %d, obj %04x:%04x, flag %d, x=%d, y=%d", id, PRINT_REG(obj), flag, x, y);
g_sci->_robotDecoder->load(id);
+ g_sci->_robotDecoder->start();
g_sci->_robotDecoder->setPos(x, y);
}
break;
@@ -267,13 +269,13 @@ reg_t kRobot(EngineState *s, int argc, reg_t *argv) {
warning("kRobot(%d)", subop);
break;
case 8: // sync
- //if (false) { // debug: automatically skip all robot videos
- if ((uint32)g_sci->_robotDecoder->getCurFrame() != g_sci->_robotDecoder->getFrameCount() - 1) {
- writeSelector(s->_segMan, argv[1], SELECTOR(signal), NULL_REG);
- } else {
+ //if (true) { // debug: automatically skip all robot videos
+ if (g_sci->_robotDecoder->endOfVideo()) {
g_sci->_robotDecoder->close();
// Signal the engine scripts that the video is done
writeSelector(s->_segMan, argv[1], SELECTOR(signal), SIGNAL_REG);
+ } else {
+ writeSelector(s->_segMan, argv[1], SELECTOR(signal), NULL_REG);
}
break;
default:
@@ -348,7 +350,7 @@ reg_t kPlayVMD(EngineState *s, int argc, reg_t *argv) {
break;
}
case 6: // Play
- videoDecoder = new Video::VMDDecoder(g_system->getMixer());
+ videoDecoder = new Video::AdvancedVMDDecoder();
if (s->_videoState.fileName.empty()) {
// Happens in Lighthouse
@@ -406,7 +408,7 @@ reg_t kPlayDuck(EngineState *s, int argc, reg_t *argv) {
s->_videoState.reset();
s->_videoState.fileName = Common::String::format("%d.duk", argv[1].toUint16());
- videoDecoder = new Video::AviDecoder(g_system->getMixer());
+ videoDecoder = new Video::AVIDecoder();
if (!videoDecoder->loadFile(s->_videoState.fileName)) {
warning("Could not open Duck %s", s->_videoState.fileName.c_str());
diff --git a/engines/sci/engine/script.cpp b/engines/sci/engine/script.cpp
index 037f4ab700..36d2841b07 100644
--- a/engines/sci/engine/script.cpp
+++ b/engines/sci/engine/script.cpp
@@ -144,7 +144,7 @@ void Script::load(int script_nr, ResourceManager *resMan) {
_heapStart = _buf + _scriptSize;
- assert(_bufSize - _scriptSize <= heap->size);
+ assert(_bufSize - _scriptSize >= heap->size);
memcpy(_heapStart, heap->data, heap->size);
}
diff --git a/engines/sci/engine/script_patches.cpp b/engines/sci/engine/script_patches.cpp
index 659c13b13e..8454be514a 100644
--- a/engines/sci/engine/script_patches.cpp
+++ b/engines/sci/engine/script_patches.cpp
@@ -915,9 +915,53 @@ const uint16 qfg3PatchImportDialog[] = {
PATCH_END
};
+
+
+// ===========================================================================
+// Patch for the Woo dialog option in Uhura's conversation. Bug #3040722
+// Problem: The Woo dialog option (0xffb5) is negative, and therefore
+// treated as an option opening a submenu. This leads to uhuraTell::doChild
+// being called, which calls hero::solvePuzzle and then proceeds with
+// Teller::doChild to open the submenu. However, there is no actual submenu
+// defined for option -75 since -75 does not show up in uhuraTell::keys.
+// This will cause Teller::doChild to run out of bounds while scanning through
+// uhuraTell::keys.
+// Strategy: there is another conversation option in uhuraTell::doChild calling
+// hero::solvePuzzle (0xfffc) which does a ret afterwards without going to
+// Teller::doChild. We jump to this call of hero::solvePuzzle to get that same
+// behaviour.
+
+const byte qfg3SignatureWooDialog[] = {
+ 30,
+ 0x67, 0x12, // pTos 12 (query)
+ 0x35, 0xb6, // ldi b6
+ 0x1a, // eq?
+ 0x2f, 0x05, // bt 05
+ 0x67, 0x12, // pTos 12 (query)
+ 0x35, 0x9b, // ldi 9b
+ 0x1a, // eq?
+ 0x31, 0x0c, // bnt 0c
+ 0x38, 0x97, 0x02, // pushi 0297
+ 0x7a, // push2
+ 0x38, 0x0c, 0x01, // pushi 010c
+ 0x7a, // push2
+ 0x81, 0x00, // lag 00
+ 0x4a, 0x08, // send 08
+ 0x67, 0x12, // pTos 12 (query)
+ 0x35, 0xb5, // ldi b5
+ 0
+};
+
+const uint16 qfg3PatchWooDialog[] = {
+ PATCH_ADDTOOFFSET | +0x29,
+ 0x33, 0x11, // jmp to 0x6a2, the call to hero::solvePuzzle for 0xFFFC
+ PATCH_END
+};
+
// script, description, magic DWORD, adjust
const SciScriptSignature qfg3Signatures[] = {
{ 944, "import dialog continuous calls", 1, PATCH_MAGICDWORD(0x2a, 0x31, 0x0b, 0x7a), -1, qfg3SignatureImportDialog, qfg3PatchImportDialog },
+ { 440, "dialog crash when asking about Woo", 1, PATCH_MAGICDWORD(0x67, 0x12, 0x35, 0xb5), -26, qfg3SignatureWooDialog, qfg3PatchWooDialog },
SCI_SIGNATUREENTRY_TERMINATOR
};
diff --git a/engines/sci/engine/scriptdebug.cpp b/engines/sci/engine/scriptdebug.cpp
index d3abb9ff41..b2f22aa985 100644
--- a/engines/sci/engine/scriptdebug.cpp
+++ b/engines/sci/engine/scriptdebug.cpp
@@ -514,7 +514,7 @@ void Kernel::dissectScript(int scriptNumber, Vocabulary *vocab) {
if (!objType) {
debugN("End of script object (#0) encountered.\n");
- debugN("Classes: %i, Objects: %i, Export: %i,\n Var: %i (all base 10)",
+ debugN("Classes: %i, Objects: %i, Export: %i,\n Var: %i (all base 10)\n",
objectctr[6], objectctr[1], objectctr[7], objectctr[10]);
return;
}
@@ -527,7 +527,8 @@ void Kernel::dissectScript(int scriptNumber, Vocabulary *vocab) {
_seeker += objsize;
- objectctr[objType]++;
+ if (objType >= 0 && objType < ARRAYSIZE(objectctr))
+ objectctr[objType]++;
switch (objType) {
case SCI_OBJ_OBJECT:
diff --git a/engines/sci/engine/seg_manager.cpp b/engines/sci/engine/seg_manager.cpp
index 951fc7c363..04c1dab158 100644
--- a/engines/sci/engine/seg_manager.cpp
+++ b/engines/sci/engine/seg_manager.cpp
@@ -825,7 +825,7 @@ byte *SegManager::allocDynmem(int size, const char *descr, reg_t *addr) {
}
bool SegManager::freeDynmem(reg_t addr) {
- if (addr.getSegment() < 1 || addr.getSegment() >= _heap.size() ||
+ if (addr.getSegment() < 1 || addr.getSegment() >= _heap.size() ||
!_heap[addr.getSegment()] || _heap[addr.getSegment()]->getType() != SEG_TYPE_DYNMEM)
return false; // error
diff --git a/engines/sci/engine/vm.cpp b/engines/sci/engine/vm.cpp
index 5a2a39def2..ef8f165084 100644
--- a/engines/sci/engine/vm.cpp
+++ b/engines/sci/engine/vm.cpp
@@ -228,7 +228,7 @@ ExecStack *execute_method(EngineState *s, uint16 script, uint16 pubfunct, StackP
uint32 exportAddr = scr->validateExportFunc(pubfunct, false);
if (!exportAddr)
return NULL;
-
+
// Check if a breakpoint is set on this method
g_sci->checkExportBreakpoint(script, pubfunct);
@@ -1173,6 +1173,7 @@ void run_vm(EngineState *s) {
case op_line: // 0x3f (63)
// Debug opcode (line number)
+ //debug("Script %d, line %d", scr->getScriptNumber(), opparams[0]);
break;
case op_lag: // 0x40 (64)
diff --git a/engines/sci/engine/workarounds.cpp b/engines/sci/engine/workarounds.cpp
index 15fca0322c..9fa0368784 100644
--- a/engines/sci/engine/workarounds.cpp
+++ b/engines/sci/engine/workarounds.cpp
@@ -36,13 +36,15 @@ const SciWorkaroundEntry arithmeticWorkarounds[] = {
{ GID_ECOQUEST2, 100, 0, 0, "Rain", "points", 0xcc6, 0, { WORKAROUND_FAKE, 0 } }, // op_or: when giving the papers to the customs officer, gets called against a pointer instead of a number - bug #3034464
{ GID_ECOQUEST2, 100, 0, 0, "Rain", "points", 0xce0, 0, { WORKAROUND_FAKE, 0 } }, // Same as above, for the Spanish version - bug #3313962
{ GID_FANMADE, 516, 983, 0, "Wander", "setTarget", -1, 0, { WORKAROUND_FAKE, 0 } }, // op_mul: The Legend of the Lost Jewel Demo (fan made): called with object as second parameter when attacked by insects - bug #3038913
+ { GID_GK1, 800,64992, 0, "Fwd", "doit", -1, 0, { WORKAROUND_FAKE, 1 } }, // op_gt: when Mosely finds Gabriel and Grace near the end of the game, compares the Grooper object with 7
{ GID_ICEMAN, 199, 977, 0, "Grooper", "doit", -1, 0, { WORKAROUND_FAKE, 0 } }, // op_add: While dancing with the girl
{ GID_MOTHERGOOSE256, -1, 999, 0, "Event", "new", -1, 0, { WORKAROUND_FAKE, 0 } }, // op_and: constantly during the game (SCI1 version)
{ GID_MOTHERGOOSE256, -1, 4, 0, "rm004", "doit", -1, 0, { WORKAROUND_FAKE, 0 } }, // op_or: when going north and reaching the castle (rooms 4 and 37) - bug #3038228
{ GID_MOTHERGOOSEHIRES,90, 90, 0, "newGameButton", "select", -1, 0, { WORKAROUND_FAKE, 0 } }, // op_ge: MUMG Deluxe, when selecting "New Game" in the main menu. It tries to compare an integer with a list. Needs to return false for the game to continue.
+ { GID_PHANTASMAGORIA, 902, 0, 0, "", "export 7", -1, 0, { WORKAROUND_FAKE, 0 } }, // op_shr: when starting a chapter in Phantasmagoria
{ GID_QFG1VGA, 301, 928, 0, "Blink", "init", -1, 0, { WORKAROUND_FAKE, 0 } }, // op_div: when entering the inn, gets called with 1 parameter, but 2nd parameter is used for div which happens to be an object
{ GID_QFG2, 200, 200, 0, "astro", "messages", -1, 0, { WORKAROUND_FAKE, 0 } }, // op_lsi: when getting asked for your name by the astrologer bug #3039879
- { GID_GK1, 800,64992, 0, "Fwd", "doit", -1, 0, { WORKAROUND_FAKE, 1 } }, // op_gt: when Mosely finds Gabriel and Grace near the end of the game, compares the Grooper object with 7
+ { GID_QFG4, 710,64941, 0, "RandCycle", "doit", -1, 0, { WORKAROUND_FAKE, 1 } }, // op_gt: when the tentacle appears in the third room of the caves
SCI_WORKAROUNDENTRY_TERMINATOR
};
@@ -165,7 +167,7 @@ const SciWorkaroundEntry uninitializedReadWorkarounds[] = {
{ GID_SQ4, -1, 928, -1, "Narrator", "startText", -1, 1000, { WORKAROUND_FAKE, 1 } }, // CD: happens in the options dialog and in-game when speech and subtitles are used simultaneously
{ GID_SQ5, 201, 201, 0, "buttonPanel", "doVerb", -1, 0, { WORKAROUND_FAKE, 1 } }, // when looking at the orange or red button - bug #3038563
{ GID_SQ6, -1, 0, 0, "SQ6", "init", -1, 2, { WORKAROUND_FAKE, 0 } }, // Demo and full version: called when the game starts (demo: room 0, full: room 100)
- { GID_SQ6, 100, 64950, 0, "View", "handleEvent", -1, 0, { WORKAROUND_FAKE, 0 } }, // called when pressing "Start game" in the main menu
+ { GID_SQ6, -1, 64950, -1, "Feature", "handleEvent", -1, 0, { WORKAROUND_FAKE, 0 } }, // called when pressing "Start game" in the main menu, when entering the Orion's Belt bar (room 300), and perhaps other places
{ GID_SQ6, -1, 64964, 0, "DPath", "init", -1, 1, { WORKAROUND_FAKE, 0 } }, // during the game
{ GID_TORIN, -1, 64017, 0, "oFlags", "clear", -1, 0, { WORKAROUND_FAKE, 0 } }, // entering Torin's home in the French version
SCI_WORKAROUNDENTRY_TERMINATOR
@@ -397,6 +399,7 @@ const SciWorkaroundEntry kUnLoad_workarounds[] = {
{ GID_LSL6, 740, 740, 0, "showCartoon", "changeState", -1, 0, { WORKAROUND_IGNORE, 0 } }, // during ending, 4 additional parameters are passed by accident
{ GID_LSL6HIRES, 130, 130, 0, "recruitLarryScr", "changeState", -1, 0, { WORKAROUND_IGNORE, 0 } }, // during intro, a 3rd parameter is passed by accident
{ GID_SQ1, 43, 303, 0, "slotGuy", "dispose", -1, 0, { WORKAROUND_IGNORE, 0 } }, // when leaving ulence flats bar, parameter 1 is not passed - script error
+ { GID_QFG4, -1, 110, 0, "dreamer", "dispose", -1, 0, { WORKAROUND_IGNORE, 0 } }, // during the dream sequence, a 3rd parameter is passed by accident
SCI_WORKAROUNDENTRY_TERMINATOR
};
diff --git a/engines/sci/graphics/controls32.cpp b/engines/sci/graphics/controls32.cpp
index ad1d9e8623..5535a7408a 100644
--- a/engines/sci/graphics/controls32.cpp
+++ b/engines/sci/graphics/controls32.cpp
@@ -68,7 +68,7 @@ void GfxControls32::kernelTexteditChange(reg_t controlObject) {
while (captureEvents) {
curEvent = g_sci->getEventManager()->getSciEvent(SCI_EVENT_KEYBOARD | SCI_EVENT_PEEK);
-
+
if (curEvent.type == SCI_EVENT_NONE) {
eventMan->getSciEvent(SCI_EVENT_KEYBOARD); // consume the event
} else {
@@ -170,11 +170,11 @@ void GfxControls32::kernelTexteditChange(reg_t controlObject) {
// Note: the following checkAltInput call might make the text
// too wide to fit, but SSCI fails to check that too.
}
-
+
reg_t hunkId = readSelector(_segMan, controlObject, SELECTOR(bitmap));
Common::Rect nsRect = g_sci->_gfxCompare->getNSRect(controlObject);
//texteditCursorErase(); // TODO: Cursor
-
+
// Write back string
_segMan->strcpy(textReference, text.c_str());
// Modify the buffer and show it
diff --git a/engines/sci/graphics/font.cpp b/engines/sci/graphics/font.cpp
index fcdd057509..30184cc091 100644
--- a/engines/sci/graphics/font.cpp
+++ b/engines/sci/graphics/font.cpp
@@ -54,7 +54,7 @@ GfxFontFromResource::GfxFontFromResource(ResourceManager *resMan, GfxScreen *scr
}
GfxFontFromResource::~GfxFontFromResource() {
- delete []_chars;
+ delete[] _chars;
_resMan->unlockResource(_resource);
}
diff --git a/engines/sci/graphics/frameout.cpp b/engines/sci/graphics/frameout.cpp
index cb56e24de9..8b7fa2c384 100644
--- a/engines/sci/graphics/frameout.cpp
+++ b/engines/sci/graphics/frameout.cpp
@@ -28,6 +28,7 @@
#include "common/system.h"
#include "common/textconsole.h"
#include "engines/engine.h"
+#include "graphics/palette.h"
#include "graphics/surface.h"
#include "sci/sci.h"
@@ -125,23 +126,17 @@ void GfxFrameout::kernelAddPlane(reg_t object) {
if (_planes.empty()) {
// There has to be another way for sierra sci to do this or maybe script resolution is compiled into
// interpreter (TODO)
- uint16 tmpRunningWidth = readSelectorValue(_segMan, object, SELECTOR(resX));
- uint16 tmpRunningHeight = readSelectorValue(_segMan, object, SELECTOR(resY));
+ uint16 scriptWidth = readSelectorValue(_segMan, object, SELECTOR(resX));
+ uint16 scriptHeight = readSelectorValue(_segMan, object, SELECTOR(resY));
- // The above can be 0 in SCI3 (e.g. Phantasmagoria 2)
- if (tmpRunningWidth == 0 && tmpRunningHeight == 0) {
- tmpRunningWidth = 320;
- tmpRunningHeight = 200;
- }
-
- // HACK: Phantasmagoria 1 sets a window size of 630x450.
- // We can't set a width of 630, as that messes up the pitch, so we hack
- // the internal script width here
- if (g_sci->getGameId() == GID_PHANTASMAGORIA) {
- tmpRunningWidth = 325;
+ // Phantasmagoria 2 doesn't specify a script width/height
+ if (g_sci->getGameId() == GID_PHANTASMAGORIA2) {
+ scriptWidth = 640;
+ scriptHeight = 480;
}
- _coordAdjuster->setScriptsResolution(tmpRunningWidth, tmpRunningHeight);
+ assert(scriptWidth > 0 && scriptHeight > 0);
+ _coordAdjuster->setScriptsResolution(scriptWidth, scriptHeight);
}
// Import of QfG character files dialog is shown in QFG4.
@@ -159,7 +154,7 @@ void GfxFrameout::kernelAddPlane(reg_t object) {
newPlane.object = object;
newPlane.priority = readSelectorValue(_segMan, object, SELECTOR(priority));
- newPlane.lastPriority = 0xFFFF; // hidden
+ newPlane.lastPriority = -1; // hidden
newPlane.planeOffsetX = 0;
newPlane.planeOffsetY = 0;
newPlane.pictureId = kPlanePlainColored;
@@ -202,7 +197,7 @@ void GfxFrameout::kernelUpdatePlane(reg_t object) {
} else {
it->planeOffsetX = 0;
}
-
+
if (it->planeRect.top < 0) {
it->planeOffsetY = -it->planeRect.top;
it->planeRect.top = 0;
@@ -425,7 +420,7 @@ void GfxFrameout::deletePlaneItems(reg_t planeObject) {
} else {
objectMatches = true;
}
-
+
if (objectMatches) {
FrameoutEntry *itemEntry = *listIterator;
listIterator = _screenItems.erase(listIterator);
@@ -465,15 +460,10 @@ bool sortHelper(const FrameoutEntry* entry1, const FrameoutEntry* entry2) {
}
bool planeSortHelper(const PlaneEntry &entry1, const PlaneEntry &entry2) {
-// SegManager *segMan = g_sci->getEngineState()->_segMan;
-
-// uint16 plane1Priority = readSelectorValue(segMan, entry1, SELECTOR(priority));
-// uint16 plane2Priority = readSelectorValue(segMan, entry2, SELECTOR(priority));
-
- if (entry1.priority == 0xffff)
+ if (entry1.priority < 0)
return true;
- if (entry2.priority == 0xffff)
+ if (entry2.priority < 0)
return false;
return entry1.priority < entry2.priority;
@@ -499,7 +489,7 @@ void GfxFrameout::showVideo() {
uint16 y = videoDecoder->getPos().y;
if (videoDecoder->hasDirtyPalette())
- videoDecoder->setSystemPalette();
+ g_system->getPaletteManager()->setPalette(videoDecoder->getPalette(), 0, 256);
while (!g_engine->shouldQuit() && !videoDecoder->endOfVideo() && !skipVideo) {
if (videoDecoder->needsUpdate()) {
@@ -508,7 +498,7 @@ void GfxFrameout::showVideo() {
g_system->copyRectToScreen(frame->pixels, frame->pitch, x, y, frame->w, frame->h);
if (videoDecoder->hasDirtyPalette())
- videoDecoder->setSystemPalette();
+ g_system->getPaletteManager()->setPalette(videoDecoder->getPalette(), 0, 256);
g_system->updateScreen();
}
@@ -639,13 +629,13 @@ void GfxFrameout::kernelFrameout() {
_screen->drawLine(startPoint, endPoint, it2->color, it2->priority, it2->control);
}
- uint16 planeLastPriority = it->lastPriority;
+ int16 planeLastPriority = it->lastPriority;
// Update priority here, sq6 sets it w/o UpdatePlane
- uint16 planePriority = it->priority = readSelectorValue(_segMan, planeObject, SELECTOR(priority));
+ int16 planePriority = it->priority = readSelectorValue(_segMan, planeObject, SELECTOR(priority));
it->lastPriority = planePriority;
- if (planePriority == 0xffff) { // Plane currently not meant to be shown
+ if (planePriority < 0) { // Plane currently not meant to be shown
// If plane was shown before, delete plane rect
if (planePriority != planeLastPriority)
_paint32->fillRect(it->planeRect, 0);
@@ -671,7 +661,7 @@ void GfxFrameout::kernelFrameout() {
if (!itemEntry->visible)
continue;
-
+
if (itemEntry->object.isNull()) {
// Picture cel data
_coordAdjuster->fromScriptToDisplay(itemEntry->y, itemEntry->x);
@@ -709,13 +699,13 @@ 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) {
- if ((itemEntry->scaleX == 128) && (itemEntry->scaleY == 128))
- view->getCelRect(itemEntry->loopNo, itemEntry->celNo,
- itemEntry->x, itemEntry->y, itemEntry->z, itemEntry->celRect);
- else
- view->getCelScaledRect(itemEntry->loopNo, itemEntry->celNo,
- itemEntry->x, itemEntry->y, itemEntry->z, itemEntry->scaleX,
- itemEntry->scaleY, itemEntry->celRect);
+ if ((itemEntry->scaleX == 128) && (itemEntry->scaleY == 128))
+ view->getCelRect(itemEntry->loopNo, itemEntry->celNo,
+ itemEntry->x, itemEntry->y, itemEntry->z, itemEntry->celRect);
+ else
+ view->getCelScaledRect(itemEntry->loopNo, itemEntry->celNo,
+ itemEntry->x, itemEntry->y, itemEntry->z, itemEntry->scaleX,
+ itemEntry->scaleY, itemEntry->celRect);
Common::Rect nsRect = itemEntry->celRect;
// Translate back to actual coordinate within scrollable plane
@@ -739,7 +729,9 @@ void GfxFrameout::kernelFrameout() {
g_sci->_gfxCompare->setNSRect(itemEntry->object, nsRect);
}
- // FIXME: When does this happen, and why?
+ // Don't attempt to draw sprites that are outside the visible
+ // screen area. An example is the random people walking in
+ // Jackson Square in GK1.
if (itemEntry->celRect.bottom < 0 || itemEntry->celRect.top >= _screen->getDisplayHeight() ||
itemEntry->celRect.right < 0 || itemEntry->celRect.left >= _screen->getDisplayWidth())
continue;
@@ -763,10 +755,10 @@ void GfxFrameout::kernelFrameout() {
if (view) {
if (!clipRect.isEmpty()) {
if ((itemEntry->scaleX == 128) && (itemEntry->scaleY == 128))
- view->draw(itemEntry->celRect, clipRect, translatedClipRect,
+ view->draw(itemEntry->celRect, clipRect, translatedClipRect,
itemEntry->loopNo, itemEntry->celNo, 255, 0, view->isSci2Hires());
else
- view->drawScaled(itemEntry->celRect, clipRect, translatedClipRect,
+ view->drawScaled(itemEntry->celRect, clipRect, translatedClipRect,
itemEntry->loopNo, itemEntry->celNo, 255, itemEntry->scaleX, itemEntry->scaleY);
}
}
@@ -825,7 +817,7 @@ void GfxFrameout::printPlaneItemList(Console *con, reg_t planeObject) {
for (FrameoutList::iterator listIterator = _screenItems.begin(); listIterator != _screenItems.end(); listIterator++) {
FrameoutEntry *e = *listIterator;
reg_t itemPlane = readSelector(_segMan, e->object, SELECTOR(plane));
-
+
if (planeObject == itemPlane) {
Common::String curItemName = _segMan->getObjectName(e->object);
Common::Rect icr = e->celRect;
diff --git a/engines/sci/graphics/frameout.h b/engines/sci/graphics/frameout.h
index ecaf450d89..5fd2824224 100644
--- a/engines/sci/graphics/frameout.h
+++ b/engines/sci/graphics/frameout.h
@@ -40,8 +40,8 @@ typedef Common::List<PlaneLineEntry> PlaneLineList;
struct PlaneEntry {
reg_t object;
- uint16 priority;
- uint16 lastPriority;
+ int16 priority;
+ int16 lastPriority;
int16 planeOffsetX;
int16 planeOffsetY;
GuiResourceId pictureId;
diff --git a/engines/sci/graphics/paint16.cpp b/engines/sci/graphics/paint16.cpp
index 89f3625e2c..d20aa80c77 100644
--- a/engines/sci/graphics/paint16.cpp
+++ b/engines/sci/graphics/paint16.cpp
@@ -559,8 +559,8 @@ reg_t GfxPaint16::kernelDisplay(const char *text, int argc, reg_t *argv) {
SciTrackOriginReply originReply;
SciWorkaroundSolution solution = trackOriginAndFindWorkaround(0, kDisplay_workarounds, &originReply);
if (solution.type == WORKAROUND_NONE)
- error("Unknown kDisplay argument (%04x:%04x) from method %s::%s (script %d, localCall %x)",
- PRINT_REG(displayArg), originReply.objectName.c_str(), originReply.methodName.c_str(),
+ error("Unknown kDisplay argument (%04x:%04x) from method %s::%s (script %d, localCall %x)",
+ PRINT_REG(displayArg), originReply.objectName.c_str(), originReply.methodName.c_str(),
originReply.scriptNr, originReply.localCallOffset);
assert(solution.type == WORKAROUND_IGNORE);
break;
diff --git a/engines/sci/graphics/palette.cpp b/engines/sci/graphics/palette.cpp
index ea154c5037..53d69cdcca 100644
--- a/engines/sci/graphics/palette.cpp
+++ b/engines/sci/graphics/palette.cpp
@@ -100,6 +100,9 @@ GfxPalette::GfxPalette(ResourceManager *resMan, GfxScreen *screen)
default:
error("GfxPalette: Unknown view type");
}
+
+ _remapOn = false;
+ resetRemapping();
}
GfxPalette::~GfxPalette() {
@@ -140,8 +143,9 @@ void GfxPalette::createFromData(byte *data, int bytesLeft, Palette *paletteOut)
memset(paletteOut, 0, sizeof(Palette));
// Setup 1:1 mapping
- for (colorNo = 0; colorNo < 256; colorNo++)
+ for (colorNo = 0; colorNo < 256; colorNo++) {
paletteOut->mapping[colorNo] = colorNo;
+ }
if (bytesLeft < 37) {
// This happens when loading palette of picture 0 in sq5 - the resource is broken and doesn't contain a full
@@ -329,6 +333,79 @@ void GfxPalette::set(Palette *newPalette, bool force, bool forceRealMerge) {
}
}
+byte GfxPalette::remapColor(byte remappedColor, byte screenColor) {
+ assert(_remapOn);
+ if (_remappingType[remappedColor] == kRemappingByRange)
+ return _remappingByRange[screenColor];
+ else if (_remappingType[remappedColor] == kRemappingByPercent)
+ return _remappingByPercent[screenColor];
+ else
+ error("remapColor(): Color %d isn't remapped", remappedColor);
+
+ return 0; // should never reach here
+}
+
+void GfxPalette::resetRemapping() {
+ _remapOn = false;
+ _remappingPercentToSet = 0;
+
+ for (int i = 0; i < 256; i++) {
+ _remappingType[i] = kRemappingNone;
+ _remappingByPercent[i] = i;
+ _remappingByRange[i] = i;
+ }
+}
+
+void GfxPalette::setRemappingPercent(byte color, byte percent) {
+ _remapOn = true;
+
+ // We need to defer the setup of the remapping table every time the screen
+ // palette is changed, so that kernelFindColor() can find the correct
+ // colors. Set it once here, in case the palette stays the same and update
+ // it on each palette change by copySysPaletteToScreen().
+ _remappingPercentToSet = percent;
+
+ for (int i = 0; i < 256; i++) {
+ byte r = _sysPalette.colors[i].r * _remappingPercentToSet / 100;
+ byte g = _sysPalette.colors[i].g * _remappingPercentToSet / 100;
+ byte b = _sysPalette.colors[i].b * _remappingPercentToSet / 100;
+ _remappingByPercent[i] = kernelFindColor(r, g, b);
+ }
+
+ _remappingType[color] = kRemappingByPercent;
+}
+
+void GfxPalette::setRemappingPercentGray(byte color, byte percent) {
+ _remapOn = true;
+
+ // We need to defer the setup of the remapping table every time the screen
+ // palette is changed, so that kernelFindColor() can find the correct
+ // colors. Set it once here, in case the palette stays the same and update
+ // it on each palette change by copySysPaletteToScreen().
+ _remappingPercentToSet = 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 luminosity = rComponent + gComponent + bComponent;
+ _remappingByPercent[i] = kernelFindColor(luminosity, luminosity, luminosity);
+ }
+
+ _remappingType[color] = kRemappingByPercent;
+}
+
+void GfxPalette::setRemappingRange(byte color, byte from, byte to, byte base) {
+ _remapOn = true;
+
+ for (int i = from; i <= to; i++) {
+ _remappingByRange[i] = i + base;
+ }
+
+ _remappingType[color] = kRemappingByRange;
+}
+
bool GfxPalette::insert(Palette *newPalette, Palette *destPalette) {
bool paletteChanged = false;
@@ -491,6 +568,16 @@ void GfxPalette::copySysPaletteToScreen() {
}
}
+ // Check if we need to reset remapping by percent with the new colors.
+ if (_remappingPercentToSet) {
+ for (int i = 0; i < 256; i++) {
+ byte r = _sysPalette.colors[i].r * _remappingPercentToSet / 100;
+ byte g = _sysPalette.colors[i].g * _remappingPercentToSet / 100;
+ byte b = _sysPalette.colors[i].b * _remappingPercentToSet / 100;
+ _remappingByPercent[i] = kernelFindColor(r, g, b);
+ }
+ }
+
g_system->getPaletteManager()->setPalette(bpal, 0, 256);
}
@@ -999,8 +1086,9 @@ bool GfxPalette::loadClut(uint16 clutId) {
memset(&pal, 0, sizeof(Palette));
// Setup 1:1 mapping
- for (int i = 0; i < 256; i++)
+ for (int i = 0; i < 256; i++) {
pal.mapping[i] = i;
+ }
// Now load in the palette
for (int i = 1; i <= 236; i++) {
diff --git a/engines/sci/graphics/palette.h b/engines/sci/graphics/palette.h
index a9ea1c32de..e974781d49 100644
--- a/engines/sci/graphics/palette.h
+++ b/engines/sci/graphics/palette.h
@@ -31,6 +31,12 @@ namespace Sci {
class ResourceManager;
class GfxScreen;
+enum ColorRemappingType {
+ kRemappingNone = 0,
+ kRemappingByRange = 1,
+ kRemappingByPercent = 2
+};
+
/**
* Palette class, handles palette operations like changing intensity, setting up the palette, merging different palettes
*/
@@ -53,6 +59,15 @@ public:
void getSys(Palette *pal);
uint16 getTotalColorCount() const { return _totalScreenColors; }
+ void resetRemapping();
+ void setRemappingPercent(byte color, byte percent);
+ void setRemappingPercentGray(byte color, byte percent);
+ void setRemappingRange(byte color, byte from, byte to, byte base);
+ bool isRemapped(byte color) const {
+ return _remapOn && (_remappingType[color] != kRemappingNone);
+ }
+ byte remapColor(byte remappedColor, byte screenColor);
+
void setOnScreen();
void copySysPaletteToScreen();
@@ -123,6 +138,12 @@ private:
int _palVarySignal;
uint16 _totalScreenColors;
+ bool _remapOn;
+ ColorRemappingType _remappingType[256];
+ byte _remappingByPercent[256];
+ byte _remappingByRange[256];
+ uint16 _remappingPercentToSet;
+
void loadMacIconBarPalette();
byte *_macClut;
diff --git a/engines/sci/graphics/ports.cpp b/engines/sci/graphics/ports.cpp
index 6b4c8180bf..8acdeed763 100644
--- a/engines/sci/graphics/ports.cpp
+++ b/engines/sci/graphics/ports.cpp
@@ -380,17 +380,50 @@ Window *GfxPorts::addWindow(const Common::Rect &dims, const Common::Rect *restor
int16 oldtop = pwnd->dims.top;
int16 oldleft = pwnd->dims.left;
- if (wmprect.top > pwnd->dims.top)
+ // WORKAROUND: We also adjust the restore rect when adjusting the window
+ // rect.
+ // SSCI does not do this. It wasn't necessary in the original interpreter,
+ // but it is needed for Freddy Pharkas CD. This version does not normally
+ // have text, but we allow this by modifying the text/speech setting
+ // according to what is set in the ScummVM GUI (refer to syncIngameAudioOptions()
+ // in sci.cpp). Since the text used in Freddy Pharkas CD is quite large in
+ // some cases, it ends up being offset in order to fit inside the screen,
+ // but the associated restore rect isn't adjusted accordingly, leading to
+ // artifacts being left on screen when some text boxes are removed. The
+ // fact that the restore rect wasn't ever adjusted doesn't make sense, and
+ // adjusting it shouldn't have any negative side-effects (it *should* be
+ // adjusted, normally, but SCI doesn't do it). The big text boxes are still
+ // odd-looking, because the text rect is drawn outside the text window rect,
+ // but at least there aren't any leftover textbox artifacts left when the
+ // boxes are removed. Adjusting the text window rect would require more
+ // invasive changes than this one, thus it's not really worth the effort
+ // for a feature that was not present in the original game, and its
+ // implementation is buggy in the first place.
+ // Adjusting the restore rect properly fixes bug #3575276.
+
+ if (wmprect.top > pwnd->dims.top) {
pwnd->dims.moveTo(pwnd->dims.left, wmprect.top);
+ if (restoreRect)
+ pwnd->restoreRect.moveTo(pwnd->restoreRect.left, wmprect.top);
+ }
- if (wmprect.bottom < pwnd->dims.bottom)
+ if (wmprect.bottom < pwnd->dims.bottom) {
pwnd->dims.moveTo(pwnd->dims.left, wmprect.bottom - pwnd->dims.bottom + pwnd->dims.top);
+ if (restoreRect)
+ pwnd->restoreRect.moveTo(pwnd->restoreRect.left, wmprect.bottom - pwnd->restoreRect.bottom + pwnd->restoreRect.top);
+ }
- if (wmprect.right < pwnd->dims.right)
+ if (wmprect.right < pwnd->dims.right) {
pwnd->dims.moveTo(wmprect.right + pwnd->dims.left - pwnd->dims.right, pwnd->dims.top);
+ if (restoreRect)
+ pwnd->restoreRect.moveTo(wmprect.right + pwnd->restoreRect.left - pwnd->restoreRect.right, pwnd->restoreRect.top);
+ }
- if (wmprect.left > pwnd->dims.left)
+ if (wmprect.left > pwnd->dims.left) {
pwnd->dims.moveTo(wmprect.left, pwnd->dims.top);
+ if (restoreRect)
+ pwnd->restoreRect.moveTo(wmprect.left, pwnd->restoreRect.top);
+ }
pwnd->rect.moveTo(pwnd->rect.left + pwnd->dims.left - oldleft, pwnd->rect.top + pwnd->dims.top - oldtop);
diff --git a/engines/sci/graphics/screen.cpp b/engines/sci/graphics/screen.cpp
index 3030fb4386..246b6bfff9 100644
--- a/engines/sci/graphics/screen.cpp
+++ b/engines/sci/graphics/screen.cpp
@@ -53,23 +53,35 @@ GfxScreen::GfxScreen(ResourceManager *resMan) : _resMan(resMan) {
#ifdef ENABLE_SCI32
// GK1 Mac uses a 640x480 resolution too
- if (g_sci->getGameId() == GID_GK1 && g_sci->getPlatform() == Common::kPlatformMacintosh)
- _upscaledHires = GFX_SCREEN_UPSCALED_640x480;
+ if (g_sci->getPlatform() == Common::kPlatformMacintosh) {
+ if (g_sci->getGameId() == GID_GK1)
+ _upscaledHires = GFX_SCREEN_UPSCALED_640x480;
+ }
#endif
if (_resMan->detectHires()) {
_width = 640;
+ _pitch = 640;
_height = 480;
} else {
_width = 320;
+ _pitch = 320;
_height = getLowResScreenHeight();
}
+#ifdef ENABLE_SCI32
+ // Phantasmagoria 1 sets a window area of 630x450
+ if (g_sci->getGameId() == GID_PHANTASMAGORIA) {
+ _width = 630;
+ _height = 450;
+ }
+#endif
+
// Japanese versions of games use hi-res font on upscaled version of the game.
if ((g_sci->getLanguage() == Common::JA_JPN) && (getSciVersion() <= SCI_VERSION_1_1))
_upscaledHires = GFX_SCREEN_UPSCALED_640x400;
- _pixels = _width * _height;
+ _pixels = _pitch * _height;
switch (_upscaledHires) {
case GFX_SCREEN_UPSCALED_640x400:
@@ -91,19 +103,12 @@ GfxScreen::GfxScreen(ResourceManager *resMan) : _resMan(resMan) {
_upscaledMapping[i] = (i * 12) / 5;
break;
default:
- _displayWidth = _width;
+ _displayWidth = _pitch;
_displayHeight = _height;
memset(&_upscaledMapping, 0, sizeof(_upscaledMapping) );
break;
}
- // Phantasmagoria 1 sets a window area of 630x450
- if (g_sci->getGameId() == GID_PHANTASMAGORIA) {
- // TODO: Also set width to 630 (can't be set right now, as it messes up
- // the pitch). For now, a hack has been placed in GfxFrameout::kernelAddPlane()
- _height = 450;
- }
-
_displayPixels = _displayWidth * _displayHeight;
_visualScreen = (byte *)calloc(_pixels, 1);
_priorityScreen = (byte *)calloc(_pixels, 1);
@@ -214,7 +219,7 @@ byte GfxScreen::getDrawingMask(byte color, byte prio, byte control) {
}
void GfxScreen::putPixel(int x, int y, byte drawMask, byte color, byte priority, byte control) {
- int offset = y * _width + x;
+ int offset = y * _pitch + x;
if (drawMask & GFX_SCREEN_MASK_VISUAL) {
_visualScreen[offset] = color;
@@ -247,7 +252,7 @@ void GfxScreen::putFontPixel(int startingY, int x, int y, byte color) {
// Do not scale ourselves, but put it on the display directly
putPixelOnDisplay(x, y + startingY, color);
} else {
- int offset = (startingY + y) * _width + x;
+ int offset = (startingY + y) * _pitch + x;
_visualScreen[offset] = color;
if (!_upscaledHires) {
@@ -349,19 +354,19 @@ void GfxScreen::putKanjiChar(Graphics::FontSJIS *commonFont, int16 x, int16 y, u
}
byte GfxScreen::getVisual(int x, int y) {
- return _visualScreen[y * _width + x];
+ return _visualScreen[y * _pitch + x];
}
byte GfxScreen::getPriority(int x, int y) {
- return _priorityScreen[y * _width + x];
+ return _priorityScreen[y * _pitch + x];
}
byte GfxScreen::getControl(int x, int y) {
- return _controlScreen[y * _width + x];
+ return _controlScreen[y * _pitch + x];
}
byte GfxScreen::isFillMatch(int16 x, int16 y, byte screenMask, byte t_color, byte t_pri, byte t_con, bool isEGA) {
- int offset = y * _width + x;
+ int offset = y * _pitch + x;
byte match = 0;
if (screenMask & GFX_SCREEN_MASK_VISUAL) {
@@ -422,14 +427,14 @@ void GfxScreen::bitsSave(Common::Rect rect, byte mask, byte *memoryPtr) {
memcpy(memoryPtr, (void *)&mask, sizeof(mask)); memoryPtr += sizeof(mask);
if (mask & GFX_SCREEN_MASK_VISUAL) {
- bitsSaveScreen(rect, _visualScreen, _width, memoryPtr);
+ bitsSaveScreen(rect, _visualScreen, _pitch, memoryPtr);
bitsSaveDisplayScreen(rect, memoryPtr);
}
if (mask & GFX_SCREEN_MASK_PRIORITY) {
- bitsSaveScreen(rect, _priorityScreen, _width, memoryPtr);
+ bitsSaveScreen(rect, _priorityScreen, _pitch, memoryPtr);
}
if (mask & GFX_SCREEN_MASK_CONTROL) {
- bitsSaveScreen(rect, _controlScreen, _width, memoryPtr);
+ bitsSaveScreen(rect, _controlScreen, _pitch, memoryPtr);
}
if (mask & GFX_SCREEN_MASK_DISPLAY) {
if (!_upscaledHires)
@@ -482,14 +487,14 @@ void GfxScreen::bitsRestore(byte *memoryPtr) {
memcpy((void *)&mask, memoryPtr, sizeof(mask)); memoryPtr += sizeof(mask);
if (mask & GFX_SCREEN_MASK_VISUAL) {
- bitsRestoreScreen(rect, memoryPtr, _visualScreen, _width);
+ bitsRestoreScreen(rect, memoryPtr, _visualScreen, _pitch);
bitsRestoreDisplayScreen(rect, memoryPtr);
}
if (mask & GFX_SCREEN_MASK_PRIORITY) {
- bitsRestoreScreen(rect, memoryPtr, _priorityScreen, _width);
+ bitsRestoreScreen(rect, memoryPtr, _priorityScreen, _pitch);
}
if (mask & GFX_SCREEN_MASK_CONTROL) {
- bitsRestoreScreen(rect, memoryPtr, _controlScreen, _width);
+ bitsRestoreScreen(rect, memoryPtr, _controlScreen, _pitch);
}
if (mask & GFX_SCREEN_MASK_DISPLAY) {
if (!_upscaledHires)
@@ -567,7 +572,7 @@ void GfxScreen::dither(bool addToFlag) {
if (!_unditheringEnabled) {
// Do dithering on visual and display-screen
for (y = 0; y < _height; y++) {
- for (x = 0; x < _width; x++) {
+ for (x = 0; x < _pitch; x++) {
color = *visualPtr;
if (color & 0xF0) {
color ^= color << 4;
@@ -592,7 +597,7 @@ void GfxScreen::dither(bool addToFlag) {
memset(&_ditheredPicColors, 0, sizeof(_ditheredPicColors));
// Do dithering on visual screen and put decoded but undithered byte onto display-screen
for (y = 0; y < _height; y++) {
- for (x = 0; x < _width; x++) {
+ for (x = 0; x < _pitch; x++) {
color = *visualPtr;
if (color & 0xF0) {
color ^= color << 4;
diff --git a/engines/sci/graphics/screen.h b/engines/sci/graphics/screen.h
index 73ea596ba1..01fb899edb 100644
--- a/engines/sci/graphics/screen.h
+++ b/engines/sci/graphics/screen.h
@@ -132,6 +132,7 @@ public:
private:
uint16 _width;
+ uint16 _pitch;
uint16 _height;
uint _pixels;
uint16 _displayWidth;
diff --git a/engines/sci/graphics/view.cpp b/engines/sci/graphics/view.cpp
index 4e5c4da8b2..36aaae9232 100644
--- a/engines/sci/graphics/view.cpp
+++ b/engines/sci/graphics/view.cpp
@@ -741,8 +741,14 @@ void GfxView::draw(const Common::Rect &rect, const Common::Rect &clipRect, const
const int x2 = clipRectTranslated.left + x;
const int y2 = clipRectTranslated.top + y;
if (!upscaledHires) {
- if (priority >= _screen->getPriority(x2, y2))
- _screen->putPixel(x2, y2, drawMask, palette->mapping[color], priority, 0);
+ if (priority >= _screen->getPriority(x2, y2)) {
+ if (!_palette->isRemapped(palette->mapping[color])) {
+ _screen->putPixel(x2, y2, drawMask, palette->mapping[color], priority, 0);
+ } else {
+ byte remappedColor = _palette->remapColor(palette->mapping[color], _screen->getVisual(x2, y2));
+ _screen->putPixel(x2, y2, drawMask, remappedColor, priority, 0);
+ }
+ }
} else {
// UpscaledHires means view is hires and is supposed to
// get drawn onto lowres screen.
@@ -851,7 +857,12 @@ void GfxView::drawScaled(const Common::Rect &rect, const Common::Rect &clipRect,
const int x2 = clipRectTranslated.left + x;
const int y2 = clipRectTranslated.top + y;
if (color != clearKey && priority >= _screen->getPriority(x2, y2)) {
- _screen->putPixel(x2, y2, drawMask, palette->mapping[color], priority, 0);
+ if (!_palette->isRemapped(palette->mapping[color])) {
+ _screen->putPixel(x2, y2, drawMask, palette->mapping[color], priority, 0);
+ } else {
+ byte remappedColor = _palette->remapColor(palette->mapping[color], _screen->getVisual(x2, y2));
+ _screen->putPixel(x2, y2, drawMask, remappedColor, priority, 0);
+ }
}
}
}
diff --git a/engines/sci/sci.cpp b/engines/sci/sci.cpp
index d43a9d06fc..15b18ce8e6 100644
--- a/engines/sci/sci.cpp
+++ b/engines/sci/sci.cpp
@@ -632,7 +632,7 @@ void SciEngine::initGraphics() {
_gfxPaint = _gfxPaint32;
_gfxText32 = new GfxText32(_gamestate->_segMan, _gfxCache, _gfxScreen);
_gfxControls32 = new GfxControls32(_gamestate->_segMan, _gfxCache, _gfxScreen, _gfxText32);
- _robotDecoder = new RobotDecoder(g_system->getMixer(), getPlatform() == Common::kPlatformMacintosh);
+ _robotDecoder = new RobotDecoder(getPlatform() == Common::kPlatformMacintosh);
_gfxFrameout = new GfxFrameout(_gamestate->_segMan, _resMan, _gfxCoordAdjuster, _gfxCache, _gfxScreen, _gfxPalette, _gfxPaint32);
} else {
#endif
@@ -876,6 +876,7 @@ void SciEngine::syncIngameAudioOptions() {
if (getGameId() == GID_SQ4
|| getGameId() == GID_FREDDYPHARKAS
|| getGameId() == GID_ECOQUEST
+ || getGameId() == GID_LSL6
// TODO: The following need script patches for simultaneous speech and subtitles
//|| getGameId() == GID_KQ6
//|| getGameId() == GID_LAURABOW2
diff --git a/engines/sci/sci.h b/engines/sci/sci.h
index 9f18219cb7..3441e26c01 100644
--- a/engines/sci/sci.h
+++ b/engines/sci/sci.h
@@ -228,6 +228,26 @@ public:
bool canLoadGameStateCurrently();
bool canSaveGameStateCurrently();
void syncSoundSettings();
+
+ /**
+ * Syncs the audio options of the ScummVM launcher (speech, subtitles or
+ * both) with the in-game audio options of certain CD game versions. For
+ * some games, this allows simultaneous playing of speech and subtitles,
+ * even if the original games didn't support this feature.
+ *
+ * SCI1.1 games which support simultaneous speech and subtitles:
+ * - EcoQuest 1 CD
+ * - Leisure Suit Larry 6 CD
+ * SCI1.1 games which don't support simultaneous speech and subtitles,
+ * and we add this functionality in ScummVM:
+ * - Space Quest 4 CD
+ * - Freddy Pharkas CD
+ * SCI1.1 games which don't support simultaneous speech and subtitles,
+ * and we haven't added any extra functionality in ScummVM because extra
+ * script patches are needed:
+ * - Laura Bow 2 CD
+ * - King's Quest 6 CD
+ */
void syncIngameAudioOptions();
const SciGameId &getGameId() const { return _gameId; }
diff --git a/engines/sci/sound/audio.cpp b/engines/sci/sound/audio.cpp
index 123dd21894..528bb51393 100644
--- a/engines/sci/sound/audio.cpp
+++ b/engines/sci/sound/audio.cpp
@@ -67,7 +67,8 @@ int AudioPlayer::startAudio(uint16 module, uint32 number) {
if (audioStream) {
_wPlayFlag = false;
- _mixer->playStream(Audio::Mixer::kSpeechSoundType, &_audioHandle, audioStream);
+ Audio::Mixer::SoundType soundType = (module == 65535) ? Audio::Mixer::kSFXSoundType : Audio::Mixer::kSpeechSoundType;
+ _mixer->playStream(soundType, &_audioHandle, audioStream);
return sampleLen;
} else {
// Don't throw a warning in this case. getAudioStream() already has. Some games
diff --git a/engines/sci/sound/drivers/adlib.cpp b/engines/sci/sound/drivers/adlib.cpp
index db9317e071..191e13db0a 100644
--- a/engines/sci/sound/drivers/adlib.cpp
+++ b/engines/sci/sound/drivers/adlib.cpp
@@ -807,6 +807,9 @@ int MidiPlayer_AdLib::open(ResourceManager *resMan) {
int size = f.size();
const uint patchSize = 1344;
+ // Note: Funseeker's Guide also has another version of adl.drv, 8803 bytes.
+ // This isn't supported, but it's not really used anywhere, as that demo
+ // doesn't have sound anyway.
if ((size == 5684) || (size == 5720) || (size == 5727)) {
byte *buf = new byte[patchSize];
diff --git a/engines/sci/sound/drivers/fmtowns.cpp b/engines/sci/sound/drivers/fmtowns.cpp
index 6d8bb2e525..21cb2f1e43 100644
--- a/engines/sci/sound/drivers/fmtowns.cpp
+++ b/engines/sci/sound/drivers/fmtowns.cpp
@@ -52,8 +52,8 @@ public:
uint16 _duration;
private:
- uint8 _id;
- uint8 _velo;
+ uint8 _id;
+ uint8 _velo;
uint8 _program;
MidiDriver_FMTowns *_drv;
@@ -76,7 +76,7 @@ public:
void addChannels(int num);
void dropChannels(int num);
-
+
uint8 currentProgram() const;
private:
@@ -132,7 +132,7 @@ private:
TownsMidiPart **_parts;
TownsChannel **_out;
-
+
uint8 _masterVolume;
bool _soundOn;
@@ -590,7 +590,7 @@ void MidiDriver_FMTowns::addMissingChannels() {
avlChan -= _parts[i]->_chanMissing;
uint8 m = _parts[i]->_chanMissing;
_parts[i]->_chanMissing = 0;
- _parts[i]->addChannels(m);
+ _parts[i]->addChannels(m);
} else {
_parts[i]->_chanMissing -= avlChan;
_parts[i]->addChannels(avlChan);
@@ -601,7 +601,7 @@ void MidiDriver_FMTowns::addMissingChannels() {
void MidiDriver_FMTowns::updateParser() {
if (_timerProc)
- _timerProc(_timerProcPara);
+ _timerProc(_timerProcPara);
}
void MidiDriver_FMTowns::updateChannels() {
diff --git a/engines/sci/sound/midiparser_sci.cpp b/engines/sci/sound/midiparser_sci.cpp
index 422948f975..4e54797960 100644
--- a/engines/sci/sound/midiparser_sci.cpp
+++ b/engines/sci/sound/midiparser_sci.cpp
@@ -98,7 +98,7 @@ bool MidiParser_SCI::loadMusic(SoundResource::Track *track, MusicEntry *psnd, in
midiMixChannels();
}
- _num_tracks = 1;
+ _numTracks = 1;
_tracks[0] = _mixedData;
if (_pSnd)
setTrack(0);
@@ -144,7 +144,7 @@ byte *MidiParser_SCI::midiMixChannels() {
_mixedData = outData;
long ticker = 0;
byte channelNr, curDelta;
- byte midiCommand = 0, midiParam, global_prev = 0;
+ byte midiCommand = 0, midiParam, globalPrev = 0;
long newDelta;
SoundResource::Channel *channel;
@@ -190,13 +190,13 @@ byte *MidiParser_SCI::midiMixChannels() {
byte midiChannel = midiCommand & 0xF;
_channelUsed[midiChannel] = true;
- if (midiCommand != global_prev)
+ if (midiCommand != globalPrev)
*outData++ = midiCommand;
*outData++ = midiParam;
if (nMidiParams[(midiCommand >> 4) - 8] == 2)
*outData++ = channel->data[channel->curPos++];
channel->prev = midiCommand;
- global_prev = midiCommand;
+ globalPrev = midiCommand;
}
}
@@ -372,8 +372,8 @@ void MidiParser_SCI::unloadMusic() {
resetTracking();
allNotesOff();
}
- _num_tracks = 0;
- _active_track = 255;
+ _numTracks = 0;
+ _activeTrack = 255;
_resetOnPause = false;
if (_mixedData) {
@@ -454,26 +454,26 @@ void MidiParser_SCI::parseNextEvent(EventInfo &info) {
debugC(4, kDebugLevelSound, "signal %04x", _signalToSet);
}
- info.start = _position._play_pos;
+ info.start = _position._playPos;
info.delta = 0;
- while (*_position._play_pos == 0xF8) {
+ while (*_position._playPos == 0xF8) {
info.delta += 240;
- _position._play_pos++;
+ _position._playPos++;
}
- info.delta += *(_position._play_pos++);
+ info.delta += *(_position._playPos++);
// Process the next info.
- if ((_position._play_pos[0] & 0xF0) >= 0x80)
- info.event = *(_position._play_pos++);
+ if ((_position._playPos[0] & 0xF0) >= 0x80)
+ info.event = *(_position._playPos++);
else
- info.event = _position._running_status;
+ info.event = _position._runningStatus;
if (info.event < 0x80)
return;
- _position._running_status = info.event;
+ _position._runningStatus = info.event;
switch (info.command()) {
case 0xC:
- info.basic.param1 = *(_position._play_pos++);
+ info.basic.param1 = *(_position._playPos++);
info.basic.param2 = 0;
if (info.channel() == 0xF) {// SCI special case
if (info.basic.param1 != kSetSignalLoop) {
@@ -488,23 +488,23 @@ void MidiParser_SCI::parseNextEvent(EventInfo &info) {
// in glitches (e.g. the intro of LB1 Amiga gets stuck - bug
// #3297883). Refer to MusicEntry::setSignal() in sound/music.cpp.
if (_soundVersion <= SCI_VERSION_0_LATE ||
- _position._play_tick || info.delta) {
+ _position._playTick || info.delta) {
_signalSet = true;
_signalToSet = info.basic.param1;
}
} else {
- _loopTick = _position._play_tick + info.delta;
+ _loopTick = _position._playTick + info.delta;
}
}
break;
case 0xD:
- info.basic.param1 = *(_position._play_pos++);
+ info.basic.param1 = *(_position._playPos++);
info.basic.param2 = 0;
break;
case 0xB:
- info.basic.param1 = *(_position._play_pos++);
- info.basic.param2 = *(_position._play_pos++);
+ info.basic.param1 = *(_position._playPos++);
+ info.basic.param2 = *(_position._playPos++);
// Reference for some events:
// http://wiki.scummvm.org/index.php/SCI/Specifications/Sound/SCI0_Resource_Format#Status_Reference
@@ -588,8 +588,8 @@ void MidiParser_SCI::parseNextEvent(EventInfo &info) {
case 0x9:
case 0xA:
case 0xE:
- info.basic.param1 = *(_position._play_pos++);
- info.basic.param2 = *(_position._play_pos++);
+ info.basic.param1 = *(_position._playPos++);
+ info.basic.param2 = *(_position._playPos++);
if (info.command() == 0x9 && info.basic.param2 == 0)
info.event = info.channel() | 0x80;
info.length = 0;
@@ -598,12 +598,12 @@ void MidiParser_SCI::parseNextEvent(EventInfo &info) {
case 0xF: // System Common, Meta or SysEx event
switch (info.event & 0x0F) {
case 0x2: // Song Position Pointer
- info.basic.param1 = *(_position._play_pos++);
- info.basic.param2 = *(_position._play_pos++);
+ info.basic.param1 = *(_position._playPos++);
+ info.basic.param2 = *(_position._playPos++);
break;
case 0x3: // Song Select
- info.basic.param1 = *(_position._play_pos++);
+ info.basic.param1 = *(_position._playPos++);
info.basic.param2 = 0;
break;
@@ -617,16 +617,16 @@ void MidiParser_SCI::parseNextEvent(EventInfo &info) {
break;
case 0x0: // SysEx
- info.length = readVLQ(_position._play_pos);
- info.ext.data = _position._play_pos;
- _position._play_pos += info.length;
+ info.length = readVLQ(_position._playPos);
+ info.ext.data = _position._playPos;
+ _position._playPos += info.length;
break;
case 0xF: // META event
- info.ext.type = *(_position._play_pos++);
- info.length = readVLQ(_position._play_pos);
- info.ext.data = _position._play_pos;
- _position._play_pos += info.length;
+ info.ext.type = *(_position._playPos++);
+ info.length = readVLQ(_position._playPos);
+ info.ext.data = _position._playPos;
+ _position._playPos += info.length;
if (info.ext.type == 0x2F) {// end of track reached
if (_pSnd->loop)
_pSnd->loop--;
@@ -677,21 +677,21 @@ void MidiParser_SCI::allNotesOff() {
// Turn off all active notes
for (i = 0; i < 128; ++i) {
for (j = 0; j < 16; ++j) {
- if ((_active_notes[i] & (1 << j)) && (_channelRemap[j] != -1)){
+ if ((_activeNotes[i] & (1 << j)) && (_channelRemap[j] != -1)){
sendToDriver(0x80 | j, i, 0);
}
}
}
// Turn off all hanging notes
- for (i = 0; i < ARRAYSIZE(_hanging_notes); i++) {
- byte midiChannel = _hanging_notes[i].channel;
- if ((_hanging_notes[i].time_left) && (_channelRemap[midiChannel] != -1)) {
- sendToDriver(0x80 | midiChannel, _hanging_notes[i].note, 0);
- _hanging_notes[i].time_left = 0;
+ for (i = 0; i < ARRAYSIZE(_hangingNotes); i++) {
+ byte midiChannel = _hangingNotes[i].channel;
+ if ((_hangingNotes[i].timeLeft) && (_channelRemap[midiChannel] != -1)) {
+ sendToDriver(0x80 | midiChannel, _hangingNotes[i].note, 0);
+ _hangingNotes[i].timeLeft = 0;
}
}
- _hanging_notes_count = 0;
+ _hangingNotesCount = 0;
// To be sure, send an "All Note Off" event (but not all MIDI devices
// support this...).
@@ -703,7 +703,7 @@ void MidiParser_SCI::allNotesOff() {
}
}
- memset(_active_notes, 0, sizeof(_active_notes));
+ memset(_activeNotes, 0, sizeof(_activeNotes));
}
void MidiParser_SCI::setMasterVolume(byte masterVolume) {
diff --git a/engines/sci/sound/midiparser_sci.h b/engines/sci/sound/midiparser_sci.h
index 82f34070a4..d3fd337644 100644
--- a/engines/sci/sound/midiparser_sci.h
+++ b/engines/sci/sound/midiparser_sci.h
@@ -65,7 +65,7 @@ public:
void setMasterVolume(byte masterVolume);
void setVolume(byte volume);
void stop() {
- _abort_parse = true;
+ _abortParse = true;
allNotesOff();
}
void pause() {
diff --git a/engines/sci/sound/music.cpp b/engines/sci/sound/music.cpp
index 918b045cb9..a8a65d2aa4 100644
--- a/engines/sci/sound/music.cpp
+++ b/engines/sci/sound/music.cpp
@@ -125,7 +125,13 @@ void SciMusic::init() {
_pMidiDrv->setTimerCallback(this, &miditimerCallback);
_dwTempo = _pMidiDrv->getBaseTempo();
} else {
- error("Failed to initialize sound driver");
+ if (g_sci->getGameId() == GID_FUNSEEKER) {
+ // HACK: The Fun Seeker's Guide demo doesn't have patch 3 and the version
+ // of the Adlib driver (adl.drv) that it includes is unsupported. That demo
+ // doesn't have any sound anyway, so this shouldn't be fatal.
+ } else {
+ error("Failed to initialize sound driver");
+ }
}
// Find out what the first possible channel is (used, when doing channel
diff --git a/engines/sci/sound/soundcmd.cpp b/engines/sci/sound/soundcmd.cpp
index cbb5cab4fe..5d32f40f18 100644
--- a/engines/sci/sound/soundcmd.cpp
+++ b/engines/sci/sound/soundcmd.cpp
@@ -96,7 +96,7 @@ void SoundCommandParser::initSoundResource(MusicEntry *newSound) {
if (_useDigitalSFX || !newSound->soundRes) {
int sampleLen;
newSound->pStreamAud = _audio->getAudioStream(newSound->resourceId, 65535, &sampleLen);
- newSound->soundType = Audio::Mixer::kSpeechSoundType;
+ newSound->soundType = Audio::Mixer::kSFXSoundType;
}
}
@@ -367,29 +367,36 @@ 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
- 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)
- 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;
-
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)
- musicSlot->stopAfterFading = (argv[4].toUint16() != 0);
+ // 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);
+ }
+
+ 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
diff --git a/engines/sci/video/robot_decoder.cpp b/engines/sci/video/robot_decoder.cpp
index ebcfac6054..0337a8d306 100644
--- a/engines/sci/video/robot_decoder.cpp
+++ b/engines/sci/video/robot_decoder.cpp
@@ -22,11 +22,13 @@
#include "common/archive.h"
#include "common/stream.h"
+#include "common/substream.h"
#include "common/system.h"
#include "common/textconsole.h"
#include "common/util.h"
#include "graphics/surface.h"
+#include "audio/audiostream.h"
#include "audio/decoders/raw.h"
#include "sci/resource.h"
@@ -63,57 +65,26 @@ namespace Sci {
// our graphics engine, it looks just like a part of the room. A RBT can move
// around the screen and go behind other objects. (...)
-#ifdef ENABLE_SCI32
-
-enum robotPalTypes {
+enum RobotPalTypes {
kRobotPalVariable = 0,
kRobotPalConstant = 1
};
-RobotDecoder::RobotDecoder(Audio::Mixer *mixer, bool isBigEndian) {
- _surface = 0;
- _width = 0;
- _height = 0;
+RobotDecoder::RobotDecoder(bool isBigEndian) {
_fileStream = 0;
- _audioStream = 0;
- _dirtyPalette = false;
_pos = Common::Point(0, 0);
- _mixer = mixer;
_isBigEndian = isBigEndian;
+ _frameTotalSize = 0;
}
RobotDecoder::~RobotDecoder() {
close();
}
-bool RobotDecoder::load(GuiResourceId id) {
- // TODO: RAMA's robot 1003 cannot be played (shown at the menu screen) -
- // its drawn at odd coordinates. SV can't play it either (along with some
- // others), so it must be some new functionality added in RAMA's robot
- // videos. Skip it for now.
- if (g_sci->getGameId() == GID_RAMA && id == 1003)
- return false;
-
- // TODO: The robot video in the Lighthouse demo gets stuck
- if (g_sci->getGameId() == GID_LIGHTHOUSE && id == 16)
- return false;
-
- Common::String fileName = Common::String::format("%d.rbt", id);
- Common::SeekableReadStream *stream = SearchMan.createReadStreamForMember(fileName);
-
- if (!stream) {
- warning("Unable to open robot file %s", fileName.c_str());
- return false;
- }
-
- return loadStream(stream);
-}
-
bool RobotDecoder::loadStream(Common::SeekableReadStream *stream) {
close();
_fileStream = new Common::SeekableSubReadStreamEndian(stream, 0, stream->size(), _isBigEndian, DisposeAfterUse::YES);
- _surface = new Graphics::Surface();
readHeaderChunk();
@@ -125,131 +96,60 @@ bool RobotDecoder::loadStream(Common::SeekableReadStream *stream) {
if (_header.version < 4 || _header.version > 6)
error("Unknown robot version: %d", _header.version);
- if (_header.hasSound) {
- _audioStream = Audio::makeQueuingAudioStream(11025, false);
- _mixer->playStream(Audio::Mixer::kMusicSoundType, &_audioHandle, _audioStream, -1, getVolume(), getBalance());
- }
+ RobotVideoTrack *videoTrack = new RobotVideoTrack(_header.frameCount);
+ addTrack(videoTrack);
- readPaletteChunk(_header.paletteDataSize);
- readFrameSizesChunk();
- calculateVideoDimensions();
- _surface->create(_width, _height, Graphics::PixelFormat::createFormatCLUT8());
+ if (_header.hasSound)
+ addTrack(new RobotAudioTrack());
+ videoTrack->readPaletteChunk(_fileStream, _header.paletteDataSize);
+ readFrameSizesChunk();
+ videoTrack->calculateVideoDimensions(_fileStream, _frameTotalSize);
return true;
}
-void RobotDecoder::readHeaderChunk() {
- // Header (60 bytes)
- _fileStream->skip(6);
- _header.version = _fileStream->readUint16();
- _header.audioChunkSize = _fileStream->readUint16();
- _header.audioSilenceSize = _fileStream->readUint16();
- _fileStream->skip(2);
- _header.frameCount = _fileStream->readUint16();
- _header.paletteDataSize = _fileStream->readUint16();
- _header.unkChunkDataSize = _fileStream->readUint16();
- _fileStream->skip(5);
- _header.hasSound = _fileStream->readByte();
- _fileStream->skip(34);
-
- // Some videos (e.g. robot 1305 in Phantasmagoria and
- // robot 184 in Lighthouse) have an unknown chunk before
- // the palette chunk (probably used for sound preloading).
- // Skip it here.
- if (_header.unkChunkDataSize)
- _fileStream->skip(_header.unkChunkDataSize);
-}
-
-void RobotDecoder::readPaletteChunk(uint16 chunkSize) {
- byte *paletteData = new byte[chunkSize];
- _fileStream->read(paletteData, chunkSize);
+bool RobotDecoder::load(GuiResourceId id) {
+ // TODO: RAMA's robot 1003 cannot be played (shown at the menu screen) -
+ // its drawn at odd coordinates. SV can't play it either (along with some
+ // others), so it must be some new functionality added in RAMA's robot
+ // videos. Skip it for now.
+ if (g_sci->getGameId() == GID_RAMA && id == 1003)
+ return false;
- // SCI1.1 palette
- byte palFormat = paletteData[32];
- uint16 palColorStart = paletteData[25];
- uint16 palColorCount = READ_SCI11ENDIAN_UINT16(paletteData + 29);
+ // TODO: The robot video in the Lighthouse demo gets stuck
+ if (g_sci->getGameId() == GID_LIGHTHOUSE && id == 16)
+ return false;
- int palOffset = 37;
- memset(_palette, 0, 256 * 3);
+ Common::String fileName = Common::String::format("%d.rbt", id);
+ Common::SeekableReadStream *stream = SearchMan.createReadStreamForMember(fileName);
- for (uint16 colorNo = palColorStart; colorNo < palColorStart + palColorCount; colorNo++) {
- if (palFormat == kRobotPalVariable)
- palOffset++;
- _palette[colorNo * 3 + 0] = paletteData[palOffset++];
- _palette[colorNo * 3 + 1] = paletteData[palOffset++];
- _palette[colorNo * 3 + 2] = paletteData[palOffset++];
+ if (!stream) {
+ warning("Unable to open robot file %s", fileName.c_str());
+ return false;
}
- _dirtyPalette = true;
- delete[] paletteData;
+ return loadStream(stream);
}
+void RobotDecoder::close() {
+ VideoDecoder::close();
-void RobotDecoder::readFrameSizesChunk() {
- // The robot video file contains 2 tables, with one entry for each frame:
- // - A table containing the size of the image in each video frame
- // - A table containing the total size of each video frame.
- // In v5 robots, the tables contain 16-bit integers, whereas in v6 robots,
- // they contain 32-bit integers.
-
- _frameTotalSize = new uint32[_header.frameCount];
-
- // TODO: The table reading code can probably be removed once the
- // audio chunk size is figured out (check the TODO inside processNextFrame())
-#if 0
- // We don't need any of the two tables to play the video, so we ignore
- // both of them.
- uint16 wordSize = _header.version == 6 ? 4 : 2;
- _fileStream->skip(_header.frameCount * wordSize * 2);
-#else
- switch (_header.version) {
- case 4:
- case 5: // sizes are 16-bit integers
- // Skip table with frame image sizes, as we don't need it
- _fileStream->skip(_header.frameCount * 2);
- for (int i = 0; i < _header.frameCount; ++i)
- _frameTotalSize[i] = _fileStream->readUint16();
- break;
- case 6: // sizes are 32-bit integers
- // Skip table with frame image sizes, as we don't need it
- _fileStream->skip(_header.frameCount * 4);
- for (int i = 0; i < _header.frameCount; ++i)
- _frameTotalSize[i] = _fileStream->readUint32();
- break;
- default:
- error("Can't yet handle index table for robot version %d", _header.version);
- }
-#endif
-
- // 2 more unknown tables
- _fileStream->skip(1024 + 512);
+ delete _fileStream;
+ _fileStream = 0;
- // Pad to nearest 2 kilobytes
- uint32 curPos = _fileStream->pos();
- if (curPos & 0x7ff)
- _fileStream->seek((curPos & ~0x7ff) + 2048);
+ delete[] _frameTotalSize;
+ _frameTotalSize = 0;
}
-void RobotDecoder::calculateVideoDimensions() {
- // This is an O(n) operation, as each frame has a different size.
- // We need to know the actual frame size to have a constant video size.
- uint32 pos = _fileStream->pos();
-
- for (uint32 curFrame = 0; curFrame < _header.frameCount; curFrame++) {
- _fileStream->skip(4);
- uint16 frameWidth = _fileStream->readUint16();
- uint16 frameHeight = _fileStream->readUint16();
- if (frameWidth > _width)
- _width = frameWidth;
- if (frameHeight > _height)
- _height = frameHeight;
- _fileStream->skip(_frameTotalSize[curFrame] - 8);
- }
+void RobotDecoder::readNextPacket() {
+ // Get our track
+ RobotVideoTrack *videoTrack = (RobotVideoTrack *)getTrack(0);
+ videoTrack->increaseCurFrame();
+ Graphics::Surface *surface = videoTrack->getSurface();
- _fileStream->seek(pos);
-}
+ if (videoTrack->endOfTrack())
+ return;
-const Graphics::Surface *RobotDecoder::decodeNextFrame() {
// Read frame image header (24 bytes)
_fileStream->skip(3);
byte frameScale = _fileStream->readByte();
@@ -258,23 +158,28 @@ const Graphics::Surface *RobotDecoder::decodeNextFrame() {
_fileStream->skip(4); // unknown, almost always 0
uint16 frameX = _fileStream->readUint16();
uint16 frameY = _fileStream->readUint16();
+
// TODO: In v4 robot files, frameX and frameY have a different meaning.
// Set them both to 0 for v4 for now, so that robots in PQ:SWAT show up
// correctly.
if (_header.version == 4)
frameX = frameY = 0;
+
uint16 compressedSize = _fileStream->readUint16();
uint16 frameFragments = _fileStream->readUint16();
_fileStream->skip(4); // unknown
uint32 decompressedSize = frameWidth * frameHeight * frameScale / 100;
+
// FIXME: A frame's height + position can go off limits... why? With the
// following, we cut the contents to fit the frame
- uint16 scaledHeight = CLIP<uint16>(decompressedSize / frameWidth, 0, _height - frameY);
+ uint16 scaledHeight = CLIP<uint16>(decompressedSize / frameWidth, 0, surface->h - frameY);
+
// FIXME: Same goes for the frame's width + position. In this case, we
// modify the position to fit the contents on screen.
- if (frameWidth + frameX > _width)
- frameX = _width - frameWidth;
- assert (frameWidth + frameX <= _width && scaledHeight + frameY <= _height);
+ if (frameWidth + frameX > surface->w)
+ frameX = surface->w - frameWidth;
+
+ assert(frameWidth + frameX <= surface->w && scaledHeight + frameY <= surface->h);
DecompressorLZS lzs;
byte *decompressedFrame = new byte[decompressedSize];
@@ -305,24 +210,23 @@ const Graphics::Surface *RobotDecoder::decodeNextFrame() {
// Copy over the decompressed frame
byte *inFrame = decompressedFrame;
- byte *outFrame = (byte *)_surface->pixels;
+ byte *outFrame = (byte *)surface->pixels;
// Black out the surface
- memset(outFrame, 0, _width * _height);
+ memset(outFrame, 0, surface->w * surface->h);
// Move to the correct y coordinate
- outFrame += _width * frameY;
+ outFrame += surface->w * frameY;
for (uint16 y = 0; y < scaledHeight; y++) {
memcpy(outFrame + frameX, inFrame, frameWidth);
inFrame += frameWidth;
- outFrame += _width;
+ outFrame += surface->w;
}
delete[] decompressedFrame;
- // +1 because we start with frame number -1
- uint32 audioChunkSize = _frameTotalSize[_curFrame + 1] - (24 + compressedSize);
+ uint32 audioChunkSize = _frameTotalSize[videoTrack->getCurFrame()] - (24 + compressedSize);
// TODO: The audio chunk size below is usually correct, but there are some
// exceptions (e.g. robot 4902 in Phantasmagoria, towards its end)
@@ -337,51 +241,166 @@ const Graphics::Surface *RobotDecoder::decodeNextFrame() {
// Queue the next audio frame
// FIXME: For some reason, there are audio hiccups/gaps
if (_header.hasSound) {
- _fileStream->skip(8); // header
- _audioStream->queueBuffer(g_sci->_audio->getDecodedRobotAudioFrame(_fileStream, audioChunkSize - 8),
- (audioChunkSize - 8) * 2, DisposeAfterUse::NO,
- Audio::FLAG_16BITS | Audio::FLAG_LITTLE_ENDIAN);
+ RobotAudioTrack *audioTrack = (RobotAudioTrack *)getTrack(1);
+ _fileStream->skip(8); // header
+ audioChunkSize -= 8;
+ audioTrack->queueBuffer(g_sci->_audio->getDecodedRobotAudioFrame(_fileStream, audioChunkSize), audioChunkSize * 2);
} else {
_fileStream->skip(audioChunkSize);
}
+}
- if (_curFrame == -1)
- _startTime = g_system->getMillis();
-
- _curFrame++;
+void RobotDecoder::readHeaderChunk() {
+ // Header (60 bytes)
+ _fileStream->skip(6);
+ _header.version = _fileStream->readUint16();
+ _header.audioChunkSize = _fileStream->readUint16();
+ _header.audioSilenceSize = _fileStream->readUint16();
+ _fileStream->skip(2);
+ _header.frameCount = _fileStream->readUint16();
+ _header.paletteDataSize = _fileStream->readUint16();
+ _header.unkChunkDataSize = _fileStream->readUint16();
+ _fileStream->skip(5);
+ _header.hasSound = _fileStream->readByte();
+ _fileStream->skip(34);
- return _surface;
+ // Some videos (e.g. robot 1305 in Phantasmagoria and
+ // robot 184 in Lighthouse) have an unknown chunk before
+ // the palette chunk (probably used for sound preloading).
+ // Skip it here.
+ if (_header.unkChunkDataSize)
+ _fileStream->skip(_header.unkChunkDataSize);
}
-void RobotDecoder::close() {
- if (!_fileStream)
- return;
+void RobotDecoder::readFrameSizesChunk() {
+ // The robot video file contains 2 tables, with one entry for each frame:
+ // - A table containing the size of the image in each video frame
+ // - A table containing the total size of each video frame.
+ // In v5 robots, the tables contain 16-bit integers, whereas in v6 robots,
+ // they contain 32-bit integers.
- delete _fileStream;
- _fileStream = 0;
+ _frameTotalSize = new uint32[_header.frameCount];
+
+ // TODO: The table reading code can probably be removed once the
+ // audio chunk size is figured out (check the TODO inside processNextFrame())
+#if 0
+ // We don't need any of the two tables to play the video, so we ignore
+ // both of them.
+ uint16 wordSize = _header.version == 6 ? 4 : 2;
+ _fileStream->skip(_header.frameCount * wordSize * 2);
+#else
+ switch (_header.version) {
+ case 4:
+ case 5: // sizes are 16-bit integers
+ // Skip table with frame image sizes, as we don't need it
+ _fileStream->skip(_header.frameCount * 2);
+ for (int i = 0; i < _header.frameCount; ++i)
+ _frameTotalSize[i] = _fileStream->readUint16();
+ break;
+ case 6: // sizes are 32-bit integers
+ // Skip table with frame image sizes, as we don't need it
+ _fileStream->skip(_header.frameCount * 4);
+ for (int i = 0; i < _header.frameCount; ++i)
+ _frameTotalSize[i] = _fileStream->readUint32();
+ break;
+ default:
+ error("Can't yet handle index table for robot version %d", _header.version);
+ }
+#endif
+
+ // 2 more unknown tables
+ _fileStream->skip(1024 + 512);
+ // Pad to nearest 2 kilobytes
+ uint32 curPos = _fileStream->pos();
+ if (curPos & 0x7ff)
+ _fileStream->seek((curPos & ~0x7ff) + 2048);
+}
+
+RobotDecoder::RobotVideoTrack::RobotVideoTrack(int frameCount) : _frameCount(frameCount) {
+ _surface = new Graphics::Surface();
+ _curFrame = -1;
+ _dirtyPalette = false;
+}
+
+RobotDecoder::RobotVideoTrack::~RobotVideoTrack() {
_surface->free();
delete _surface;
- _surface = 0;
+}
- if (_header.hasSound) {
- _mixer->stopHandle(_audioHandle);
- //delete _audioStream; _audioStream = 0;
+uint16 RobotDecoder::RobotVideoTrack::getWidth() const {
+ return _surface->w;
+}
+
+uint16 RobotDecoder::RobotVideoTrack::getHeight() const {
+ return _surface->h;
+}
+
+Graphics::PixelFormat RobotDecoder::RobotVideoTrack::getPixelFormat() const {
+ return _surface->format;
+}
+
+void RobotDecoder::RobotVideoTrack::readPaletteChunk(Common::SeekableSubReadStreamEndian *stream, uint16 chunkSize) {
+ byte *paletteData = new byte[chunkSize];
+ stream->read(paletteData, chunkSize);
+
+ // SCI1.1 palette
+ byte palFormat = paletteData[32];
+ uint16 palColorStart = paletteData[25];
+ uint16 palColorCount = READ_SCI11ENDIAN_UINT16(paletteData + 29);
+
+ int palOffset = 37;
+ memset(_palette, 0, 256 * 3);
+
+ for (uint16 colorNo = palColorStart; colorNo < palColorStart + palColorCount; colorNo++) {
+ if (palFormat == kRobotPalVariable)
+ palOffset++;
+ _palette[colorNo * 3 + 0] = paletteData[palOffset++];
+ _palette[colorNo * 3 + 1] = paletteData[palOffset++];
+ _palette[colorNo * 3 + 2] = paletteData[palOffset++];
}
- reset();
+ _dirtyPalette = true;
+ delete[] paletteData;
}
-void RobotDecoder::updateVolume() {
- if (g_system->getMixer()->isSoundHandleActive(_audioHandle))
- g_system->getMixer()->setChannelVolume(_audioHandle, getVolume());
+void RobotDecoder::RobotVideoTrack::calculateVideoDimensions(Common::SeekableSubReadStreamEndian *stream, uint32 *frameSizes) {
+ // This is an O(n) operation, as each frame has a different size.
+ // We need to know the actual frame size to have a constant video size.
+ uint32 pos = stream->pos();
+
+ uint16 width = 0, height = 0;
+
+ for (int curFrame = 0; curFrame < _frameCount; curFrame++) {
+ stream->skip(4);
+ uint16 frameWidth = stream->readUint16();
+ uint16 frameHeight = stream->readUint16();
+ if (frameWidth > width)
+ width = frameWidth;
+ if (frameHeight > height)
+ height = frameHeight;
+ stream->skip(frameSizes[curFrame] - 8);
+ }
+
+ stream->seek(pos);
+
+ _surface->create(width, height, Graphics::PixelFormat::createFormatCLUT8());
}
-void RobotDecoder::updateBalance() {
- if (g_system->getMixer()->isSoundHandleActive(_audioHandle))
- g_system->getMixer()->setChannelBalance(_audioHandle, getBalance());
+RobotDecoder::RobotAudioTrack::RobotAudioTrack() {
+ _audioStream = Audio::makeQueuingAudioStream(11025, false);
}
-#endif
+RobotDecoder::RobotAudioTrack::~RobotAudioTrack() {
+ delete _audioStream;
+}
+
+void RobotDecoder::RobotAudioTrack::queueBuffer(byte *buffer, int size) {
+ _audioStream->queueBuffer(buffer, size, DisposeAfterUse::YES, Audio::FLAG_16BITS | Audio::FLAG_LITTLE_ENDIAN);
+}
+
+Audio::AudioStream *RobotDecoder::RobotAudioTrack::getAudioStream() const {
+ return _audioStream;
+}
} // End of namespace Sci
diff --git a/engines/sci/video/robot_decoder.h b/engines/sci/video/robot_decoder.h
index e9cefe7d91..437954f7fb 100644
--- a/engines/sci/video/robot_decoder.h
+++ b/engines/sci/video/robot_decoder.h
@@ -25,84 +25,103 @@
#include "common/rational.h"
#include "common/rect.h"
-#include "common/stream.h"
-#include "common/substream.h"
-#include "audio/audiostream.h"
-#include "audio/mixer.h"
-#include "graphics/pixelformat.h"
#include "video/video_decoder.h"
-namespace Sci {
+namespace Audio {
+class QueuingAudioStream;
+}
-#ifdef ENABLE_SCI32
-
-struct RobotHeader {
- // 6 bytes, identifier bytes
- uint16 version;
- uint16 audioChunkSize;
- uint16 audioSilenceSize;
- // 2 bytes, unknown
- uint16 frameCount;
- uint16 paletteDataSize;
- uint16 unkChunkDataSize;
- // 5 bytes, unknown
- byte hasSound;
- // 34 bytes, unknown
-};
+namespace Common {
+class SeekableSubReadStreamEndian;
+}
+
+namespace Sci {
-class RobotDecoder : public Video::FixedRateVideoDecoder {
+class RobotDecoder : public Video::VideoDecoder {
public:
- RobotDecoder(Audio::Mixer *mixer, bool isBigEndian);
+ RobotDecoder(bool isBigEndian);
virtual ~RobotDecoder();
bool loadStream(Common::SeekableReadStream *stream);
bool load(GuiResourceId id);
void close();
- bool isVideoLoaded() const { return _fileStream != 0; }
- uint16 getWidth() const { return _width; }
- uint16 getHeight() const { return _height; }
- uint32 getFrameCount() const { return _header.frameCount; }
- const Graphics::Surface *decodeNextFrame();
- Graphics::PixelFormat getPixelFormat() const { return Graphics::PixelFormat::createFormatCLUT8(); }
- const byte *getPalette() { _dirtyPalette = false; return _palette; }
- bool hasDirtyPalette() const { return _dirtyPalette; }
void setPos(uint16 x, uint16 y) { _pos = Common::Point(x, y); }
Common::Point getPos() const { return _pos; }
protected:
- // VideoDecoder API
- void updateVolume();
- void updateBalance();
-
- // FixedRateVideoDecoder API
- Common::Rational getFrameRate() const { return Common::Rational(60, 10); }
+ void readNextPacket();
private:
+ class RobotVideoTrack : public FixedRateVideoTrack {
+ public:
+ RobotVideoTrack(int frameCount);
+ ~RobotVideoTrack();
+
+ uint16 getWidth() const;
+ uint16 getHeight() const;
+ Graphics::PixelFormat getPixelFormat() const;
+ int getCurFrame() const { return _curFrame; }
+ int getFrameCount() const { return _frameCount; }
+ const Graphics::Surface *decodeNextFrame() { return _surface; }
+ const byte *getPalette() const { _dirtyPalette = false; return _palette; }
+ bool hasDirtyPalette() const { return _dirtyPalette; }
+
+ void readPaletteChunk(Common::SeekableSubReadStreamEndian *stream, uint16 chunkSize);
+ void calculateVideoDimensions(Common::SeekableSubReadStreamEndian *stream, uint32 *frameSizes);
+ Graphics::Surface *getSurface() { return _surface; }
+ void increaseCurFrame() { _curFrame++; }
+
+ protected:
+ Common::Rational getFrameRate() const { return Common::Rational(60, 10); }
+
+ private:
+ int _frameCount;
+ int _curFrame;
+ byte _palette[256 * 3];
+ mutable bool _dirtyPalette;
+ Graphics::Surface *_surface;
+ };
+
+ class RobotAudioTrack : public AudioTrack {
+ public:
+ RobotAudioTrack();
+ ~RobotAudioTrack();
+
+ Audio::Mixer::SoundType getSoundType() const { return Audio::Mixer::kMusicSoundType; }
+
+ void queueBuffer(byte *buffer, int size);
+
+ protected:
+ Audio::AudioStream *getAudioStream() const;
+
+ private:
+ Audio::QueuingAudioStream *_audioStream;
+ };
+
+ struct RobotHeader {
+ // 6 bytes, identifier bytes
+ uint16 version;
+ uint16 audioChunkSize;
+ uint16 audioSilenceSize;
+ // 2 bytes, unknown
+ uint16 frameCount;
+ uint16 paletteDataSize;
+ uint16 unkChunkDataSize;
+ // 5 bytes, unknown
+ byte hasSound;
+ // 34 bytes, unknown
+ } _header;
+
void readHeaderChunk();
- void readPaletteChunk(uint16 chunkSize);
void readFrameSizesChunk();
- void calculateVideoDimensions();
- void freeData();
-
- RobotHeader _header;
Common::Point _pos;
bool _isBigEndian;
+ uint32 *_frameTotalSize;
Common::SeekableSubReadStreamEndian *_fileStream;
-
- uint16 _width;
- uint16 _height;
- uint32 *_frameTotalSize;
- byte _palette[256 * 3];
- bool _dirtyPalette;
- Graphics::Surface *_surface;
- Audio::QueuingAudioStream *_audioStream;
- Audio::SoundHandle _audioHandle;
- Audio::Mixer *_mixer;
};
-#endif
} // End of namespace Sci
diff --git a/engines/sci/video/seq_decoder.cpp b/engines/sci/video/seq_decoder.cpp
index abd64911a7..a7b6346eca 100644
--- a/engines/sci/video/seq_decoder.cpp
+++ b/engines/sci/video/seq_decoder.cpp
@@ -41,33 +41,44 @@ enum seqFrameTypes {
kSeqFrameDiff = 1
};
-SeqDecoder::SeqDecoder() {
- _fileStream = 0;
- _surface = 0;
- _dirtyPalette = false;
+SEQDecoder::SEQDecoder(uint frameDelay) : _frameDelay(frameDelay) {
}
-SeqDecoder::~SeqDecoder() {
+SEQDecoder::~SEQDecoder() {
close();
}
-bool SeqDecoder::loadStream(Common::SeekableReadStream *stream) {
+bool SEQDecoder::loadStream(Common::SeekableReadStream *stream) {
close();
+ addTrack(new SEQVideoTrack(stream, _frameDelay));
+
+ return true;
+}
+
+SEQDecoder::SEQVideoTrack::SEQVideoTrack(Common::SeekableReadStream *stream, uint frameDelay) {
+ assert(stream);
+ assert(frameDelay != 0);
_fileStream = stream;
+ _frameDelay = frameDelay;
+ _curFrame = -1;
+
_surface = new Graphics::Surface();
_surface->create(SEQ_SCREEN_WIDTH, SEQ_SCREEN_HEIGHT, Graphics::PixelFormat::createFormatCLUT8());
_frameCount = _fileStream->readUint16LE();
- // Set palette
- int paletteChunkSize = _fileStream->readUint32LE();
- readPaletteChunk(paletteChunkSize);
+ // Set initial palette
+ readPaletteChunk(_fileStream->readUint32LE());
+}
- return true;
+SEQDecoder::SEQVideoTrack::~SEQVideoTrack() {
+ delete _fileStream;
+ _surface->free();
+ delete _surface;
}
-void SeqDecoder::readPaletteChunk(uint16 chunkSize) {
+void SEQDecoder::SEQVideoTrack::readPaletteChunk(uint16 chunkSize) {
byte *paletteData = new byte[chunkSize];
_fileStream->read(paletteData, chunkSize);
@@ -91,23 +102,7 @@ void SeqDecoder::readPaletteChunk(uint16 chunkSize) {
delete[] paletteData;
}
-void SeqDecoder::close() {
- if (!_fileStream)
- return;
-
- _frameDelay = 0;
-
- delete _fileStream;
- _fileStream = 0;
-
- _surface->free();
- delete _surface;
- _surface = 0;
-
- reset();
-}
-
-const Graphics::Surface *SeqDecoder::decodeNextFrame() {
+const Graphics::Surface *SEQDecoder::SEQVideoTrack::decodeNextFrame() {
int16 frameWidth = _fileStream->readUint16LE();
int16 frameHeight = _fileStream->readUint16LE();
int16 frameLeft = _fileStream->readUint16LE();
@@ -142,9 +137,6 @@ const Graphics::Surface *SeqDecoder::decodeNextFrame() {
delete[] buf;
}
- if (_curFrame == -1)
- _startTime = g_system->getMillis();
-
_curFrame++;
return _surface;
}
@@ -159,7 +151,7 @@ const Graphics::Surface *SeqDecoder::decodeNextFrame() {
} \
memcpy(dest + writeRow * SEQ_SCREEN_WIDTH + writeCol, litData + litPos, n);
-bool SeqDecoder::decodeFrame(byte *rleData, int rleSize, byte *litData, int litSize, byte *dest, int left, int width, int height, int colorKey) {
+bool SEQDecoder::SEQVideoTrack::decodeFrame(byte *rleData, int rleSize, byte *litData, int litSize, byte *dest, int left, int width, int height, int colorKey) {
int writeRow = 0;
int writeCol = left;
int litPos = 0;
@@ -237,4 +229,9 @@ bool SeqDecoder::decodeFrame(byte *rleData, int rleSize, byte *litData, int litS
return true;
}
+const byte *SEQDecoder::SEQVideoTrack::getPalette() const {
+ _dirtyPalette = false;
+ return _palette;
+}
+
} // End of namespace Sci
diff --git a/engines/sci/video/seq_decoder.h b/engines/sci/video/seq_decoder.h
index 800a3c9024..890f349feb 100644
--- a/engines/sci/video/seq_decoder.h
+++ b/engines/sci/video/seq_decoder.h
@@ -40,44 +40,49 @@ namespace Sci {
/**
* Implementation of the Sierra SEQ decoder, used in KQ6 DOS floppy/CD and GK1 DOS
*/
-class SeqDecoder : public Video::FixedRateVideoDecoder {
+class SEQDecoder : public Video::VideoDecoder {
public:
- SeqDecoder();
- virtual ~SeqDecoder();
+ SEQDecoder(uint frameDelay);
+ virtual ~SEQDecoder();
bool loadStream(Common::SeekableReadStream *stream);
- void close();
-
- void setFrameDelay(int frameDelay) { _frameDelay = frameDelay; }
-
- bool isVideoLoaded() const { return _fileStream != 0; }
- uint16 getWidth() const { return SEQ_SCREEN_WIDTH; }
- uint16 getHeight() const { return SEQ_SCREEN_HEIGHT; }
- uint32 getFrameCount() const { return _frameCount; }
- const Graphics::Surface *decodeNextFrame();
- Graphics::PixelFormat getPixelFormat() const { return Graphics::PixelFormat::createFormatCLUT8(); }
- const byte *getPalette() { _dirtyPalette = false; return _palette; }
- bool hasDirtyPalette() const { return _dirtyPalette; }
-
-protected:
- Common::Rational getFrameRate() const { assert(_frameDelay); return Common::Rational(60, _frameDelay); }
private:
- enum {
- SEQ_SCREEN_WIDTH = 320,
- SEQ_SCREEN_HEIGHT = 200
+ class SEQVideoTrack : public FixedRateVideoTrack {
+ public:
+ SEQVideoTrack(Common::SeekableReadStream *stream, uint frameDelay);
+ ~SEQVideoTrack();
+
+ uint16 getWidth() const { return SEQ_SCREEN_WIDTH; }
+ uint16 getHeight() const { return SEQ_SCREEN_HEIGHT; }
+ Graphics::PixelFormat getPixelFormat() const { return Graphics::PixelFormat::createFormatCLUT8(); }
+ int getCurFrame() const { return _curFrame; }
+ int getFrameCount() const { return _frameCount; }
+ const Graphics::Surface *decodeNextFrame();
+ const byte *getPalette() const;
+ bool hasDirtyPalette() const { return _dirtyPalette; }
+
+ protected:
+ Common::Rational getFrameRate() const { return Common::Rational(60, _frameDelay); }
+
+ private:
+ enum {
+ SEQ_SCREEN_WIDTH = 320,
+ SEQ_SCREEN_HEIGHT = 200
+ };
+
+ void readPaletteChunk(uint16 chunkSize);
+ bool decodeFrame(byte *rleData, int rleSize, byte *litData, int litSize, byte *dest, int left, int width, int height, int colorKey);
+
+ Common::SeekableReadStream *_fileStream;
+ int _curFrame, _frameCount;
+ byte _palette[256 * 3];
+ mutable bool _dirtyPalette;
+ Graphics::Surface *_surface;
+ uint _frameDelay;
};
- void readPaletteChunk(uint16 chunkSize);
- bool decodeFrame(byte *rleData, int rleSize, byte *litData, int litSize, byte *dest, int left, int width, int height, int colorKey);
-
- uint16 _width, _height;
- uint16 _frameDelay;
- Common::SeekableReadStream *_fileStream;
- byte _palette[256 * 3];
- bool _dirtyPalette;
- uint32 _frameCount;
- Graphics::Surface *_surface;
+ uint _frameDelay;
};
} // End of namespace Sci