aboutsummaryrefslogtreecommitdiff
path: root/engines/sci
diff options
context:
space:
mode:
Diffstat (limited to 'engines/sci')
-rw-r--r--engines/sci/configure.engine2
-rw-r--r--engines/sci/console.cpp112
-rw-r--r--engines/sci/console.h3
-rw-r--r--engines/sci/decompressor.cpp2
-rw-r--r--engines/sci/detection.cpp27
-rw-r--r--engines/sci/detection_tables.h124
-rw-r--r--engines/sci/engine/kernel.cpp2
-rw-r--r--engines/sci/engine/kernel.h53
-rw-r--r--engines/sci/engine/kernel_tables.h157
-rw-r--r--engines/sci/engine/kevent.cpp27
-rw-r--r--engines/sci/engine/kfile.cpp57
-rw-r--r--engines/sci/engine/kgraphics.cpp47
-rw-r--r--engines/sci/engine/kgraphics32.cpp931
-rw-r--r--engines/sci/engine/kmisc.cpp19
-rw-r--r--engines/sci/engine/kpathing.cpp12
-rw-r--r--engines/sci/engine/ksound.cpp42
-rw-r--r--engines/sci/engine/kstring.cpp20
-rw-r--r--engines/sci/engine/object.cpp9
-rw-r--r--engines/sci/engine/object.h39
-rw-r--r--engines/sci/engine/savegame.cpp111
-rw-r--r--engines/sci/engine/savegame.h3
-rw-r--r--engines/sci/engine/script_patches.cpp674
-rw-r--r--engines/sci/engine/script_patches.h21
-rw-r--r--engines/sci/engine/scriptdebug.cpp4
-rw-r--r--engines/sci/engine/seg_manager.cpp9
-rw-r--r--engines/sci/engine/segment.h2
-rw-r--r--engines/sci/engine/selector.cpp29
-rw-r--r--engines/sci/engine/selector.h18
-rw-r--r--engines/sci/engine/state.cpp1
-rw-r--r--engines/sci/engine/state.h2
-rw-r--r--engines/sci/engine/vm.cpp14
-rw-r--r--engines/sci/engine/vm_types.cpp38
-rw-r--r--engines/sci/engine/vm_types.h17
-rw-r--r--engines/sci/engine/workarounds.cpp11
-rw-r--r--engines/sci/engine/workarounds.h1
-rw-r--r--engines/sci/event.cpp56
-rw-r--r--engines/sci/event.h18
-rw-r--r--engines/sci/graphics/animate.cpp92
-rw-r--r--engines/sci/graphics/animate.h10
-rw-r--r--engines/sci/graphics/cache.cpp4
-rw-r--r--engines/sci/graphics/cache.h2
-rw-r--r--engines/sci/graphics/celobj32.cpp1050
-rw-r--r--engines/sci/graphics/celobj32.h581
-rw-r--r--engines/sci/graphics/compare.cpp74
-rw-r--r--engines/sci/graphics/compare.h3
-rw-r--r--engines/sci/graphics/controls16.cpp2
-rw-r--r--engines/sci/graphics/controls32.cpp426
-rw-r--r--engines/sci/graphics/controls32.h83
-rw-r--r--engines/sci/graphics/cursor.cpp12
-rw-r--r--engines/sci/graphics/cursor.h12
-rw-r--r--engines/sci/graphics/frameout.cpp2005
-rw-r--r--engines/sci/graphics/frameout.h504
-rw-r--r--engines/sci/graphics/helpers.h106
-rw-r--r--engines/sci/graphics/lists32.h192
-rw-r--r--engines/sci/graphics/palette.cpp90
-rw-r--r--engines/sci/graphics/palette.h21
-rw-r--r--engines/sci/graphics/palette32.cpp108
-rw-r--r--engines/sci/graphics/palette32.h392
-rw-r--r--engines/sci/graphics/picture.cpp6
-rw-r--r--engines/sci/graphics/picture.h3
-rw-r--r--engines/sci/graphics/plane32.cpp907
-rw-r--r--engines/sci/graphics/plane32.h485
-rw-r--r--engines/sci/graphics/remap.cpp386
-rw-r--r--engines/sci/graphics/remap.h154
-rw-r--r--engines/sci/graphics/screen.cpp278
-rw-r--r--engines/sci/graphics/screen.h315
-rw-r--r--engines/sci/graphics/screen_item32.cpp647
-rw-r--r--engines/sci/graphics/screen_item32.h288
-rw-r--r--engines/sci/graphics/text32.cpp822
-rw-r--r--engines/sci/graphics/text32.h457
-rw-r--r--engines/sci/graphics/view.cpp45
-rw-r--r--engines/sci/graphics/view.h2
-rw-r--r--engines/sci/module.mk4
-rw-r--r--engines/sci/parser/vocabulary.cpp14
-rw-r--r--engines/sci/parser/vocabulary.h2
-rw-r--r--engines/sci/resource.cpp75
-rw-r--r--engines/sci/resource.h9
-rw-r--r--engines/sci/resource_audio.cpp8
-rw-r--r--engines/sci/sci.cpp76
-rw-r--r--engines/sci/sci.h13
-rw-r--r--engines/sci/sound/drivers/amigamac.cpp2
-rw-r--r--engines/sci/sound/soundcmd.cpp2
-rw-r--r--engines/sci/util.cpp9
-rw-r--r--engines/sci/util.h3
84 files changed, 10565 insertions, 2930 deletions
diff --git a/engines/sci/configure.engine b/engines/sci/configure.engine
index d1c45a4654..f8a519002e 100644
--- a/engines/sci/configure.engine
+++ b/engines/sci/configure.engine
@@ -1,4 +1,4 @@
# This file is included from the main "configure" script
# add_engine [name] [desc] [build-by-default] [subengines] [base games] [deps]
add_engine sci "SCI" yes "sci32" "SCI 0-1.1 games"
-add_engine sci32 "SCI32 games" no
+add_engine sci32 "SCI32 games" no "" "" "highres"
diff --git a/engines/sci/console.cpp b/engines/sci/console.cpp
index 438c725324..a092e0676d 100644
--- a/engines/sci/console.cpp
+++ b/engines/sci/console.cpp
@@ -137,8 +137,12 @@ Console::Console(SciEngine *engine) : GUI::Debugger(),
registerCmd("wl", WRAP_METHOD(Console, cmdWindowList)); // alias
registerCmd("plane_list", WRAP_METHOD(Console, cmdPlaneList));
registerCmd("pl", WRAP_METHOD(Console, cmdPlaneList)); // alias
+ registerCmd("visible_plane_list", WRAP_METHOD(Console, cmdVisiblePlaneList));
+ registerCmd("vpl", WRAP_METHOD(Console, cmdVisiblePlaneList)); // alias
registerCmd("plane_items", WRAP_METHOD(Console, cmdPlaneItemList));
registerCmd("pi", WRAP_METHOD(Console, cmdPlaneItemList)); // alias
+ registerCmd("visible_plane_items", WRAP_METHOD(Console, cmdVisiblePlaneItemList));
+ registerCmd("vpi", WRAP_METHOD(Console, cmdVisiblePlaneItemList)); // alias
registerCmd("saved_bits", WRAP_METHOD(Console, cmdSavedBits));
registerCmd("show_saved_bits", WRAP_METHOD(Console, cmdShowSavedBits));
// Segments
@@ -190,6 +194,7 @@ Console::Console(SciEngine *engine) : GUI::Debugger(),
registerCmd("send", WRAP_METHOD(Console, cmdSend));
registerCmd("go", WRAP_METHOD(Console, cmdGo));
registerCmd("logkernel", WRAP_METHOD(Console, cmdLogKernel));
+ registerCmd("vocab994", WRAP_METHOD(Console, cmdMapVocab994));
// Breakpoints
registerCmd("bp_list", WRAP_METHOD(Console, cmdBreakpointList));
registerCmd("bplist", WRAP_METHOD(Console, cmdBreakpointList)); // alias
@@ -330,6 +335,7 @@ bool Console::cmdHelp(int argc, const char **argv) {
debugPrintf("debugflag_list - Lists the available debug flags and their status\n");
debugPrintf("debugflag_enable - Enables a debug flag\n");
debugPrintf("debugflag_disable - Disables a debug flag\n");
+ debugPrintf("debuglevel - Shows or sets debug level\n");
debugPrintf("\n");
debugPrintf("Commands\n");
debugPrintf("--------\n");
@@ -380,7 +386,9 @@ bool Console::cmdHelp(int argc, const char **argv) {
debugPrintf(" animate_list / al - Shows the current list of objects in kAnimate's draw list (SCI0 - SCI1.1)\n");
debugPrintf(" window_list / wl - Shows a list of all the windows (ports) in the draw list (SCI0 - SCI1.1)\n");
debugPrintf(" plane_list / pl - Shows a list of all the planes in the draw list (SCI2+)\n");
+ debugPrintf(" visible_plane_list / vpl - Shows a list of all the planes in the visible draw list (SCI2+)\n");
debugPrintf(" plane_items / pi - Shows a list of all items for a plane (SCI2+)\n");
+ debugPrintf(" visible_plane_items / vpi - Shows a list of all items for a plane in the visible draw list (SCI2+)\n");
debugPrintf(" saved_bits - List saved bits on the hunk\n");
debugPrintf(" show_saved_bits - Display saved bits\n");
debugPrintf("\n");
@@ -487,6 +495,9 @@ bool Console::cmdGetVersion(int argc, const char **argv) {
debugPrintf("SCI2.1 kernel table: %s\n", (_engine->_features->detectSci21KernelType() == SCI_VERSION_2) ? "modified SCI2 (old)" : "SCI2.1 (new)");
#endif
debugPrintf("View type: %s\n", viewTypeDesc[g_sci->getResMan()->getViewType()]);
+ if (getSciVersion() <= SCI_VERSION_1_1) {
+ debugPrintf("kAnimate fastCast enabled: %s\n", g_sci->_gfxAnimate->isFastCastEnabled() ? "yes" : "no");
+ }
debugPrintf("Uses palette merging: %s\n", g_sci->_gfxPalette16->isMerging() ? "yes" : "no");
debugPrintf("Uses 16 bit color matching: %s\n", g_sci->_gfxPalette16->isUsing16bitColorMatch() ? "yes" : "no");
debugPrintf("Resource volume version: %s\n", g_sci->getResMan()->getVolVersionDesc());
@@ -994,7 +1005,7 @@ bool Console::cmdHexgrep(int argc, const char **argv) {
for (; resNumber <= resMax; resNumber++) {
script = _engine->getResMan()->findResource(ResourceId(restype, resNumber), 0);
if (script) {
- unsigned int seeker = 0, seekerold = 0;
+ uint32 seeker = 0, seekerold = 0;
uint32 comppos = 0;
int output_script_name = 0;
@@ -1500,7 +1511,7 @@ bool Console::cmdSaid(int argc, const char **argv) {
}
// TODO: Maybe turn this into a proper said spec compiler
- unsigned int len = 0;
+ uint32 len = 0;
for (p++; p < argc; p++) {
if (strcmp(argv[p], ",") == 0) {
spec[len++] = 0xf0;
@@ -1537,7 +1548,7 @@ bool Console::cmdSaid(int argc, const char **argv) {
spec[len++] = 0xfe;
spec[len++] = 0xf6;
} else {
- unsigned int s = strtol(argv[p], 0, 16);
+ uint32 s = strtol(argv[p], 0, 16);
if (s >= 0xf0 && s <= 0xff) {
spec[len++] = s;
} else {
@@ -1766,6 +1777,21 @@ bool Console::cmdPlaneList(int argc, const char **argv) {
return true;
}
+bool Console::cmdVisiblePlaneList(int argc, const char **argv) {
+#ifdef ENABLE_SCI32
+ if (_engine->_gfxFrameout) {
+ debugPrintf("Visible plane list:\n");
+ _engine->_gfxFrameout->printVisiblePlaneList(this);
+ } else {
+ debugPrintf("This SCI version does not have a list of planes\n");
+ }
+#else
+ debugPrintf("SCI32 isn't included in this compiled executable\n");
+#endif
+ return true;
+}
+
+
bool Console::cmdPlaneItemList(int argc, const char **argv) {
if (argc != 2) {
debugPrintf("Shows the list of items for a plane\n");
@@ -1794,6 +1820,34 @@ bool Console::cmdPlaneItemList(int argc, const char **argv) {
return true;
}
+bool Console::cmdVisiblePlaneItemList(int argc, const char **argv) {
+ if (argc != 2) {
+ debugPrintf("Shows the list of items for a plane\n");
+ debugPrintf("Usage: %s <plane address>\n", argv[0]);
+ return true;
+ }
+
+ reg_t planeObject = NULL_REG;
+
+ if (parse_reg_t(_engine->_gamestate, argv[1], &planeObject, false)) {
+ debugPrintf("Invalid address passed.\n");
+ debugPrintf("Check the \"addresses\" command on how to use addresses\n");
+ return true;
+ }
+
+#ifdef ENABLE_SCI32
+ if (_engine->_gfxFrameout) {
+ debugPrintf("Visible plane item list:\n");
+ _engine->_gfxFrameout->printVisiblePlaneItemList(this, planeObject);
+ } else {
+ debugPrintf("This SCI version does not have a list of plane items\n");
+ }
+#else
+ debugPrintf("SCI32 isn't included in this compiled executable\n");
+#endif
+ return true;
+}
+
bool Console::cmdSavedBits(int argc, const char **argv) {
SegManager *segman = _engine->_gamestate->_segMan;
SegmentId id = segman->findSegmentByType(SEG_TYPE_HUNK);
@@ -3901,6 +3955,55 @@ bool Console::cmdSfx01Track(int argc, const char **argv) {
return true;
}
+bool Console::cmdMapVocab994(int argc, const char **argv) {
+ EngineState *s = _engine->_gamestate; // for the several defines in this function
+ reg_t reg;
+
+ if (argc != 4) {
+ debugPrintf("Attempts to map a range of vocab.994 entries to a given class\n");
+ debugPrintf("Usage: %s <class addr> <first> <last>\n", argv[0]);
+ return true;
+ }
+
+ if (parse_reg_t(_engine->_gamestate, argv[1], &reg, false)) {
+ debugPrintf("Invalid address passed.\n");
+ debugPrintf("Check the \"addresses\" command on how to use addresses\n");
+ return true;
+ }
+
+ Resource *resource = _engine->_resMan->findResource(ResourceId(kResourceTypeVocab, 994), 0);
+ const Object *obj = s->_segMan->getObject(reg);
+ uint16 *data = (uint16 *) resource->data;
+ uint32 first = atoi(argv[2]);
+ uint32 last = atoi(argv[3]);
+ Common::Array<bool> markers;
+
+ markers.resize(_engine->getKernel()->getSelectorNamesSize());
+ if (!obj->isClass() && getSciVersion() != SCI_VERSION_3)
+ obj = s->_segMan->getObject(obj->getSuperClassSelector());
+
+ first = MIN(first, (uint32) (resource->size / 2 - 2));
+ last = MIN(last, (uint32) (resource->size / 2 - 2));
+
+ for (uint32 i = first; i <= last; ++i) {
+ uint16 ofs = data[i];
+
+ if (obj && ofs < obj->getVarCount()) {
+ uint16 varSelector = obj->getVarSelector(ofs);
+ debugPrintf("%d: property at index %04x of %s is %s %s\n", i, ofs,
+ s->_segMan->derefString(obj->getNameSelector()),
+ _engine->getKernel()->getSelectorName(varSelector).c_str(),
+ markers[varSelector] ? "(repeat!)" : "");
+ markers[varSelector] = true;
+ }
+ else {
+ debugPrintf("%d: property at index %04x doesn't match up with %s\n", i, ofs,
+ s->_segMan->derefString(obj->getNameSelector()));
+ }
+ }
+
+ return true;
+}
bool Console::cmdQuit(int argc, const char **argv) {
if (argc != 2) {
}
@@ -4339,7 +4442,8 @@ int Console::printObject(reg_t pos) {
debugPrintf(" ");
if (var_container && i < var_container->getVarCount()) {
uint16 varSelector = var_container->getVarSelector(i);
- debugPrintf("[%03x] %s = ", varSelector, _engine->getKernel()->getSelectorName(varSelector).c_str());
+ // Times two commented out for now for easy parsing of vocab.994
+ debugPrintf("(%04x) [%03x] %s = ", i /* *2 */, varSelector, _engine->getKernel()->getSelectorName(varSelector).c_str());
} else
debugPrintf("p#%x = ", i);
diff --git a/engines/sci/console.h b/engines/sci/console.h
index 8b10912fbe..cf85def950 100644
--- a/engines/sci/console.h
+++ b/engines/sci/console.h
@@ -96,7 +96,9 @@ private:
bool cmdAnimateList(int argc, const char **argv);
bool cmdWindowList(int argc, const char **argv);
bool cmdPlaneList(int argc, const char **argv);
+ bool cmdVisiblePlaneList(int argc, const char **argv);
bool cmdPlaneItemList(int argc, const char **argv);
+ bool cmdVisiblePlaneItemList(int argc, const char **argv);
bool cmdSavedBits(int argc, const char **argv);
bool cmdShowSavedBits(int argc, const char **argv);
// Segments
@@ -137,6 +139,7 @@ private:
bool cmdSend(int argc, const char **argv);
bool cmdGo(int argc, const char **argv);
bool cmdLogKernel(int argc, const char **argv);
+ bool cmdMapVocab994(int argc, const char **argv);
// Breakpoints
bool cmdBreakpointList(int argc, const char **argv);
bool cmdBreakpointDelete(int argc, const char **argv);
diff --git a/engines/sci/decompressor.cpp b/engines/sci/decompressor.cpp
index e65ff148de..ca2298e67e 100644
--- a/engines/sci/decompressor.cpp
+++ b/engines/sci/decompressor.cpp
@@ -257,7 +257,7 @@ int DecompressorLZW::unpackLZW1(Common::ReadStream *src, byte *dest, uint32 nPac
init(src, dest, nPacked, nUnpacked);
byte *stak = (byte *)malloc(0x1014);
- unsigned int tokensSize = 0x1004 * sizeof(Tokenlist);
+ uint32 tokensSize = 0x1004 * sizeof(Tokenlist);
Tokenlist *tokens = (Tokenlist *)malloc(tokensSize);
if (!stak || !tokens) {
free(stak);
diff --git a/engines/sci/detection.cpp b/engines/sci/detection.cpp
index f4f104010f..c920ef10e2 100644
--- a/engines/sci/detection.cpp
+++ b/engines/sci/detection.cpp
@@ -98,8 +98,11 @@ static const PlainGameDescriptor s_sciGameTitles[] = {
{"lsl6", "Leisure Suit Larry 6: Shape Up or Slip Out!"},
{"pepper", "Pepper's Adventure in Time"},
{"slater", "Slater & Charlie Go Camping"},
+ {"gk1demo", "Gabriel Knight: Sins of the Fathers"},
+ {"qfg4demo", "Quest for Glory IV: Shadows of Darkness"},
+ {"pq4demo", "Police Quest IV: Open Season"},
// === SCI2 games =========================================================
- {"gk1", "Gabriel Knight: Sins of the Fathers"}, // demo is SCI11, full version SCI32
+ {"gk1", "Gabriel Knight: Sins of the Fathers"},
{"pq4", "Police Quest IV: Open Season"}, // floppy is SCI2, CD SCI2.1
{"qfg4", "Quest for Glory IV: Shadows of Darkness"}, // floppy is SCI2, CD SCI2.1
// === SCI2.1 games ========================================================
@@ -146,6 +149,7 @@ static const GameIdStrToEnum s_gameIdStrToEnum[] = {
{ "fairytales", GID_FAIRYTALES },
{ "freddypharkas", GID_FREDDYPHARKAS },
{ "funseeker", GID_FUNSEEKER },
+ { "gk1demo", GID_GK1DEMO },
{ "gk1", GID_GK1 },
{ "gk2", GID_GK2 },
{ "hoyle1", GID_HOYLE1 },
@@ -183,12 +187,14 @@ static const GameIdStrToEnum s_gameIdStrToEnum[] = {
{ "pq2", GID_PQ2 },
{ "pq3", GID_PQ3 },
{ "pq4", GID_PQ4 },
+ { "pq4demo", GID_PQ4DEMO },
{ "pqswat", GID_PQSWAT },
{ "qfg1", GID_QFG1 },
{ "qfg1vga", GID_QFG1VGA },
{ "qfg2", GID_QFG2 },
{ "qfg3", GID_QFG3 },
{ "qfg4", GID_QFG4 },
+ { "qfg4demo", GID_QFG4DEMO },
{ "rama", GID_RAMA },
{ "sci-fanmade", GID_FANMADE }, // FIXME: Do we really need/want this?
{ "shivers", GID_SHIVERS },
@@ -356,7 +362,7 @@ Common::String convertSierraGameId(Common::String sierraId, uint32 *gameFlags, R
// qfg4 demo has less than 50 scripts
if (resources.size() < 50)
- return "qfg4";
+ return "qfg4demo";
// Otherwise it's qfg3
return "qfg3";
@@ -473,7 +479,7 @@ static char s_fallbackGameIdBuf[256];
class SciMetaEngine : public AdvancedMetaEngine {
public:
SciMetaEngine() : AdvancedMetaEngine(Sci::SciGameDescriptions, sizeof(ADGameDescription), s_sciGameTitles, optionsList) {
- _singleid = "sci";
+ _singleId = "sci";
}
virtual const char *getName() const {
@@ -526,8 +532,8 @@ const ADGameDescription *SciMetaEngine::fallbackDetect(const FileMap &allFiles,
s_fallbackDesc.language = Common::EN_ANY;
s_fallbackDesc.flags = ADGF_NO_FLAGS;
s_fallbackDesc.platform = Common::kPlatformDOS; // default to PC platform
- s_fallbackDesc.gameid = "sci";
- s_fallbackDesc.guioptions = GUIO3(GAMEOPTION_PREFER_DIGITAL_SFX, GAMEOPTION_ORIGINAL_SAVELOAD, GAMEOPTION_FB01_MIDI);
+ s_fallbackDesc.gameId = "sci";
+ s_fallbackDesc.guiOptions = GUIO3(GAMEOPTION_PREFER_DIGITAL_SFX, GAMEOPTION_ORIGINAL_SAVELOAD, GAMEOPTION_FB01_MIDI);
if (allFiles.contains("resource.map") || allFiles.contains("Data1")
|| allFiles.contains("resmap.001") || allFiles.contains("resmap.001")) {
@@ -578,7 +584,7 @@ const ADGameDescription *SciMetaEngine::fallbackDetect(const FileMap &allFiles,
ResourceManager resMan;
resMan.addAppropriateSourcesForDetection(fslist);
- resMan.initForDetection();
+ resMan.init();
// TODO: Add error handling.
#ifndef ENABLE_SCI32
@@ -610,7 +616,7 @@ const ADGameDescription *SciMetaEngine::fallbackDetect(const FileMap &allFiles,
Common::String gameId = convertSierraGameId(sierraGameId, &s_fallbackDesc.flags, resMan);
strncpy(s_fallbackGameIdBuf, gameId.c_str(), sizeof(s_fallbackGameIdBuf) - 1);
s_fallbackGameIdBuf[sizeof(s_fallbackGameIdBuf) - 1] = 0; // Make sure string is NULL terminated
- s_fallbackDesc.gameid = s_fallbackGameIdBuf;
+ s_fallbackDesc.gameId = s_fallbackGameIdBuf;
// Try to determine the game language
// Load up text 0 and start looking for "#" characters
@@ -653,7 +659,7 @@ const ADGameDescription *SciMetaEngine::fallbackDetect(const FileMap &allFiles,
const bool isCD = (s_fallbackDesc.flags & ADGF_CD);
if (!isCD)
- s_fallbackDesc.guioptions = GUIO4(GUIO_NOSPEECH, GAMEOPTION_PREFER_DIGITAL_SFX, GAMEOPTION_ORIGINAL_SAVELOAD, GAMEOPTION_FB01_MIDI);
+ s_fallbackDesc.guiOptions = GUIO4(GUIO_NOSPEECH, GAMEOPTION_PREFER_DIGITAL_SFX, GAMEOPTION_ORIGINAL_SAVELOAD, GAMEOPTION_FB01_MIDI);
if (gameId.hasSuffix("sci")) {
s_fallbackDesc.extra = "SCI";
@@ -686,7 +692,7 @@ const ADGameDescription *SciMetaEngine::fallbackDetect(const FileMap &allFiles,
bool SciMetaEngine::createInstance(OSystem *syst, Engine **engine, const ADGameDescription *desc) const {
const GameIdStrToEnum *g = s_gameIdStrToEnum;
for (; g->gameidStr; ++g) {
- if (0 == strcmp(desc->gameid, g->gameidStr)) {
+ if (0 == strcmp(desc->gameId, g->gameidStr)) {
*engine = new SciEngine(syst, desc, g->gameidEnum);
return true;
}
@@ -727,7 +733,6 @@ SaveStateList SciMetaEngine::listSaves(const char *target) const {
pattern += ".###";
filenames = saveFileMan->listSavefiles(pattern);
- sort(filenames.begin(), filenames.end()); // Sort (hopefully ensuring we are sorted numerically..)
SaveStateList saveList;
int slotNr = 0;
@@ -760,6 +765,8 @@ SaveStateList SciMetaEngine::listSaves(const char *target) const {
}
}
+ // Sort saves based on slot number.
+ Common::sort(saveList.begin(), saveList.end(), SaveStateDescriptorSlotComparator());
return saveList;
}
diff --git a/engines/sci/detection_tables.h b/engines/sci/detection_tables.h
index 25623215ed..968eb784b1 100644
--- a/engines/sci/detection_tables.h
+++ b/engines/sci/detection_tables.h
@@ -684,21 +684,23 @@ static const struct ADGameDescription SciGameDescriptions[] = {
AD_LISTEND},
Common::EN_ANY, Common::kPlatformDOS, 0, GUIO5(GUIO_NOSPEECH, GAMEOPTION_EGA_UNDITHER, GAMEOPTION_PREFER_DIGITAL_SFX, GAMEOPTION_ORIGINAL_SAVELOAD, GAMEOPTION_FB01_MIDI) },
- // Gabriel Knight - English DOS CD Demo
+ // Gabriel Knight - English DOS Demo
// SCI interpreter version 1.001.092
- {"gk1", "CD Demo", {
+ // Note: we are not using ADGF_DEMO here, to avoid a game ID like gk1demo-demo
+ {"gk1demo", "Demo", {
{"resource.map", 0, "39645952ae0ed8072c7e838f31b75464", 2490},
{"resource.000", 0, "eb3ed7477ca4110813fe1fcf35928561", 1718450},
AD_LISTEND},
- Common::EN_ANY, Common::kPlatformDOS, ADGF_DEMO, GUIO3(GAMEOPTION_PREFER_DIGITAL_SFX, GAMEOPTION_ORIGINAL_SAVELOAD, GAMEOPTION_FB01_MIDI) },
+ Common::EN_ANY, Common::kPlatformDOS, 0, GUIO3(GAMEOPTION_PREFER_DIGITAL_SFX, GAMEOPTION_ORIGINAL_SAVELOAD, GAMEOPTION_FB01_MIDI) },
- // Gabriel Knight - English DOS CD Demo (from DrMcCoy)
+ // Gabriel Knight - English DOS Demo (from DrMcCoy)
// SCI interpreter version 1.001.092
- {"gk1", "CD Demo", {
+ // Note: we are not using ADGF_DEMO here, to avoid a game ID like gk1demo-demo
+ {"gk1demo", "Demo", {
{"resource.map", 0, "8cad2a256f41463030cbb7ea1bfb2857", 2490},
{"resource.000", 0, "eb3ed7477ca4110813fe1fcf35928561", 1718450},
AD_LISTEND},
- Common::EN_ANY, Common::kPlatformDOS, ADGF_DEMO, GUIO3(GAMEOPTION_PREFER_DIGITAL_SFX, GAMEOPTION_ORIGINAL_SAVELOAD, GAMEOPTION_FB01_MIDI) },
+ Common::EN_ANY, Common::kPlatformDOS, 0, GUIO3(GAMEOPTION_PREFER_DIGITAL_SFX, GAMEOPTION_ORIGINAL_SAVELOAD, GAMEOPTION_FB01_MIDI) },
#ifdef ENABLE_SCI32
// Gabriel Knight - English DOS Floppy
@@ -941,6 +943,15 @@ static const struct ADGameDescription SciGameDescriptions[] = {
AD_LISTEND},
Common::EN_ANY, Common::kPlatformDOS, 0, GUIO5(GUIO_NOSPEECH, GAMEOPTION_EGA_UNDITHER, GAMEOPTION_PREFER_DIGITAL_SFX, GAMEOPTION_ORIGINAL_SAVELOAD, GAMEOPTION_FB01_MIDI) },
+ // Hoyle 2 - English DOS (supplied by m_kiewitz)
+ // SCI interpreter version 0.000.668, Ver 1.000.014, 2x5.25"
+ {"hoyle2", "", {
+ {"resource.map", 0, "8cef06c93d17d96f44aacd5902d84b30", 2100},
+ {"resource.001", 0, "8f2dd70abe01112eca464cda818b5eb6", 98289},
+ {"resource.002", 0, "8f2dd70abe01112eca464cda818b5eb6", 197326},
+ AD_LISTEND},
+ Common::EN_ANY, Common::kPlatformDOS, 0, GUIO5(GUIO_NOSPEECH, GAMEOPTION_EGA_UNDITHER, GAMEOPTION_PREFER_DIGITAL_SFX, GAMEOPTION_ORIGINAL_SAVELOAD, GAMEOPTION_FB01_MIDI) },
+
// Hoyle 2 - English DOS (supplied by misterhands in bug report #6598)
// Game v1.000.016, interpreter 0.000.668, INT #12.5.90
{"hoyle2", "", {
@@ -1531,13 +1542,7 @@ static const struct ADGameDescription SciGameDescriptions[] = {
{"resource.000", 0, "71afd220d46bde1109c58e6acc0f3a01", 469094},
{"resource.001", 0, "72a569f46f1abf2d9d2b1526ad3799c3", 12808839},
AD_LISTEND},
- Common::EN_ANY, Common::kPlatformFMTowns, 0, GUIO2(GUIO_NOASPECT, GUIO_MIDITOWNS) },
- {"kq5", "", {
- {"resource.map", 0, "20c7cd248ff1a349ed354568eebd972b", 12733},
- {"resource.000", 0, "71afd220d46bde1109c58e6acc0f3a01", 469094},
- {"resource.001", 0, "72a569f46f1abf2d9d2b1526ad3799c3", 12808839},
- AD_LISTEND},
- Common::JA_JPN, Common::kPlatformFMTowns, 0, GUIO2(GUIO_NOASPECT, GUIO_MIDITOWNS) },
+ Common::JA_JPN, Common::kPlatformFMTowns, ADGF_ADDENGLISH, GUIO3(GUIO_NOASPECT, GAMEOPTION_ORIGINAL_SAVELOAD, GUIO_MIDITOWNS) },
// King's Quest 5 - Japanese PC-98 Floppy 0.000.015 (supplied by omer_mor in bug report #3073583)
{"kq5", "", {
@@ -1646,14 +1651,6 @@ static const struct ADGameDescription SciGameDescriptions[] = {
#ifdef ENABLE_SCI32
- // King's Quest 7 - English Windows (from abevi)
- // VERSION 1.65c
- {"kq7", "", {
- {"resource.000", 0, "4948e4e1506f1e1c4e1d47abfa06b7f8", 204385195},
- {"resource.map", 0, "40ccafb2195301504eba2e4f4f2c7f3d", 18925},
- AD_LISTEND},
- Common::EN_ANY, Common::kPlatformWindows, ADGF_UNSTABLE | ADGF_CD, GUIO4(GUIO_NOASPECT, GAMEOPTION_PREFER_DIGITAL_SFX, GAMEOPTION_ORIGINAL_SAVELOAD, GAMEOPTION_FB01_MIDI) },
-
// King's Quest 7 - English Windows (from the King's Quest Collection)
// Executable scanning reports "2.100.002", VERSION file reports "1.4"
{"kq7", "", {
@@ -1662,6 +1659,33 @@ static const struct ADGameDescription SciGameDescriptions[] = {
AD_LISTEND},
Common::EN_ANY, Common::kPlatformWindows, ADGF_UNSTABLE | ADGF_CD, GUIO4(GUIO_NOASPECT, GAMEOPTION_PREFER_DIGITAL_SFX, GAMEOPTION_ORIGINAL_SAVELOAD, GAMEOPTION_FB01_MIDI) },
+ // King's Quest 7 - English Windows-interpreter-only (supplied by m_kiewitz)
+ // SCI interpreter version 2.100.002, VERSION file reports "1.51"
+ {"kq7", "", {
+ {"resource.map", 0, "838b9ff132bd6962026fee832e8a7ddb", 18697},
+ {"resource.000", 0, "eb63ea3a2c2469dc2d777d351c626404", 206626576},
+ {"resource.aud", 0, "c2a988a16053eb98c7b73a75139902a0", 217716879},
+ AD_LISTEND},
+ Common::EN_ANY, Common::kPlatformWindows, ADGF_UNSTABLE | ADGF_CD, GUIO4(GUIO_NOASPECT, GAMEOPTION_PREFER_DIGITAL_SFX, GAMEOPTION_ORIGINAL_SAVELOAD, GAMEOPTION_FB01_MIDI) },
+
+ // King's Quest 7 - German Windows-interpreter-only (supplied by markcoolio in bug report #2727402)
+ // SCI interpreter version 2.100.002, VERSION file reports "1.51"
+ // same as English 1.51, only resource.aud/resource.sfx are different
+ {"kq7", "", {
+ {"resource.map", 0, "838b9ff132bd6962026fee832e8a7ddb", 18697},
+ {"resource.000", 0, "eb63ea3a2c2469dc2d777d351c626404", 206626576},
+ {"resource.aud", 0, "3f17bcaf8a9ff6a6c2d4de1a2078fdcc", 258119621},
+ AD_LISTEND},
+ Common::DE_DEU, Common::kPlatformWindows, ADGF_UNSTABLE | ADGF_CD, GUIO4(GUIO_NOASPECT, GAMEOPTION_PREFER_DIGITAL_SFX, GAMEOPTION_ORIGINAL_SAVELOAD, GAMEOPTION_FB01_MIDI) },
+
+ // King's Quest 7 - English Windows (from abevi)
+ // VERSION 1.65c
+ {"kq7", "", {
+ {"resource.000", 0, "4948e4e1506f1e1c4e1d47abfa06b7f8", 204385195},
+ {"resource.map", 0, "40ccafb2195301504eba2e4f4f2c7f3d", 18925},
+ AD_LISTEND},
+ Common::EN_ANY, Common::kPlatformWindows, ADGF_UNSTABLE | ADGF_CD, GUIO4(GUIO_NOASPECT, GAMEOPTION_PREFER_DIGITAL_SFX, GAMEOPTION_ORIGINAL_SAVELOAD, GAMEOPTION_FB01_MIDI) },
+
// King's Quest 7 - English DOS (from FRG)
// SCI interpreter version 2.100.002, VERSION file reports "2.00b"
{"kq7", "", {
@@ -1678,14 +1702,6 @@ static const struct ADGameDescription SciGameDescriptions[] = {
AD_LISTEND},
Common::EN_ANY, Common::kPlatformWindows, ADGF_UNSTABLE | ADGF_CD, GUIO4(GUIO_NOASPECT, GAMEOPTION_PREFER_DIGITAL_SFX, GAMEOPTION_ORIGINAL_SAVELOAD, GAMEOPTION_FB01_MIDI) },
- // King's Quest 7 - German Windows (supplied by markcoolio in bug report #2727402)
- // SCI interpreter version 2.100.002
- {"kq7", "", {
- {"resource.map", 0, "838b9ff132bd6962026fee832e8a7ddb", 18697},
- {"resource.000", 0, "eb63ea3a2c2469dc2d777d351c626404", 206626576},
- AD_LISTEND},
- Common::DE_DEU, Common::kPlatformDOS, ADGF_UNSTABLE | ADGF_CD, GUIO4(GUIO_NOASPECT, GAMEOPTION_PREFER_DIGITAL_SFX, GAMEOPTION_ORIGINAL_SAVELOAD, GAMEOPTION_FB01_MIDI) },
-
// King's Quest 7 - Spanish DOS (from jvprat)
// Executable scanning reports "2.100.002", VERSION file reports "2.00"
{"kq7", "", {
@@ -2286,6 +2302,14 @@ static const struct ADGameDescription SciGameDescriptions[] = {
AD_LISTEND},
Common::EN_ANY, Common::kPlatformDOS, 0, GUIO4(GUIO_NOSPEECH, GAMEOPTION_PREFER_DIGITAL_SFX, GAMEOPTION_ORIGINAL_SAVELOAD, GAMEOPTION_FB01_MIDI) },
+ // Larry 6 - French DOS Floppy - LOWRES (provided by theco33)
+ // SCI interpreter version 1.001.113
+ {"lsl6", "", {
+ {"resource.map", 0, "1e07144d3b06a3269236880170978acb", 6943},
+ {"resource.000", 0, "7884a8db9253e29e6b37a2651fd90ba3", 5749882},
+ AD_LISTEND},
+ Common::FR_FRA, Common::kPlatformDOS, 0, GUIO4(GUIO_NOSPEECH, GAMEOPTION_PREFER_DIGITAL_SFX, GAMEOPTION_ORIGINAL_SAVELOAD, GAMEOPTION_FB01_MIDI) },
+
// Larry 6 - English/German/French DOS CD - LOWRES
// SCI interpreter version 1.001.115
{"lsl6", "", {
@@ -2593,12 +2617,7 @@ static const struct ADGameDescription SciGameDescriptions[] = {
{"resource.map", 0, "b11e971ccd2040bebba59dfb409a08ef", 5772},
{"resource.001", 0, "d49625d9b8005ec01c852f8322a82867", 4330713},
AD_LISTEND},
- Common::EN_ANY, Common::kPlatformFMTowns, 0, GUIO4(GUIO_NOASPECT, GAMEOPTION_PREFER_DIGITAL_SFX, GAMEOPTION_ORIGINAL_SAVELOAD, GAMEOPTION_FB01_MIDI) },
- {"mothergoose256", "", {
- {"resource.map", 0, "b11e971ccd2040bebba59dfb409a08ef", 5772},
- {"resource.001", 0, "d49625d9b8005ec01c852f8322a82867", 4330713},
- AD_LISTEND},
- Common::JA_JPN, Common::kPlatformFMTowns, 0, GUIO4(GUIO_NOASPECT, GAMEOPTION_PREFER_DIGITAL_SFX, GAMEOPTION_ORIGINAL_SAVELOAD, GAMEOPTION_FB01_MIDI) },
+ Common::JA_JPN, Common::kPlatformFMTowns, ADGF_ADDENGLISH, GUIO4(GUIO_NOASPECT, GAMEOPTION_PREFER_DIGITAL_SFX, GAMEOPTION_ORIGINAL_SAVELOAD, GAMEOPTION_FB01_MIDI) },
#ifdef ENABLE_SCI32
// Mixed-Up Mother Goose Deluxe - English Windows/DOS CD (supplied by markcoolio in bug report #2723810)
@@ -2627,6 +2646,28 @@ static const struct ADGameDescription SciGameDescriptions[] = {
Common::EN_ANY, Common::kPlatformDOS, 0, GUIO4(GUIO_NOSPEECH, GAMEOPTION_PREFER_DIGITAL_SFX, GAMEOPTION_ORIGINAL_SAVELOAD, GAMEOPTION_FB01_MIDI) },
#ifdef ENABLE_SCI32
+ // Phantasmagoria - English DOS/Windows (from csnover)
+ // Windows executable scanning reports "2.100.002" - "Aug 06 1995"
+ // DOS executable scanning reports "2.100.002" - "May 24 1995"
+ // VERSION file reports "1.000.000"
+ {"phantasmagoria", "", {
+ {"resmap.001", 0, "43c395f312a190e67b90b2c1e93a79e2", 11518},
+ {"ressci.001", 0, "3aae6559aa1df273bc542d5ac6330d75", 65844612},
+ {"resmap.002", 0, "94f142cfe8ec4107b6a42876cb603ed0", 12058},
+ {"ressci.002", 0, "3aae6559aa1df273bc542d5ac6330d75", 71588691},
+ {"resmap.003", 0, "39e9abd4501b5b6168dd07379c0be753", 12334},
+ {"ressci.003", 0, "3aae6559aa1df273bc542d5ac6330d75", 73651084},
+ {"resmap.004", 0, "434f9704658229fef322c863d2422a9a", 12556},
+ {"ressci.004", 0, "3aae6559aa1df273bc542d5ac6330d75", 75811935},
+ {"resmap.005", 0, "3ff9b4f7301800825c0ed008e091205e", 12604},
+ {"ressci.005", 0, "3aae6559aa1df273bc542d5ac6330d75", 78814934},
+ {"resmap.006", 0, "27ad413313e2a3ec3c53250e7ff5b2d1", 12532},
+ {"ressci.006", 0, "3aae6559aa1df273bc542d5ac6330d75", 77901360},
+ {"resmap.007", 0, "aa8175cfc93242af6f5e65bdceaafc0d", 7972},
+ //{"ressci.007", 0, "3aae6559aa1df273bc542d5ac6330d75", 25859038},
+ AD_LISTEND},
+ Common::EN_ANY, Common::kPlatformDOS, ADGF_UNSTABLE, GUIO5(GUIO_NOSPEECH, GUIO_NOASPECT, GAMEOPTION_PREFER_DIGITAL_SFX, GAMEOPTION_ORIGINAL_SAVELOAD, GAMEOPTION_FB01_MIDI) },
+
// Phantasmagoria - English DOS (from jvprat)
// Executable scanning reports "2.100.002", VERSION file reports "1.100.000UK"
{"phantasmagoria", "", {
@@ -2875,6 +2916,7 @@ static const struct ADGameDescription SciGameDescriptions[] = {
Common::EN_ANY, Common::kPlatformDOS, 0, GUIO5(GUIO_NOSPEECH, GAMEOPTION_EGA_UNDITHER, GAMEOPTION_PREFER_DIGITAL_SFX, GAMEOPTION_ORIGINAL_SAVELOAD, GAMEOPTION_FB01_MIDI) },
// Police Quest 2 - Japanese PC-98 (also includes english language)
+ // Executable scanning reports "x.yyy.zzz"
// SCI interpreter version unknown
{"pq2", "", {
{"resource.map", 0, "883804c616dca1d82373bf9fda3a71d2", 4656},
@@ -2882,7 +2924,7 @@ static const struct ADGameDescription SciGameDescriptions[] = {
{"resource.002", 0, "05fdee43a228dd6ea4d1a92ccae3f788", 637662},
{"resource.003", 0, "05fdee43a228dd6ea4d1a92ccae3f788", 684395},
AD_LISTEND},
- Common::JA_JPN, Common::kPlatformPC98, ADGF_ADDENGLISH, GUIO5(GUIO_NOSPEECH, GUIO_NOASPECT, GAMEOPTION_PREFER_DIGITAL_SFX, GAMEOPTION_ORIGINAL_SAVELOAD, GAMEOPTION_FB01_MIDI) },
+ Common::JA_JPN, Common::kPlatformPC98, ADGF_ADDENGLISH, GUIO6(GUIO_NOSPEECH, GUIO_NOASPECT, GAMEOPTION_EGA_UNDITHER, GAMEOPTION_PREFER_DIGITAL_SFX, GAMEOPTION_ORIGINAL_SAVELOAD, GAMEOPTION_FB01_MIDI) },
// Police Quest 3 - English Amiga
// Executable scanning reports "1.004.024"
@@ -2969,11 +3011,12 @@ static const struct ADGameDescription SciGameDescriptions[] = {
// Police Quest 4 - English DOS Non-Interactive Demo (from FRG)
// SCI interpreter version 1.001.096
- {"pq4", "Demo", {
+ // Note: we are not using ADGF_DEMO here, to avoid a game ID like pq4demo-demo
+ {"pq4demo", "Demo", {
{"resource.map", 0, "be56f87a1c4a13062a30a362df860c2f", 1472},
{"resource.000", 0, "527d5684016e6816157cd15d9071b11b", 1121310},
AD_LISTEND},
- Common::EN_ANY, Common::kPlatformDOS, ADGF_DEMO, GUIO4(GUIO_NOSPEECH, GAMEOPTION_PREFER_DIGITAL_SFX, GAMEOPTION_ORIGINAL_SAVELOAD, GAMEOPTION_FB01_MIDI) },
+ Common::EN_ANY, Common::kPlatformDOS, 0, GUIO4(GUIO_NOSPEECH, GAMEOPTION_PREFER_DIGITAL_SFX, GAMEOPTION_ORIGINAL_SAVELOAD, GAMEOPTION_FB01_MIDI) },
#ifdef ENABLE_SCI32
// Police Quest 4 - English DOS CD (from the Police Quest Collection)
@@ -3381,11 +3424,12 @@ static const struct ADGameDescription SciGameDescriptions[] = {
// Quest for Glory 4 - English DOS Non-Interactive Demo (from FRG)
// SCI interpreter version 1.001.069 (just a guess)
- {"qfg4", "Demo", {
+ // Note: we are not using ADGF_DEMO here, to avoid a game ID like qfg4demo-demo
+ {"qfg4demo", "Demo", {
{"resource.map", 0, "1ba7c7ae1efb315326d45cb931569b1b", 922},
{"resource.000", 0, "41ba03f0b188b029132daa3ece0d3e14", 623154},
AD_LISTEND},
- Common::EN_ANY, Common::kPlatformDOS, ADGF_DEMO, GUIO4(GUIO_NOSPEECH, GAMEOPTION_PREFER_DIGITAL_SFX, GAMEOPTION_ORIGINAL_SAVELOAD, GAMEOPTION_FB01_MIDI) },
+ Common::EN_ANY, Common::kPlatformDOS, 0, GUIO4(GUIO_NOSPEECH, GAMEOPTION_PREFER_DIGITAL_SFX, GAMEOPTION_ORIGINAL_SAVELOAD, GAMEOPTION_FB01_MIDI) },
#ifdef ENABLE_SCI32
// Quest for Glory 4 1.1 Floppy - English DOS (supplied by markcool in bug report #2723852)
diff --git a/engines/sci/engine/kernel.cpp b/engines/sci/engine/kernel.cpp
index 0df4701334..796dea2382 100644
--- a/engines/sci/engine/kernel.cpp
+++ b/engines/sci/engine/kernel.cpp
@@ -853,7 +853,7 @@ void Kernel::loadKernelNames(GameFeatures *features) {
_kernelNames[0x26] = "Portrait";
else if (g_sci->getPlatform() == Common::kPlatformMacintosh)
_kernelNames[0x84] = "ShowMovie";
- } else if (g_sci->getGameId() == GID_QFG4 && g_sci->isDemo()) {
+ } else if (g_sci->getGameId() == GID_QFG4DEMO) {
_kernelNames[0x7b] = "RemapColors"; // QFG4 Demo has this SCI2 function instead of StrSplit
}
diff --git a/engines/sci/engine/kernel.h b/engines/sci/engine/kernel.h
index f62c43840f..62566a74b2 100644
--- a/engines/sci/engine/kernel.h
+++ b/engines/sci/engine/kernel.h
@@ -75,6 +75,10 @@ struct SciWorkaroundEntry; // from workarounds.h
* vocab.997. This results in much more readable code. Thus, this vocabulary isn't
* used at all.
*
+ * 993.voc (unneeded) - Contains the SCI3 equivalent of vocab.994; like its predecessor,
+ * the raw selector numbers can be deduced and used instead. In fact, one version of this
+ * file has turned out to cover all versiona of SCI3.
+ *
* SCI0 parser vocabularies:
* - vocab.901 / 901.voc - suffix vocabulary
* - vocab.900 / 900.voc - parse tree branches
@@ -408,7 +412,7 @@ reg_t kPlatform(EngineState *s, int argc, reg_t *argv);
reg_t kTextColors(EngineState *s, int argc, reg_t *argv);
reg_t kTextFonts(EngineState *s, int argc, reg_t *argv);
reg_t kShow(EngineState *s, int argc, reg_t *argv);
-reg_t kRemapColors(EngineState *s, int argc, reg_t *argv);
+reg_t kRemapColors16(EngineState *s, int argc, reg_t *argv);
reg_t kDummy(EngineState *s, int argc, reg_t *argv);
reg_t kEmpty(EngineState *s, int argc, reg_t *argv);
reg_t kStub(EngineState *s, int argc, reg_t *argv);
@@ -441,24 +445,54 @@ reg_t kStringLower(EngineState *s, int argc, reg_t *argv);
reg_t kStringTrn(EngineState *s, int argc, reg_t *argv);
reg_t kStringTrnExclude(EngineState *s, int argc, reg_t *argv);
+reg_t kScrollWindowCreate(EngineState *s, int argc, reg_t *argv);
+reg_t kScrollWindowAdd(EngineState *s, int argc, reg_t *argv);
+reg_t kScrollWindowWhere(EngineState *s, int argc, reg_t *argv);
+reg_t kScrollWindowShow(EngineState *s, int argc, reg_t *argv);
+reg_t kScrollWindowDestroy(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 kRemapColors(EngineState *s, int argc, reg_t *argv);
+reg_t kRemapOff(EngineState *s, int argc, reg_t *argv);
+reg_t kRemapByRange(EngineState *s, int argc, reg_t *argv);
+reg_t kRemapByPercent(EngineState *s, int argc, reg_t *argv);
+reg_t kRemapToGray(EngineState *s, int argc, reg_t *argv);
+reg_t kRemapToPercentGray(EngineState *s, int argc, reg_t *argv);
+reg_t kRemapSetNoMatchRange(EngineState *s, int argc, reg_t *argv);
+
reg_t kAddScreenItem(EngineState *s, int argc, reg_t *argv);
reg_t kUpdateScreenItem(EngineState *s, int argc, reg_t *argv);
reg_t kDeleteScreenItem(EngineState *s, int argc, reg_t *argv);
-// Text
+
reg_t kCreateTextBitmap(EngineState *s, int argc, reg_t *argv);
-reg_t kDisposeTextBitmap(EngineState *s, int argc, reg_t *argv);
-// "Planes" in SCI32 are pictures
+reg_t kBitmap(EngineState *s, int argc, reg_t *argv);
+reg_t kBitmapCreate(EngineState *s, int argc, reg_t *argv);
+reg_t kBitmapDestroy(EngineState *s, int argc, reg_t *argv);
+reg_t kBitmapDrawLine(EngineState *s, int argc, reg_t *argv);
+reg_t kBitmapDrawView(EngineState *s, int argc, reg_t *argv);
+reg_t kBitmapDrawText(EngineState *s, int argc, reg_t *argv);
+reg_t kBitmapDrawColor(EngineState *s, int argc, reg_t *argv);
+reg_t kBitmapDrawBitmap(EngineState *s, int argc, reg_t *argv);
+reg_t kBitmapInvert(EngineState *s, int argc, reg_t *argv);
+reg_t kBitmapSetDisplace(EngineState *s, int argc, reg_t *argv);
+reg_t kBitmapCreateFromView(EngineState *s, int argc, reg_t *argv);
+reg_t kBitmapCopyPixels(EngineState *s, int argc, reg_t *argv);
+reg_t kBitmapClone(EngineState *s, int argc, reg_t *argv);
+reg_t kBitmapGetInfo(EngineState *s, int argc, reg_t *argv);
+reg_t kBitmapScale(EngineState *s, int argc, reg_t *argv);
+reg_t kBitmapCreateFromUnknown(EngineState *s, int argc, reg_t *argv);
+
reg_t kAddPlane(EngineState *s, int argc, reg_t *argv);
reg_t kDeletePlane(EngineState *s, int argc, reg_t *argv);
reg_t kUpdatePlane(EngineState *s, int argc, reg_t *argv);
+reg_t kMovePlaneItems(EngineState *s, int argc, reg_t *argv);
reg_t kSetShowStyle(EngineState *s, int argc, reg_t *argv);
reg_t kSetPalStyleRange(EngineState *s, int argc, reg_t *argv);
reg_t kGetHighPlanePri(EngineState *s, int argc, reg_t *argv);
reg_t kFrameOut(EngineState *s, int argc, reg_t *argv);
+reg_t kCelHigh32(EngineState *s, int argc, reg_t *argv);
+reg_t kCelWide32(EngineState *s, int argc, reg_t *argv);
reg_t kIsOnMe(EngineState *s, int argc, reg_t *argv); // kOnMe for SCI2, kIsOnMe for SCI2.1
reg_t kInPolygon(EngineState *s, int argc, reg_t *argv);
@@ -473,6 +507,7 @@ reg_t kEditText(EngineState *s, int argc, reg_t *argv);
reg_t kMakeSaveCatName(EngineState *s, int argc, reg_t *argv);
reg_t kMakeSaveFileName(EngineState *s, int argc, reg_t *argv);
reg_t kSetScroll(EngineState *s, int argc, reg_t *argv);
+
reg_t kPalCycle(EngineState *s, int argc, reg_t *argv);
reg_t kPaletteSetFade(EngineState *s, int argc, reg_t *argv);
reg_t kPalVarySetVary(EngineState *s, int argc, reg_t *argv);
@@ -488,6 +523,8 @@ reg_t kPalVaryMergeStart(EngineState *s, int argc, reg_t *argv);
// SCI2.1 Kernel Functions
reg_t kMorphOn(EngineState *s, int argc, reg_t *argv);
reg_t kText(EngineState *s, int argc, reg_t *argv);
+reg_t kTextSize32(EngineState *s, int argc, reg_t *argv);
+reg_t kTextWidth(EngineState *s, int argc, reg_t *argv);
reg_t kSave(EngineState *s, int argc, reg_t *argv);
reg_t kAutoSave(EngineState *s, int argc, reg_t *argv);
reg_t kList(EngineState *s, int argc, reg_t *argv);
@@ -505,9 +542,9 @@ reg_t kGetSierraProfileInt(EngineState *s, int argc, reg_t *argv);
reg_t kCelInfo(EngineState *s, int argc, reg_t *argv);
reg_t kSetLanguage(EngineState *s, int argc, reg_t *argv);
reg_t kScrollWindow(EngineState *s, int argc, reg_t *argv);
+reg_t kSetFontHeight(EngineState *s, int argc, reg_t *argv);
reg_t kSetFontRes(EngineState *s, int argc, reg_t *argv);
reg_t kFont(EngineState *s, int argc, reg_t *argv);
-reg_t kBitmap(EngineState *s, int argc, reg_t *argv);
reg_t kAddLine(EngineState *s, int argc, reg_t *argv);
reg_t kUpdateLine(EngineState *s, int argc, reg_t *argv);
reg_t kDeleteLine(EngineState *s, int argc, reg_t *argv);
diff --git a/engines/sci/engine/kernel_tables.h b/engines/sci/engine/kernel_tables.h
index fce3230a18..3463d05e77 100644
--- a/engines/sci/engine/kernel_tables.h
+++ b/engines/sci/engine/kernel_tables.h
@@ -60,14 +60,19 @@ struct SciKernelMapSubEntry {
#define SCI_SUBOPENTRY_TERMINATOR { SCI_VERSION_NONE, SCI_VERSION_NONE, 0, NULL, NULL, NULL, NULL }
-#define SIG_SCIALL SCI_VERSION_NONE, SCI_VERSION_NONE
-#define SIG_SCI0 SCI_VERSION_NONE, SCI_VERSION_01
-#define SIG_SCI1 SCI_VERSION_1_EGA_ONLY, SCI_VERSION_1_LATE
-#define SIG_SCI11 SCI_VERSION_1_1, SCI_VERSION_1_1
-#define SIG_SINCE_SCI11 SCI_VERSION_1_1, SCI_VERSION_NONE
-#define SIG_SINCE_SCI21 SCI_VERSION_2_1_EARLY, SCI_VERSION_3
-#define SIG_UNTIL_SCI21MID SCI_VERSION_2, SCI_VERSION_2_1_MIDDLE
-#define SIG_SINCE_SCI21LATE SCI_VERSION_2_1_LATE, SCI_VERSION_3
+#define SIG_SCIALL SCI_VERSION_NONE, SCI_VERSION_NONE
+#define SIG_SCI0 SCI_VERSION_NONE, SCI_VERSION_01
+#define SIG_SCI1 SCI_VERSION_1_EGA_ONLY, SCI_VERSION_1_LATE
+#define SIG_SCI11 SCI_VERSION_1_1, SCI_VERSION_1_1
+#define SIG_SINCE_SCI11 SCI_VERSION_1_1, SCI_VERSION_NONE
+#define SIG_SCI2 SCI_VERSION_2, SCI_VERSION_2
+#define SIG_SCI21EARLY SCI_VERSION_2_1_EARLY, SCI_VERSION_2_1_EARLY
+#define SIG_UNTIL_SCI21EARLY SCI_VERSION_2, SCI_VERSION_2_1_EARLY
+#define SIG_UNTIL_SCI21MID SCI_VERSION_2, SCI_VERSION_2_1_MIDDLE
+#define SIG_SINCE_SCI21 SCI_VERSION_2_1_EARLY, SCI_VERSION_3
+#define SIG_SINCE_SCI21MID SCI_VERSION_2_1_MIDDLE, SCI_VERSION_3
+#define SIG_SINCE_SCI21LATE SCI_VERSION_2_1_LATE, SCI_VERSION_3
+#define SIG_SCI3 SCI_VERSION_3, SCI_VERSION_3
#define SIG_SCI16 SCI_VERSION_NONE, SCI_VERSION_1_1
#define SIG_SCI32 SCI_VERSION_2, SCI_VERSION_NONE
@@ -210,7 +215,7 @@ static const SciKernelMapSubEntry kPalVary_subops[] = {
{ SIG_SCI16, 6, MAP_CALL(PalVaryPauseResume), "i", NULL },
#ifdef ENABLE_SCI32
{ SIG_SCI32, 0, MAP_CALL(PalVarySetVary), "i(i)(i)(ii)", NULL },
- { SIG_SCI32, 1, MAP_CALL(PalVarySetPercent), "(i)(i)", NULL },
+ { SIG_SCI32, 1, MAP_CALL(PalVarySetPercent), "(i)(i)", kPalVarySetPercent_workarounds },
{ SIG_SCI32, 2, MAP_CALL(PalVaryGetPercent), "", NULL },
{ SIG_SCI32, 3, MAP_CALL(PalVaryOff), "", NULL },
{ SIG_SCI32, 4, MAP_CALL(PalVaryMergeTarget), "i", NULL },
@@ -283,6 +288,42 @@ static const SciKernelMapSubEntry kSave_subops[] = {
};
// version, subId, function-mapping, signature, workarounds
+static const SciKernelMapSubEntry kFont_subops[] = {
+ { SIG_SINCE_SCI21MID, 0, MAP_CALL(SetFontHeight), "i", NULL },
+ { SIG_SINCE_SCI21MID, 1, MAP_CALL(SetFontRes), "ii", NULL },
+ SCI_SUBOPENTRY_TERMINATOR
+};
+
+// version, subId, function-mapping, signature, workarounds
+static const SciKernelMapSubEntry kText_subops[] = {
+ { SIG_SINCE_SCI21MID, 0, MAP_CALL(TextSize32), "r[r0]i(i)(i)", NULL },
+ { SIG_SINCE_SCI21MID, 1, MAP_CALL(TextWidth), "ri", NULL },
+ SCI_SUBOPENTRY_TERMINATOR
+};
+
+// version, subId, function-mapping, signature, workarounds
+static const SciKernelMapSubEntry kBitmap_subops[] = {
+ { SIG_SINCE_SCI21, 0, MAP_CALL(BitmapCreate), "iiii(i)(i)(i)", NULL },
+ { SIG_SINCE_SCI21, 1, MAP_CALL(BitmapDestroy), "r", NULL },
+ { SIG_SINCE_SCI21, 2, MAP_CALL(BitmapDrawLine), "riiiii(i)(i)", NULL },
+ { SIG_SINCE_SCI21, 3, MAP_CALL(BitmapDrawView), "riii(i)(i)(0)(i)(i)", NULL },
+ { SIG_SINCE_SCI21, 4, MAP_CALL(BitmapDrawText), "rriiiiiiiiiii", NULL },
+ { SIG_SINCE_SCI21, 5, MAP_CALL(BitmapDrawColor), "riiiii", NULL },
+ { SIG_SINCE_SCI21, 6, MAP_CALL(BitmapDrawBitmap), "rr(i)(i)(i)", NULL },
+ { SIG_SINCE_SCI21, 7, MAP_CALL(BitmapInvert), "riiiiii", NULL },
+ { SIG_SINCE_SCI21MID, 8, MAP_CALL(BitmapSetDisplace), "rii", NULL },
+ { SIG_SINCE_SCI21MID, 9, MAP_CALL(BitmapCreateFromView), "iii(i)(i)(i)([r0])", NULL },
+ { SIG_SINCE_SCI21MID, 10, MAP_CALL(BitmapCopyPixels), "rr", NULL },
+ { SIG_SINCE_SCI21MID, 11, MAP_CALL(BitmapClone), "r", NULL },
+ { SIG_SINCE_SCI21LATE, 12, MAP_CALL(BitmapGetInfo), "r(i)(i)", NULL },
+ { SIG_SINCE_SCI21LATE, 13, MAP_CALL(BitmapScale), "r...ii", NULL },
+ { SIG_SCI3, 14, MAP_CALL(BitmapCreateFromUnknown), "......", NULL },
+ { SIG_SCI3, 15, MAP_EMPTY(Bitmap), "(.*)", NULL },
+ { SIG_SCI3, 16, MAP_EMPTY(Bitmap), "(.*)", NULL },
+ SCI_SUBOPENTRY_TERMINATOR
+};
+
+// version, subId, function-mapping, signature, workarounds
static const SciKernelMapSubEntry kList_subops[] = {
{ SIG_SINCE_SCI21, 0, MAP_CALL(NewList), "", NULL },
{ SIG_SINCE_SCI21, 1, MAP_CALL(DisposeList), "l", NULL },
@@ -311,6 +352,17 @@ static const SciKernelMapSubEntry kList_subops[] = {
};
// version, subId, function-mapping, signature, workarounds
+static const SciKernelMapSubEntry kRemapColors_subops[] = {
+ { SIG_SCI32, 0, MAP_CALL(RemapOff), "(i)", NULL },
+ { SIG_SCI32, 1, MAP_CALL(RemapByRange), "iiii(i)", NULL },
+ { SIG_SCI32, 2, MAP_CALL(RemapByPercent), "ii(i)", NULL },
+ { SIG_SCI32, 3, MAP_CALL(RemapToGray), "ii(i)", NULL },
+ { SIG_SCI32, 4, MAP_CALL(RemapToPercentGray), "iii(i)", NULL },
+ { SIG_SCI32, 5, MAP_CALL(RemapSetNoMatchRange), "ii", NULL },
+ SCI_SUBOPENTRY_TERMINATOR
+};
+
+// version, subId, function-mapping, signature, workarounds
static const SciKernelMapSubEntry kString_subops[] = {
{ SIG_SCI32, 0, MAP_CALL(StringNew), "i(i)", NULL },
{ SIG_SCI32, 1, MAP_CALL(StringSize), "[or]", NULL },
@@ -352,6 +404,31 @@ static const SciKernelMapSubEntry kString_subops[] = {
SCI_SUBOPENTRY_TERMINATOR
};
+// version, subId, function-mapping, signature, workarounds
+static const SciKernelMapSubEntry kScrollWindow_subops[] = {
+ { SIG_SCI32, 0, MAP_CALL(ScrollWindowCreate), "oi", NULL },
+ { SIG_SCI32, 1, MAP_CALL(ScrollWindowAdd), "o.ii.(.)", NULL },
+ { SIG_SCI32, 2, MAP_DUMMY(ScrollWindowClear), "o", NULL },
+ { SIG_SCI32, 3, MAP_DUMMY(ScrollWindowPageUp), "o", NULL },
+ { SIG_SCI32, 4, MAP_DUMMY(ScrollWindowPageDown), "o", NULL },
+ { SIG_SCI32, 5, MAP_DUMMY(ScrollWindowUpArrow), "o", NULL },
+ { SIG_SCI32, 6, MAP_DUMMY(ScrollWindowDownArrow), "o", NULL },
+ { SIG_SCI32, 7, MAP_DUMMY(ScrollWindowHome), "o", NULL },
+ { SIG_SCI32, 8, MAP_DUMMY(ScrollWindowEnd), "o", NULL },
+ { SIG_SCI32, 9, MAP_DUMMY(ScrollWindowResize), "o.", NULL },
+ { SIG_SCI32, 10, MAP_CALL(ScrollWindowWhere), "oi", NULL },
+ { SIG_SCI32, 11, MAP_DUMMY(ScrollWindowGo), "o..", NULL },
+ { SIG_SCI32, 12, MAP_DUMMY(ScrollWindowInsert), "o.....", NULL },
+ { SIG_SCI32, 13, MAP_DUMMY(ScrollWindowDelete), "o.", NULL },
+ { SIG_SCI32, 14, MAP_DUMMY(ScrollWindowModify), "o.....(.)", NULL },
+ { SIG_SCI32, 15, MAP_DUMMY(ScrollWindowHide), "o", NULL },
+ { SIG_SCI32, 16, MAP_CALL(ScrollWindowShow), "o", NULL },
+ { SIG_SCI32, 17, MAP_CALL(ScrollWindowDestroy), "o", NULL },
+ { SIG_SCI32, 18, MAP_DUMMY(ScrollWindowText), "o", NULL },
+ { SIG_SCI32, 19, MAP_DUMMY(ScrollWindowReconstruct), "o.", NULL },
+ SCI_SUBOPENTRY_TERMINATOR
+};
+
#endif
struct SciKernelMapEntry {
@@ -380,12 +457,16 @@ static SciKernelMapEntry s_kernelMap[] = {
{ MAP_CALL(AvoidPath), SIG_EVERYWHERE, "ii(.*)", NULL, NULL },
{ MAP_CALL(BaseSetter), SIG_EVERYWHERE, "o", NULL, NULL },
{ MAP_CALL(CanBeHere), SIG_EVERYWHERE, "o(l)", NULL, NULL },
+ { MAP_CALL(CantBeHere), SIG_SCI16, SIGFOR_ALL, "o(l)", NULL, NULL },
#ifdef ENABLE_SCI32
- { "CantBeHere", kCantBeHere32, SIG_SCI32, SIGFOR_ALL, "ol", NULL, NULL },
+ { MAP_CALL(CantBeHere), SIG_SCI32, SIGFOR_ALL, "ol", NULL, NULL },
+#endif
+ { MAP_CALL(CelHigh), SIG_SCI16, SIGFOR_ALL, "ii(i)", NULL, kCelHigh_workarounds },
+ { MAP_CALL(CelWide), SIG_SCI16, SIGFOR_ALL, "ii(i)", NULL, kCelWide_workarounds },
+#ifdef ENABLE_SCI32
+ { "CelHigh", kCelHigh32, SIG_SCI32, SIGFOR_ALL, "iii", NULL, NULL },
+ { "CelWide", kCelWide32, SIG_SCI32, SIGFOR_ALL, "iii", NULL, NULL },
#endif
- { MAP_CALL(CantBeHere), SIG_EVERYWHERE, "o(l)", NULL, NULL },
- { MAP_CALL(CelHigh), SIG_EVERYWHERE, "ii(i)", NULL, kCelHigh_workarounds },
- { MAP_CALL(CelWide), SIG_EVERYWHERE, "ii(i)", NULL, kCelWide_workarounds },
{ MAP_CALL(CheckFreeSpace), SIG_SCI32, SIGFOR_ALL, "r.*", NULL, NULL },
{ MAP_CALL(CheckFreeSpace), SIG_SCI11, SIGFOR_ALL, "r(i)", NULL, NULL },
{ MAP_CALL(CheckFreeSpace), SIG_EVERYWHERE, "r", NULL, NULL },
@@ -484,9 +565,9 @@ 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, kReadNumber_workarounds },
- { MAP_CALL(RemapColors), SIG_SCI11, SIGFOR_ALL, "i(i)(i)(i)(i)", NULL, NULL },
+ { "RemapColors", kRemapColors16, 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 },
+ { MAP_CALL(RemapColors), SIG_SCI32, SIGFOR_ALL, "i(i)(i)(i)(i)(i)", kRemapColors_subops, NULL },
#endif
{ MAP_CALL(ResCheck), SIG_EVERYWHERE, "ii(iiii)", NULL, NULL },
{ MAP_CALL(RespondsTo), SIG_EVERYWHERE, ".i", NULL, NULL },
@@ -502,7 +583,10 @@ static SciKernelMapEntry s_kernelMap[] = {
{ MAP_CALL(SetDebug), SIG_EVERYWHERE, "(i*)", NULL, NULL },
{ MAP_CALL(SetJump), SIG_EVERYWHERE, "oiii", NULL, NULL },
{ MAP_CALL(SetMenu), SIG_EVERYWHERE, "i(.*)", NULL, NULL },
- { MAP_CALL(SetNowSeen), SIG_EVERYWHERE, "o(i)", NULL, NULL },
+ { MAP_CALL(SetNowSeen), SIG_SCI16, SIGFOR_ALL, "o(i)", NULL, NULL },
+#ifdef ENABLE_SCI32
+ { MAP_CALL(SetNowSeen), SIG_SCI32, SIGFOR_ALL, "o", NULL, NULL },
+#endif
{ MAP_CALL(SetPort), SIG_EVERYWHERE, "i(iiiii)(i)", NULL, kSetPort_workarounds },
{ MAP_CALL(SetQuitStr), SIG_EVERYWHERE, "r", NULL, NULL },
{ MAP_CALL(SetSynonyms), SIG_EVERYWHERE, "o", NULL, NULL },
@@ -520,10 +604,10 @@ static SciKernelMapEntry s_kernelMap[] = {
{ MAP_CALL(StrEnd), SIG_EVERYWHERE, "r", NULL, NULL },
{ MAP_CALL(StrLen), SIG_EVERYWHERE, "[r0]", NULL, kStrLen_workarounds },
{ MAP_CALL(StrSplit), SIG_EVERYWHERE, "rr[r0]", NULL, NULL },
- { MAP_CALL(TextColors), SIG_EVERYWHERE, "(i*)", NULL, NULL },
- { MAP_CALL(TextFonts), SIG_EVERYWHERE, "(i*)", NULL, NULL },
- { MAP_CALL(TextSize), SIG_SCIALL, SIGFOR_MAC, "r[r0]i(i)(r0)(i)", NULL, NULL },
- { MAP_CALL(TextSize), SIG_EVERYWHERE, "r[r0]i(i)(r0)", NULL, NULL },
+ { MAP_CALL(TextColors), SIG_SCI16, SIGFOR_ALL, "(i*)", NULL, NULL },
+ { MAP_CALL(TextFonts), SIG_SCI16, SIGFOR_ALL, "(i*)", NULL, NULL },
+ { MAP_CALL(TextSize), SIG_SCI16, SIGFOR_MAC, "r[r0]i(i)(r0)(i)", NULL, NULL },
+ { MAP_CALL(TextSize), SIG_SCI16, SIGFOR_ALL, "r[r0]i(i)(r0)", NULL, NULL },
{ MAP_CALL(TimesCos), SIG_EVERYWHERE, "ii", NULL, NULL },
{ "CosMult", kTimesCos, SIG_EVERYWHERE, "ii", NULL, NULL },
{ MAP_CALL(TimesCot), SIG_EVERYWHERE, "ii", NULL, NULL },
@@ -555,14 +639,18 @@ static SciKernelMapEntry s_kernelMap[] = {
#ifdef ENABLE_SCI32
// SCI2 Kernel Functions
// TODO: whoever knows his way through those calls, fix the signatures.
+ { "TextSize", kTextSize32, SIG_UNTIL_SCI21EARLY, SIGFOR_ALL, "r[r0]i(i)", NULL, NULL },
+ { MAP_DUMMY(TextColors), SIG_UNTIL_SCI21EARLY, SIGFOR_ALL, "(.*)", NULL, NULL },
+ { MAP_DUMMY(TextFonts), SIG_UNTIL_SCI21EARLY, SIGFOR_ALL, "(.*)", NULL, NULL },
+
{ MAP_CALL(AddPlane), SIG_EVERYWHERE, "o", NULL, NULL },
{ MAP_CALL(AddScreenItem), SIG_EVERYWHERE, "o", NULL, NULL },
{ MAP_CALL(Array), SIG_EVERYWHERE, "(.*)", NULL, NULL },
{ MAP_CALL(CreateTextBitmap), SIG_EVERYWHERE, "i(.*)", NULL, NULL },
{ MAP_CALL(DeletePlane), SIG_EVERYWHERE, "o", NULL, NULL },
{ MAP_CALL(DeleteScreenItem), SIG_EVERYWHERE, "o", NULL, NULL },
- { MAP_CALL(DisposeTextBitmap), SIG_EVERYWHERE, "r", NULL, NULL },
- { MAP_CALL(FrameOut), SIG_EVERYWHERE, "", NULL, NULL },
+ { "DisposeTextBitmap", kBitmapDestroy, SIG_SCI2, SIGFOR_ALL, "r", NULL, NULL },
+ { MAP_CALL(FrameOut), SIG_EVERYWHERE, "(i)", NULL, NULL },
{ MAP_CALL(GetHighPlanePri), SIG_EVERYWHERE, "", NULL, NULL },
{ MAP_CALL(InPolygon), SIG_EVERYWHERE, "iio", NULL, NULL },
{ MAP_CALL(IsHiRes), SIG_EVERYWHERE, "", NULL, NULL },
@@ -623,9 +711,9 @@ static SciKernelMapEntry s_kernelMap[] = {
{ MAP_DUMMY(MarkMemory), SIG_EVERYWHERE, "(.*)", NULL, NULL },
{ MAP_DUMMY(GetHighItemPri), SIG_EVERYWHERE, "(.*)", NULL, NULL },
{ MAP_DUMMY(ShowStylePercent), SIG_EVERYWHERE, "(.*)", NULL, NULL },
- { MAP_DUMMY(InvertRect), SIG_EVERYWHERE, "(.*)", NULL, NULL },
+ { MAP_DUMMY(InvertRect), SIG_UNTIL_SCI21EARLY, SIGFOR_ALL, "(.*)", NULL, NULL },
{ MAP_DUMMY(InputText), SIG_EVERYWHERE, "(.*)", NULL, NULL },
- { MAP_DUMMY(TextWidth), SIG_EVERYWHERE, "(.*)", NULL, NULL },
+ { MAP_CALL(TextWidth), SIG_UNTIL_SCI21EARLY, SIGFOR_ALL, "ri", NULL, NULL },
{ MAP_DUMMY(PointSize), SIG_EVERYWHERE, "(.*)", NULL, NULL },
// SCI2.1 Kernel Functions
@@ -636,18 +724,18 @@ static SciKernelMapEntry s_kernelMap[] = {
{ MAP_CALL(PlayVMD), SIG_EVERYWHERE, "(.*)", NULL, NULL },
{ MAP_CALL(Robot), SIG_EVERYWHERE, "(.*)", NULL, NULL },
{ MAP_CALL(Save), SIG_EVERYWHERE, "i(.*)", kSave_subops, NULL },
- { MAP_CALL(Text), SIG_EVERYWHERE, "(.*)", NULL, NULL },
+ { MAP_CALL(Text), SIG_SINCE_SCI21MID, SIGFOR_ALL, "i(.*)", kText_subops, NULL },
{ MAP_CALL(AddPicAt), SIG_EVERYWHERE, "oiii", NULL, NULL },
{ MAP_CALL(GetWindowsOption), SIG_EVERYWHERE, "i", NULL, NULL },
{ MAP_CALL(WinHelp), SIG_EVERYWHERE, "(.*)", NULL, NULL },
{ MAP_CALL(GetConfig), SIG_EVERYWHERE, "ro", NULL, NULL },
{ MAP_CALL(GetSierraProfileInt), SIG_EVERYWHERE, "rri", NULL, NULL },
- { MAP_CALL(CelInfo), SIG_EVERYWHERE, "iiiiii", NULL, NULL },
- { MAP_CALL(SetLanguage), SIG_EVERYWHERE, "r", NULL, NULL },
- { MAP_CALL(ScrollWindow), SIG_EVERYWHERE, "io(.*)", NULL, NULL },
- { MAP_CALL(SetFontRes), SIG_EVERYWHERE, "ii", NULL, NULL },
- { MAP_CALL(Font), SIG_EVERYWHERE, "i(.*)", NULL, NULL },
- { MAP_CALL(Bitmap), SIG_EVERYWHERE, "(.*)", NULL, NULL },
+ { MAP_CALL(CelInfo), SIG_SINCE_SCI21MID, SIGFOR_ALL, "iiiiii", NULL, NULL },
+ { MAP_CALL(SetLanguage), SIG_SINCE_SCI21MID, SIGFOR_ALL, "r", NULL, NULL },
+ { MAP_CALL(ScrollWindow), SIG_EVERYWHERE, "i(.*)", kScrollWindow_subops, NULL },
+ { MAP_CALL(SetFontRes), SIG_SCI21EARLY, SIGFOR_ALL, "ii", NULL, NULL },
+ { MAP_CALL(Font), SIG_SINCE_SCI21MID, SIGFOR_ALL, "i(.*)", kFont_subops, NULL },
+ { MAP_CALL(Bitmap), SIG_EVERYWHERE, "(.*)", kBitmap_subops, NULL },
{ MAP_CALL(AddLine), SIG_EVERYWHERE, "oiiiiiiiii", NULL, NULL },
{ MAP_CALL(UpdateLine), SIG_EVERYWHERE, "[r0]oiiiiiiiii", NULL, NULL },
{ MAP_CALL(DeleteLine), SIG_EVERYWHERE, "[r0]o", NULL, NULL },
@@ -699,9 +787,9 @@ static SciKernelMapEntry s_kernelMap[] = {
// <lskovlun> The idea, if I understand correctly, is that the engine generates events
// of a special HotRect type continuously when the mouse is on that rectangle
- // MovePlaneItems - used by SQ6 to scroll through the inventory via the up/down buttons
- // SetPalStyleRange - 2 integer parameters, start and end. All styles from start-end
- // (inclusive) are set to 0
+ // Used by SQ6 to scroll through the inventory via the up/down buttons
+ { MAP_CALL(MovePlaneItems), SIG_SINCE_SCI21, SIGFOR_ALL, "oii(i)", NULL, NULL },
+
{ MAP_CALL(SetPalStyleRange), SIG_EVERYWHERE, "ii", NULL, NULL },
{ MAP_CALL(MorphOn), SIG_EVERYWHERE, "", NULL, NULL },
@@ -1004,7 +1092,6 @@ static const char *const sci2_default_knames[] = {
/*0x89*/ "TextWidth", // for debugging(?), only in SCI2, not used in any SCI2 game
/*0x8a*/ "PointSize", // for debugging(?), only in SCI2, not used in any SCI2 game
- // GK2 Demo (and similar) only kernel functions
/*0x8b*/ "AddLine",
/*0x8c*/ "DeleteLine",
/*0x8d*/ "UpdateLine",
diff --git a/engines/sci/engine/kevent.cpp b/engines/sci/engine/kevent.cpp
index bb595e9960..534d9ce713 100644
--- a/engines/sci/engine/kevent.cpp
+++ b/engines/sci/engine/kevent.cpp
@@ -83,11 +83,12 @@ reg_t kGetEvent(EngineState *s, int argc, reg_t *argv) {
}
// For a real event we use its associated mouse position
- mousePos = curEvent.mousePos;
#ifdef ENABLE_SCI32
- if (getSciVersion() >= SCI_VERSION_2_1_EARLY)
- g_sci->_gfxCoordAdjuster->fromDisplayToScript(mousePos.y, mousePos.x);
+ if (getSciVersion() >= SCI_VERSION_2)
+ mousePos = curEvent.mousePosSci;
+ else
#endif
+ mousePos = curEvent.mousePos;
// Limit the mouse cursor position, if necessary
g_sci->_gfxCursor->refreshPosition();
@@ -101,7 +102,25 @@ reg_t kGetEvent(EngineState *s, int argc, reg_t *argv) {
// question. Check GfxCursor::setPosition(), for a more detailed
// explanation and a list of cursor position workarounds.
if (s->_cursorWorkaroundRect.contains(mousePos.x, mousePos.y)) {
- s->_cursorWorkaroundActive = false;
+ // For OpenPandora and possibly other platforms, that support analog-stick control + touch screen
+ // control at the same time: in case the cursor is currently at the coordinate set by the scripts,
+ // we will count down instead of immediately disabling the workaround.
+ // On OpenPandora the cursor position is set, but it's overwritten shortly afterwards by the
+ // touch screen. In this case we would sometimes disable the workaround, simply because the touch
+ // screen hasn't yet overwritten the position and thus the workaround would not work anymore.
+ // On OpenPandora it would sometimes work and sometimes not without this.
+ if (s->_cursorWorkaroundPoint == mousePos) {
+ // Cursor is still at the same spot as set by the scripts
+ if (s->_cursorWorkaroundPosCount > 0) {
+ s->_cursorWorkaroundPosCount--;
+ } else {
+ // Was for quite a bit of time at that spot, so disable workaround now
+ s->_cursorWorkaroundActive = false;
+ }
+ } else {
+ // Cursor has moved, but is within the rect -> disable workaround immediately
+ s->_cursorWorkaroundActive = false;
+ }
} else {
mousePos.x = s->_cursorWorkaroundPoint.x;
mousePos.y = s->_cursorWorkaroundPoint.y;
diff --git a/engines/sci/engine/kfile.cpp b/engines/sci/engine/kfile.cpp
index 979fa95a42..335763a35f 100644
--- a/engines/sci/engine/kfile.cpp
+++ b/engines/sci/engine/kfile.cpp
@@ -37,7 +37,6 @@
#include "sci/engine/state.h"
#include "sci/engine/kernel.h"
#include "sci/engine/savegame.h"
-#include "sci/graphics/menu.h"
#include "sci/sound/audio.h"
#include "sci/console.h"
@@ -602,6 +601,16 @@ reg_t kFileIOExists(EngineState *s, int argc, reg_t *argv) {
bool exists = false;
+ if (g_sci->getGameId() == GID_PEPPER) {
+ // HACK: Special case for Pepper's Adventure in Time
+ // The game checks like crazy for the file CDAUDIO when entering the game menu.
+ // On at least Windows that makes the engine slow down to a crawl and takes at least 1 second.
+ // Should get solved properly by changing the code below. This here is basically for 1.8.0 release.
+ // TODO: Fix this properly.
+ if (name == "CDAUDIO")
+ return NULL_REG;
+ }
+
// Check for regular file
exists = Common::File::exists(name);
@@ -907,50 +916,8 @@ reg_t kRestoreGame(EngineState *s, int argc, reg_t *argv) {
gamestate_restore(s, in);
delete in;
- switch (g_sci->getGameId()) {
- case GID_MOTHERGOOSE:
- // WORKAROUND: Mother Goose SCI0
- // Script 200 / rm200::newRoom will set global C5h directly right after creating a child to the
- // current number of children plus 1.
- // We can't trust that global, that's why we set the actual savedgame id right here directly after
- // restoring a saved game.
- // If we didn't, the game would always save to a new slot
- s->variables[VAR_GLOBAL][0xC5].setOffset(SAVEGAMEID_OFFICIALRANGE_START + savegameId);
- break;
- case GID_MOTHERGOOSE256:
- // WORKAROUND: Mother Goose SCI1/SCI1.1 does some weird things for
- // saving a previously restored game.
- // We set the current savedgame-id directly and remove the script
- // code concerning this via script patch.
- s->variables[VAR_GLOBAL][0xB3].setOffset(SAVEGAMEID_OFFICIALRANGE_START + savegameId);
- break;
- case GID_JONES:
- // HACK: The code that enables certain menu items isn't called when a game is restored from the
- // launcher, or the "Restore game" option in the game's main menu - bugs #6537 and #6723.
- // These menu entries are disabled when the game is launched, and are enabled when a new game is
- // started. The code for enabling these entries is is all in script 1, room1::init, but that code
- // path is never followed in these two cases (restoring game from the menu, or restoring a game
- // from the ScummVM launcher). Thus, we perform the calls to enable the menus ourselves here.
- // These two are needed when restoring from the launcher
- // FIXME: The original interpreter saves and restores the menu state, so these attributes
- // are automatically reset there. We may want to do the same.
- g_sci->_gfxMenu->kernelSetAttribute(257 >> 8, 257 & 0xFF, SCI_MENU_ATTRIBUTE_ENABLED, TRUE_REG); // Sierra -> About Jones
- g_sci->_gfxMenu->kernelSetAttribute(258 >> 8, 258 & 0xFF, SCI_MENU_ATTRIBUTE_ENABLED, TRUE_REG); // Sierra -> Help
- // The rest are normally enabled from room1::init
- g_sci->_gfxMenu->kernelSetAttribute(769 >> 8, 769 & 0xFF, SCI_MENU_ATTRIBUTE_ENABLED, TRUE_REG); // Options -> Delete current player
- g_sci->_gfxMenu->kernelSetAttribute(513 >> 8, 513 & 0xFF, SCI_MENU_ATTRIBUTE_ENABLED, TRUE_REG); // Game -> Save Game
- g_sci->_gfxMenu->kernelSetAttribute(515 >> 8, 515 & 0xFF, SCI_MENU_ATTRIBUTE_ENABLED, TRUE_REG); // Game -> Restore Game
- g_sci->_gfxMenu->kernelSetAttribute(1025 >> 8, 1025 & 0xFF, SCI_MENU_ATTRIBUTE_ENABLED, TRUE_REG); // Status -> Statistics
- g_sci->_gfxMenu->kernelSetAttribute(1026 >> 8, 1026 & 0xFF, SCI_MENU_ATTRIBUTE_ENABLED, TRUE_REG); // Status -> Goals
- break;
- case GID_PQ2:
- // HACK: Same as above - enable the save game menu option when loading in PQ2 (bug #6875).
- // It gets disabled in the game's death screen.
- g_sci->_gfxMenu->kernelSetAttribute(2, 1, SCI_MENU_ATTRIBUTE_ENABLED, TRUE_REG); // Game -> Save Game
- break;
- default:
- break;
- }
+ gamestate_afterRestoreFixUp(s, savegameId);
+
} else {
s->r_acc = TRUE_REG;
warning("Savegame #%d not found", savegameId);
diff --git a/engines/sci/engine/kgraphics.cpp b/engines/sci/engine/kgraphics.cpp
index 0b945c1eec..73236b98ed 100644
--- a/engines/sci/engine/kgraphics.cpp
+++ b/engines/sci/engine/kgraphics.cpp
@@ -45,6 +45,7 @@
#include "sci/graphics/paint16.h"
#include "sci/graphics/picture.h"
#include "sci/graphics/ports.h"
+#include "sci/graphics/remap.h"
#include "sci/graphics/screen.h"
#include "sci/graphics/text16.h"
#include "sci/graphics/view.h"
@@ -359,12 +360,7 @@ reg_t kTextSize(EngineState *s, int argc, reg_t *argv) {
uint16 languageSplitter = 0;
Common::String splitText = g_sci->strSplitLanguage(text.c_str(), &languageSplitter, sep);
-#ifdef ENABLE_SCI32
- if (g_sci->_gfxText32)
- g_sci->_gfxText32->kernelTextSize(splitText.c_str(), font_nr, maxwidth, &textWidth, &textHeight);
- else
-#endif
- g_sci->_gfxText16->kernelTextSize(splitText.c_str(), languageSplitter, font_nr, maxwidth, &textWidth, &textHeight);
+ g_sci->_gfxText16->kernelTextSize(splitText.c_str(), languageSplitter, font_nr, maxwidth, &textWidth, &textHeight);
// One of the game texts in LB2 German contains loads of spaces in
// its end. We trim the text here, otherwise the graphics code will
@@ -445,8 +441,15 @@ reg_t kCantBeHere(EngineState *s, int argc, reg_t *argv) {
reg_t curObject = argv[0];
reg_t listReference = (argc > 1) ? argv[1] : NULL_REG;
- reg_t canBeHere = g_sci->_gfxCompare->kernelCanBeHere(curObject, listReference);
- return canBeHere;
+#ifdef ENABLE_SCI32
+ if (getSciVersion() >= SCI_VERSION_2) {
+ return g_sci->_gfxCompare->kernelCantBeHere32(curObject, listReference);
+ } else {
+#endif
+ return g_sci->_gfxCompare->kernelCanBeHere(curObject, listReference);
+#ifdef ENABLE_SCI32
+ }
+#endif
}
reg_t kIsItSkip(EngineState *s, int argc, reg_t *argv) {
@@ -492,7 +495,7 @@ reg_t kNumLoops(EngineState *s, int argc, reg_t *argv) {
loopCount = g_sci->_gfxCache->kernelViewGetLoopCount(viewId);
- debugC(kDebugLevelGraphics, "NumLoops(view.%d) = %d", viewId, loopCount);
+ debugC(9, kDebugLevelGraphics, "NumLoops(view.%d) = %d", viewId, loopCount);
return make_reg(0, loopCount);
}
@@ -505,7 +508,7 @@ reg_t kNumCels(EngineState *s, int argc, reg_t *argv) {
celCount = g_sci->_gfxCache->kernelViewGetCelCount(viewId, loopNo);
- debugC(kDebugLevelGraphics, "NumCels(view.%d, %d) = %d", viewId, loopNo, celCount);
+ debugC(9, kDebugLevelGraphics, "NumCels(view.%d, %d) = %d", viewId, loopNo, celCount);
return make_reg(0, celCount);
}
@@ -576,9 +579,17 @@ reg_t kBaseSetter(EngineState *s, int argc, reg_t *argv) {
}
reg_t kSetNowSeen(EngineState *s, int argc, reg_t *argv) {
- g_sci->_gfxCompare->kernelSetNowSeen(argv[0]);
-
- return s->r_acc;
+#ifdef ENABLE_SCI32
+ if (getSciVersion() >= SCI_VERSION_2) {
+ g_sci->_gfxFrameout->kernelSetNowSeen(argv[0]);
+ return NULL_REG;
+ } else {
+#endif
+ g_sci->_gfxCompare->kernelSetNowSeen(argv[0]);
+ return s->r_acc;
+#ifdef ENABLE_SCI32
+ }
+#endif
}
reg_t kPalette(EngineState *s, int argc, reg_t *argv) {
@@ -1247,22 +1258,22 @@ reg_t kShow(EngineState *s, int argc, reg_t *argv) {
}
// Early variant of the SCI32 kRemapColors kernel function, used in the demo of QFG4
-reg_t kRemapColors(EngineState *s, int argc, reg_t *argv) {
+reg_t kRemapColors16(EngineState *s, int argc, reg_t *argv) {
uint16 operation = argv[0].toUint16();
switch (operation) {
case 0: { // remap by percent
uint16 percent = argv[1].toUint16();
- g_sci->_gfxPalette16->resetRemapping();
- g_sci->_gfxPalette16->setRemappingPercent(254, percent);
+ g_sci->_gfxRemap16->resetRemapping();
+ g_sci->_gfxRemap16->setRemappingPercent(254, percent);
}
break;
case 1: { // remap by range
uint16 from = argv[1].toUint16();
uint16 to = argv[2].toUint16();
uint16 base = argv[3].toUint16();
- g_sci->_gfxPalette16->resetRemapping();
- g_sci->_gfxPalette16->setRemappingRange(254, from, to, base);
+ g_sci->_gfxRemap16->resetRemapping();
+ g_sci->_gfxRemap16->setRemappingRange(254, from, to, base);
}
break;
case 2: // turn remapping off (unused)
diff --git a/engines/sci/engine/kgraphics32.cpp b/engines/sci/engine/kgraphics32.cpp
index 8d41393a9e..7850a10006 100644
--- a/engines/sci/engine/kgraphics32.cpp
+++ b/engines/sci/engine/kgraphics32.cpp
@@ -45,15 +45,17 @@
#include "sci/graphics/paint16.h"
#include "sci/graphics/picture.h"
#include "sci/graphics/ports.h"
+#include "sci/graphics/remap.h"
#include "sci/graphics/screen.h"
#include "sci/graphics/text16.h"
#include "sci/graphics/view.h"
#ifdef ENABLE_SCI32
-#include "sci/graphics/palette32.h"
+#include "sci/graphics/celobj32.h"
#include "sci/graphics/controls32.h"
#include "sci/graphics/font.h" // TODO: remove once kBitmap is moved in a separate class
-#include "sci/graphics/text32.h"
#include "sci/graphics/frameout.h"
+#include "sci/graphics/palette32.h"
+#include "sci/graphics/text32.h"
#endif
namespace Sci {
@@ -62,63 +64,67 @@ namespace Sci {
extern void showScummVMDialog(const Common::String &message);
reg_t kIsHiRes(EngineState *s, int argc, reg_t *argv) {
- // Returns 0 if the screen width or height is less than 640 or 400,
- // respectively.
- if (g_system->getWidth() < 640 || g_system->getHeight() < 400)
+ const Buffer &buffer = g_sci->_gfxFrameout->getCurrentBuffer();
+ if (buffer.screenWidth < 640 || buffer.screenHeight < 400)
return make_reg(0, 0);
return make_reg(0, 1);
}
-// SCI32 variant, can't work like sci16 variants
-reg_t kCantBeHere32(EngineState *s, int argc, reg_t *argv) {
- // TODO
-// reg_t curObject = argv[0];
-// reg_t listReference = (argc > 1) ? argv[1] : NULL_REG;
-
- return NULL_REG;
-}
-
reg_t kAddScreenItem(EngineState *s, int argc, reg_t *argv) {
- if (g_sci->_gfxFrameout->findScreenItem(argv[0]) == NULL)
- g_sci->_gfxFrameout->kernelAddScreenItem(argv[0]);
- else
- g_sci->_gfxFrameout->kernelUpdateScreenItem(argv[0]);
+ debugC(6, kDebugLevelGraphics, "kAddScreenItem %x:%x (%s)", PRINT_REG(argv[0]), s->_segMan->getObjectName(argv[0]));
+ g_sci->_gfxFrameout->kernelAddScreenItem(argv[0]);
return s->r_acc;
}
reg_t kUpdateScreenItem(EngineState *s, int argc, reg_t *argv) {
+ debugC(7, kDebugLevelGraphics, "kUpdateScreenItem %x:%x (%s)", PRINT_REG(argv[0]), s->_segMan->getObjectName(argv[0]));
g_sci->_gfxFrameout->kernelUpdateScreenItem(argv[0]);
return s->r_acc;
}
reg_t kDeleteScreenItem(EngineState *s, int argc, reg_t *argv) {
+ debugC(6, kDebugLevelGraphics, "kDeleteScreenItem %x:%x (%s)", PRINT_REG(argv[0]), s->_segMan->getObjectName(argv[0]));
g_sci->_gfxFrameout->kernelDeleteScreenItem(argv[0]);
return s->r_acc;
}
reg_t kAddPlane(EngineState *s, int argc, reg_t *argv) {
+ debugC(6, kDebugLevelGraphics, "kAddPlane %x:%x (%s)", PRINT_REG(argv[0]), s->_segMan->getObjectName(argv[0]));
g_sci->_gfxFrameout->kernelAddPlane(argv[0]);
return s->r_acc;
}
+reg_t kUpdatePlane(EngineState *s, int argc, reg_t *argv) {
+ debugC(7, kDebugLevelGraphics, "kUpdatePlane %x:%x (%s)", PRINT_REG(argv[0]), s->_segMan->getObjectName(argv[0]));
+ g_sci->_gfxFrameout->kernelUpdatePlane(argv[0]);
+ return s->r_acc;
+}
+
reg_t kDeletePlane(EngineState *s, int argc, reg_t *argv) {
+ debugC(6, kDebugLevelGraphics, "kDeletePlane %x:%x (%s)", PRINT_REG(argv[0]), s->_segMan->getObjectName(argv[0]));
g_sci->_gfxFrameout->kernelDeletePlane(argv[0]);
return s->r_acc;
}
-reg_t kUpdatePlane(EngineState *s, int argc, reg_t *argv) {
- g_sci->_gfxFrameout->kernelUpdatePlane(argv[0]);
+reg_t kMovePlaneItems(EngineState *s, int argc, reg_t *argv) {
+ const reg_t plane = argv[0];
+ const int16 deltaX = argv[1].toSint16();
+ const int16 deltaY = argv[2].toSint16();
+ const bool scrollPics = argc > 3 ? argv[3].toUint16() : false;
+
+ g_sci->_gfxFrameout->kernelMovePlaneItems(plane, deltaX, deltaY, scrollPics);
return s->r_acc;
}
reg_t kAddPicAt(EngineState *s, int argc, reg_t *argv) {
reg_t planeObj = argv[0];
GuiResourceId pictureId = argv[1].toUint16();
- int16 pictureX = argv[2].toSint16();
- int16 pictureY = argv[3].toSint16();
+ int16 x = argv[2].toSint16();
+ int16 y = argv[3].toSint16();
+ bool mirrorX = argc > 4 ? argv[4].toSint16() : false;
- g_sci->_gfxFrameout->kernelAddPicAt(planeObj, pictureId, pictureX, pictureY);
+ g_sci->_gfxFrameout->kernelAddPicAt(planeObj, pictureId, x, y, mirrorX);
return s->r_acc;
}
@@ -127,44 +133,16 @@ reg_t kGetHighPlanePri(EngineState *s, int argc, reg_t *argv) {
}
reg_t kFrameOut(EngineState *s, int argc, reg_t *argv) {
-/* TODO: Transcribed from SCI engine disassembly.
- GraphicsMgr &graphicsMgr = g_sci->_graphicsMgr;
- if (graphicsMgr.palMorphNeeded) {
- graphicsMgr.PalMorphFrameOut(&g_PalStyleRanges, false);
- }
- else {
- // TODO: Not sure if this is a pointer or not yet.
- if (g_ScrollState != nullptr) {
- kFrameOutDoScroll();
- }
-
- bool showBits = true;
- if (argc == 1) {
- showBits = (bool) argv[0].toUint16();
- }
-
- rect SOL_Rect = { .left = 0, .top = 0, .right = UINT32_MAX, .bottom = UINT32_MAX };
- graphicsMgr.FrameOut(showBits, &rect);
- }
-*/
- g_sci->_gfxFrameout->kernelFrameout();
- return NULL_REG;
+ bool showBits = argc > 0 ? argv[0].toUint16() : true;
+ g_sci->_gfxFrameout->kernelFrameOut(showBits);
+ s->speedThrottler(16);
+ s->_throttleTrigger = true;
+ return s->r_acc;
}
reg_t kSetPalStyleRange(EngineState *s, int argc, reg_t *argv) {
-/* TODO: Transcribed from SCI engine disassembly.
- uint16 start = argv[0].toUint16();
- uint16 end = argv[1].toUint16();
- if (end <= start) {
- uint16 index = start;
- while (index <= end) {
- g_PalStyleRanges[index] = 0;
- }
- }
-*/
-
- kStub(s, argc, argv);
- return NULL_REG;
+ g_sci->_gfxFrameout->kernelSetPalStyleRange(argv[0].toUint16(), argv[1].toUint16());
+ return s->r_acc;
}
reg_t kObjectIntersect(EngineState *s, int argc, reg_t *argv) {
@@ -173,81 +151,92 @@ reg_t kObjectIntersect(EngineState *s, int argc, reg_t *argv) {
return make_reg(0, objRect1.intersects(objRect2));
}
-// Tests if the coordinate is on the passed object
reg_t kIsOnMe(EngineState *s, int argc, reg_t *argv) {
- uint16 x = argv[0].toUint16();
- uint16 y = argv[1].toUint16();
- reg_t targetObject = argv[2];
- uint16 illegalBits = argv[3].getOffset();
- Common::Rect nsRect = g_sci->_gfxCompare->getNSRect(targetObject);
-
- uint16 itemX = readSelectorValue(s->_segMan, targetObject, SELECTOR(x));
- uint16 itemY = readSelectorValue(s->_segMan, targetObject, SELECTOR(y));
- // If top and left are negative, we need to adjust coordinates by the item's x and y
- if (nsRect.left < 0)
- nsRect.translate(itemX, 0);
- if (nsRect.top < 0)
- nsRect.translate(0, itemY);
-
- // we assume that x, y are local coordinates
-
- bool contained = nsRect.contains(x, y);
- if (contained && illegalBits) {
- // If illegalbits are set, we check the color of the pixel that got clicked on
- // for now, we return false if the pixel is transparent
- // although illegalBits may get differently set, don't know yet how this really works out
- uint16 viewId = readSelectorValue(s->_segMan, targetObject, SELECTOR(view));
- int16 loopNo = readSelectorValue(s->_segMan, targetObject, SELECTOR(loop));
- int16 celNo = readSelectorValue(s->_segMan, targetObject, SELECTOR(cel));
- if (g_sci->_gfxCompare->kernelIsItSkip(viewId, loopNo, celNo, Common::Point(x - nsRect.left, y - nsRect.top)))
- contained = false;
- }
- return make_reg(0, contained);
+ int16 x = argv[0].toSint16();
+ int16 y = argv[1].toSint16();
+ reg_t object = argv[2];
+ bool checkPixel = argv[3].toSint16();
+
+ return g_sci->_gfxFrameout->kernelIsOnMe(object, Common::Point(x, y), checkPixel);
}
reg_t kCreateTextBitmap(EngineState *s, int argc, reg_t *argv) {
- switch (argv[0].toUint16()) {
- case 0: {
- if (argc != 4) {
- warning("kCreateTextBitmap(0): expected 4 arguments, got %i", argc);
- return NULL_REG;
- }
- reg_t object = argv[3];
- Common::String text = s->_segMan->getString(readSelector(s->_segMan, object, SELECTOR(text)));
- 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());
- 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: {
- if (argc != 2) {
- warning("kCreateTextBitmap(1): expected 2 arguments, got %i", argc);
- return NULL_REG;
- }
- reg_t object = argv[1];
- Common::String text = s->_segMan->getString(readSelector(s->_segMan, object, SELECTOR(text)));
- debugC(kDebugLevelStrings, "kCreateTextBitmap case 1 (%04x:%04x)", PRINT_REG(argv[1]));
- debugC(kDebugLevelStrings, "%s", text.c_str());
- return g_sci->_gfxText32->createTextBitmap(object);
- }
- default:
- warning("CreateTextBitmap(%d)", argv[0].toUint16());
+ SegManager *segMan = s->_segMan;
+
+ int16 subop = argv[0].toUint16();
+
+ int16 width = 0;
+ int16 height = 0;
+ reg_t object;
+
+ if (subop == 0) {
+ width = argv[1].toUint16();
+ height = argv[2].toUint16();
+ object = argv[3];
+ } else if (subop == 1) {
+ object = argv[1];
+ } else {
+ warning("Invalid kCreateTextBitmap subop %d", subop);
return NULL_REG;
}
+
+ Common::String text = segMan->getString(readSelector(segMan, object, SELECTOR(text)));
+ int16 foreColor = readSelectorValue(segMan, object, SELECTOR(fore));
+ int16 backColor = readSelectorValue(segMan, object, SELECTOR(back));
+ int16 skipColor = readSelectorValue(segMan, object, SELECTOR(skip));
+ GuiResourceId fontId = (GuiResourceId)readSelectorValue(segMan, object, SELECTOR(font));
+ int16 borderColor = readSelectorValue(segMan, object, SELECTOR(borderColor));
+ int16 dimmed = readSelectorValue(segMan, object, SELECTOR(dimmed));
+
+ Common::Rect rect(
+ readSelectorValue(segMan, object, SELECTOR(textLeft)),
+ readSelectorValue(segMan, object, SELECTOR(textTop)),
+ readSelectorValue(segMan, object, SELECTOR(textRight)) + 1,
+ readSelectorValue(segMan, object, SELECTOR(textBottom)) + 1
+ );
+
+ if (subop == 0) {
+ TextAlign alignment = (TextAlign)readSelectorValue(segMan, object, SELECTOR(mode));
+ return g_sci->_gfxText32->createFontBitmap(width, height, rect, text, foreColor, backColor, skipColor, fontId, alignment, borderColor, dimmed, true);
+ } else {
+ CelInfo32 celInfo;
+ celInfo.type = kCelTypeView;
+ celInfo.resourceId = readSelectorValue(segMan, object, SELECTOR(view));
+ celInfo.loopNo = readSelectorValue(segMan, object, SELECTOR(loop));
+ celInfo.celNo = readSelectorValue(segMan, object, SELECTOR(cel));
+ return g_sci->_gfxText32->createFontBitmap(celInfo, rect, text, foreColor, backColor, fontId, skipColor, borderColor, dimmed);
+ }
+}
+
+reg_t kText(EngineState *s, int argc, reg_t *argv) {
+ if (!s)
+ return make_reg(0, getSciVersion());
+ error("not supposed to call this");
}
-reg_t kDisposeTextBitmap(EngineState *s, int argc, reg_t *argv) {
- g_sci->_gfxText32->disposeTextBitmap(argv[0]);
+reg_t kTextSize32(EngineState *s, int argc, reg_t *argv) {
+ g_sci->_gfxText32->setFont(argv[2].toUint16());
+
+ reg_t *rect = s->_segMan->derefRegPtr(argv[0], 4);
+
+ Common::String text = s->_segMan->getString(argv[1]);
+ int16 maxWidth = argc > 3 ? argv[3].toSint16() : 0;
+ bool doScaling = argc > 4 ? argv[4].toSint16() : true;
+
+ Common::Rect textRect = g_sci->_gfxText32->getTextSize(text, maxWidth, doScaling);
+ rect[0] = make_reg(0, textRect.left);
+ rect[1] = make_reg(0, textRect.top);
+ rect[2] = make_reg(0, textRect.right - 1);
+ rect[3] = make_reg(0, textRect.bottom - 1);
return s->r_acc;
}
+reg_t kTextWidth(EngineState *s, int argc, reg_t *argv) {
+ g_sci->_gfxText32->setFont(argv[1].toUint16());
+ Common::String text = s->_segMan->getString(argv[0]);
+ return make_reg(0, g_sci->_gfxText32->getStringWidth(text));
+}
+
reg_t kWinHelp(EngineState *s, int argc, reg_t *argv) {
switch (argv[0].toUint16()) {
case 1:
@@ -266,98 +255,137 @@ reg_t kWinHelp(EngineState *s, int argc, reg_t *argv) {
}
/**
- * Used for scene transitions, replacing (but reusing parts of) the old
- * transition code.
+ * Causes an immediate plane transition with an optional transition
+ * effect
*/
reg_t kSetShowStyle(EngineState *s, int argc, reg_t *argv) {
- // Can be called with 7 or 8 parameters
- // The style defines which transition to perform. Related to the transition
- // tables inside graphics/transitions.cpp
- uint16 showStyle = argv[0].toUint16(); // 0 - 15
- reg_t planeObj = argv[1]; // the affected plane
- Common::String planeObjName = s->_segMan->getObjectName(planeObj);
- uint16 seconds = argv[2].toUint16(); // seconds that the transition lasts
- uint16 backColor = argv[3].toUint16(); // target back color(?). When fading out, it's 0x0000. When fading in, it's 0xffff
- int16 priority = argv[4].toSint16(); // always 0xc8 (200) when fading in/out
- uint16 animate = argv[5].toUint16(); // boolean, animate or not while the transition lasts
- uint16 refFrame = argv[6].toUint16(); // refFrame, always 0 when fading in/out
+ ShowStyleType type = (ShowStyleType)argv[0].toUint16();
+ reg_t planeObj = argv[1];
+ int16 seconds = argv[2].toSint16();
+ // NOTE: This value seems to indicate whether the transition is an
+ // “exit” transition (0) or an “enter” transition (-1) for fade
+ // transitions. For other types of transitions, it indicates a palette
+ // index value to use when filling the screen.
+ int16 back = argv[3].toSint16();
+ int16 priority = argv[4].toSint16();
+ int16 animate = argv[5].toSint16();
+ // TODO: Rename to frameOutNow?
+ int16 refFrame = argv[6].toSint16();
+ int16 blackScreen;
+ reg_t pFadeArray;
int16 divisions;
- // If the game has the pFadeArray selector, another parameter is used here,
- // before the optional last parameter
- bool hasFadeArray = g_sci->getKernel()->findSelector("pFadeArray") > 0;
- if (hasFadeArray) {
- // argv[7]
- divisions = (argc >= 9) ? argv[8].toSint16() : -1; // divisions (transition steps?)
- } else {
- divisions = (argc >= 8) ? argv[7].toSint16() : -1; // divisions (transition steps?)
+ // SCI 2–2.1early
+ if (getSciVersion() < SCI_VERSION_2_1_MIDDLE) {
+ blackScreen = 0;
+ pFadeArray = NULL_REG;
+ divisions = argc > 7 ? argv[7].toSint16() : -1;
}
-
- if (showStyle > 15) {
- warning("kSetShowStyle: Illegal style %d for plane %04x:%04x", showStyle, PRINT_REG(planeObj));
- return s->r_acc;
+ // SCI 2.1mid–2.1late
+ else if (getSciVersion() < SCI_VERSION_3) {
+ blackScreen = 0;
+ pFadeArray = argc > 7 ? argv[7] : NULL_REG;
+ divisions = argc > 8 ? argv[8].toSint16() : -1;
+ }
+ // SCI 3
+ else {
+ blackScreen = argv[7].toSint16();
+ pFadeArray = argc > 8 ? argv[8] : NULL_REG;
+ divisions = argc > 9 ? argv[9].toSint16() : -1;
}
- // GK1 calls fadeout (13) / fadein (14) with the following parameters:
- // seconds: 1
- // backColor: 0 / -1
- // fade: 200
- // animate: 0
- // refFrame: 0
- // divisions: 0 / 20
+// TODO: Reuse later for SCI2 and SCI3 implementation and then discard
+// warning("kSetShowStyle: effect %d, plane: %04x:%04x (%s), sec: %d, "
+// "dir: %d, prio: %d, animate: %d, ref frame: %d, black screen: %d, "
+// "pFadeArray: %04x:%04x (%s), divisions: %d",
+// type, PRINT_REG(planeObj), s->_segMan->getObjectName(planeObj), seconds,
+// back, priority, animate, refFrame, blackScreen,
+// PRINT_REG(pFadeArray), s->_segMan->getObjectName(pFadeArray), divisions);
- // TODO: Check if the plane is in the list of planes to draw
+ // NOTE: The order of planeObj and showStyle are reversed
+ // because this is how SCI3 called the corresponding method
+ // on the KernelMgr
+ g_sci->_gfxFrameout->kernelSetShowStyle(argc, planeObj, type, seconds, back, priority, animate, refFrame, pFadeArray, divisions, blackScreen);
- Common::String effectName = "unknown";
+ return s->r_acc;
+}
- switch (showStyle) {
- case 0: // no transition / show
- effectName = "show";
+reg_t kCelHigh32(EngineState *s, int argc, reg_t *argv) {
+ GuiResourceId resourceId = argv[0].toUint16();
+ int16 loopNo = argv[1].toSint16();
+ int16 celNo = argv[2].toSint16();
+ CelObjView celObj(resourceId, loopNo, celNo);
+ return make_reg(0, mulru(celObj._height, Ratio(g_sci->_gfxFrameout->getCurrentBuffer().scriptHeight, celObj._scaledHeight)));
+}
+
+reg_t kCelWide32(EngineState *s, int argc, reg_t *argv) {
+ GuiResourceId resourceId = argv[0].toUint16();
+ int16 loopNo = argv[1].toSint16();
+ int16 celNo = argv[2].toSint16();
+ CelObjView celObj(resourceId, loopNo, celNo);
+ return make_reg(0, mulru(celObj._width, Ratio(g_sci->_gfxFrameout->getCurrentBuffer().scriptWidth, celObj._scaledWidth)));
+}
+
+reg_t kCelInfo(EngineState *s, int argc, reg_t *argv) {
+ // Used by Shivers 1, room 23601 to determine what blocks on the red door puzzle board
+ // are occupied by pieces already
+
+ CelObjView view(argv[1].toUint16(), argv[2].toSint16(), argv[3].toSint16());
+
+ int16 result = 0;
+
+ switch (argv[0].toUint16()) {
+ case 0:
+ result = view._displace.x;
break;
- case 13: // fade out
- effectName = "fade out";
- // TODO
+ case 1:
+ result = view._displace.y;
break;
- case 14: // fade in
- effectName = "fade in";
- // TODO
+ case 2:
+ case 3:
+ // null operation
break;
- default:
- // TODO
+ case 4:
+ result = view.readPixel(argv[4].toSint16(), argv[5].toSint16(), view._mirrorX);
break;
}
- warning("kSetShowStyle: effect %d (%s) - plane: %04x:%04x (%s), sec: %d, "
- "back: %d, prio: %d, animate: %d, ref frame: %d, divisions: %d",
- showStyle, effectName.c_str(), PRINT_REG(planeObj), planeObjName.c_str(),
- seconds, backColor, priority, animate, refFrame, divisions);
- return s->r_acc;
+ return make_reg(0, result);
}
-reg_t kCelInfo(EngineState *s, int argc, reg_t *argv) {
- // Used by Shivers 1, room 23601 to determine what blocks on the red door puzzle board
- // are occupied by pieces already
+reg_t kScrollWindow(EngineState *s, int argc, reg_t *argv) {
+ if (!s)
+ return make_reg(0, getSciVersion());
+ error("not supposed to call this");
+}
- switch (argv[0].toUint16()) { // subops 0 - 4
- // 0 - return the view
- // 1 - return the loop
- // 2, 3 - nop
- case 4: {
- GuiResourceId viewId = argv[1].toSint16();
- int16 loopNo = argv[2].toSint16();
- int16 celNo = argv[3].toSint16();
- int16 x = argv[4].toUint16();
- int16 y = argv[5].toUint16();
- byte color = g_sci->_gfxCache->kernelViewGetColorAtCoordinate(viewId, loopNo, celNo, x, y);
- return make_reg(0, color);
- }
- default: {
- kStub(s, argc, argv);
- return s->r_acc;
- }
- }
+reg_t kScrollWindowCreate(EngineState *s, int argc, reg_t *argv) {
+ debug("kScrollWindowCreate");
+ kStub(s, argc, argv);
+ return argv[0];
+}
+
+reg_t kScrollWindowAdd(EngineState *s, int argc, reg_t *argv) {
+ debug("kScrollWindowAdd");
+ return kStubNull(s, argc, argv);
}
+reg_t kScrollWindowWhere(EngineState *s, int argc, reg_t *argv) {
+ debug("kScrollWindowWhere");
+ return kStubNull(s, argc, argv);
+}
+
+reg_t kScrollWindowShow(EngineState *s, int argc, reg_t *argv) {
+ debug("kScrollWindowShow");
+ return kStubNull(s, argc, argv);
+}
+
+reg_t kScrollWindowDestroy(EngineState *s, int argc, reg_t *argv) {
+ debug("kScrollWindowDestroy");
+ return kStubNull(s, argc, argv);
+}
+
+#if 0
reg_t kScrollWindow(EngineState *s, int argc, reg_t *argv) {
// Used by SQ6 and LSL6 hires for the text area in the bottom of the
// screen. The relevant scripts also exist in Phantasmagoria 1, but they're
@@ -465,226 +493,233 @@ reg_t kScrollWindow(EngineState *s, int argc, reg_t *argv) {
return s->r_acc;
}
+#endif
+
+reg_t kFont(EngineState *s, int argc, reg_t *argv) {
+ if (!s)
+ return make_reg(0, getSciVersion());
+ error("not supposed to call this");
+}
+
+reg_t kSetFontHeight(EngineState *s, int argc, reg_t *argv) {
+ // TODO: Setting font may have just been for side effect
+ // of setting the fontHeight on the font manager, in
+ // which case we could just get the font directly ourselves.
+ g_sci->_gfxText32->setFont(argv[0].toUint16());
+ g_sci->_gfxText32->_scaledHeight = (g_sci->_gfxText32->_font->getHeight() * g_sci->_gfxFrameout->getCurrentBuffer().scriptHeight + g_sci->_gfxText32->_scaledHeight - 1) / g_sci->_gfxText32->_scaledHeight;
+ return make_reg(0, g_sci->_gfxText32->_scaledHeight);
+}
reg_t kSetFontRes(EngineState *s, int argc, reg_t *argv) {
- // TODO: This defines the resolution that the fonts are supposed to be displayed
- // in. Currently, this is only used for showing high-res fonts in GK1 Mac, but
- // should be extended to handle other font resolutions such as those
+ g_sci->_gfxText32->_scaledWidth = argv[0].toUint16();
+ g_sci->_gfxText32->_scaledHeight = argv[1].toUint16();
+ return s->r_acc;
+}
- int xResolution = argv[0].toUint16();
- //int yResolution = argv[1].toUint16();
+reg_t kBitmap(EngineState *s, int argc, reg_t *argv) {
+ if (!s)
+ return make_reg(0, getSciVersion());
+ error("not supposed to call this");
+}
- g_sci->_gfxScreen->setFontIsUpscaled(xResolution == 640 &&
- g_sci->_gfxScreen->getUpscaledHires() != GFX_SCREEN_UPSCALED_DISABLED);
+reg_t kBitmapCreate(EngineState *s, int argc, reg_t *argv) {
+ int16 width = argv[0].toSint16();
+ int16 height = argv[1].toSint16();
+ int16 skipColor = argv[2].toSint16();
+ int16 backColor = argv[3].toSint16();
+ int16 scaledWidth = argc > 4 ? argv[4].toSint16() : g_sci->_gfxText32->_scaledWidth;
+ int16 scaledHeight = argc > 5 ? argv[5].toSint16() : g_sci->_gfxText32->_scaledHeight;
+ bool useRemap = argc > 6 ? argv[6].toSint16() : false;
- return s->r_acc;
+ BitmapResource bitmap(s->_segMan, width, height, skipColor, 0, 0, scaledWidth, scaledHeight, 0, useRemap);
+ memset(bitmap.getPixels(), backColor, width * height);
+ return bitmap.getObject();
}
-reg_t kFont(EngineState *s, int argc, reg_t *argv) {
- // Handle font settings for SCI2.1
+reg_t kBitmapDestroy(EngineState *s, int argc, reg_t *argv) {
+ s->_segMan->freeHunkEntry(argv[0]);
+ return s->r_acc;
+}
- switch (argv[0].toUint16()) {
- case 1:
- // Set font resolution
- return kSetFontRes(s, argc - 1, argv + 1);
- default:
- warning("kFont: unknown subop %d", argv[0].toUint16());
+reg_t kBitmapDrawLine(EngineState *s, int argc, reg_t *argv) {
+ // bitmapMemId, (x1, y1, x2, y2) OR (x2, y2, x1, y1), line color, unknown int, unknown int
+ return kStubNull(s, argc + 1, argv - 1);
+}
+
+reg_t kBitmapDrawView(EngineState *s, int argc, reg_t *argv) {
+ // viewId, loopNo, celNo, displace x, displace y, unused, view x, view y
+
+ // called e.g. from TiledBitmap::resize() in Torin's Passage, script 64869
+ // The tiled view seems to always have 2 loops.
+ // These loops need to have 1 cel in loop 0 and 8 cels in loop 1.
+
+ return kStubNull(s, argc + 1, argv - 1);
+
+#if 0
+ // tiled surface
+ // 6 params, called e.g. from TiledBitmap::resize() in Torin's Passage,
+ // script 64869
+ reg_t hunkId = argv[1]; // obtained from kBitmap(0)
+ // The tiled view seems to always have 2 loops.
+ // These loops need to have 1 cel in loop 0 and 8 cels in loop 1.
+ uint16 viewNum = argv[2].toUint16(); // vTiles selector
+ uint16 loop = argv[3].toUint16();
+ uint16 cel = argv[4].toUint16();
+ uint16 x = argv[5].toUint16();
+ uint16 y = argv[6].toUint16();
+
+ byte *memoryPtr = s->_segMan->getHunkPointer(hunkId);
+ // Get totalWidth, totalHeight
+ uint16 totalWidth = READ_LE_UINT16(memoryPtr);
+ uint16 totalHeight = READ_LE_UINT16(memoryPtr + 2);
+ byte *bitmap = memoryPtr + BITMAP_HEADER_SIZE;
+
+ GfxView *view = g_sci->_gfxCache->getView(viewNum);
+ uint16 tileWidth = view->getWidth(loop, cel);
+ uint16 tileHeight = view->getHeight(loop, cel);
+ const byte *tileBitmap = view->getBitmap(loop, cel);
+ uint16 width = MIN<uint16>(totalWidth - x, tileWidth);
+ uint16 height = MIN<uint16>(totalHeight - y, tileHeight);
+
+ for (uint16 curY = 0; curY < height; curY++) {
+ for (uint16 curX = 0; curX < width; curX++) {
+ bitmap[(curY + y) * totalWidth + (curX + x)] = tileBitmap[curY * tileWidth + curX];
+ }
}
+#endif
+}
+
+reg_t kBitmapDrawText(EngineState *s, int argc, reg_t *argv) {
+ // called e.g. from TextButton::createBitmap() in Torin's Passage, script 64894
+
+ BitmapResource bitmap(argv[0]);
+ Common::String text = s->_segMan->getString(argv[1]);
+ Common::Rect textRect(
+ argv[2].toSint16(),
+ argv[3].toSint16(),
+ argv[4].toSint16() + 1,
+ argv[5].toSint16() + 1
+ );
+ int16 foreColor = argv[6].toSint16();
+ int16 backColor = argv[7].toSint16();
+ int16 skipColor = argv[8].toSint16();
+ GuiResourceId fontId = (GuiResourceId)argv[9].toUint16();
+ TextAlign alignment = (TextAlign)argv[10].toSint16();
+ int16 borderColor = argv[11].toSint16();
+ bool dimmed = argv[12].toUint16();
+
+ // NOTE: Technically the engine checks these things:
+ // textRect.bottom > 0
+ // textRect.right > 0
+ // textRect.left < bitmap.width
+ // textRect.top < bitmap.height
+ // Then clips. But this seems stupid.
+ textRect.clip(Common::Rect(bitmap.getWidth(), bitmap.getHeight()));
+
+ reg_t textBitmapObject = g_sci->_gfxText32->createFontBitmap(textRect.width(), textRect.height(), Common::Rect(textRect.width(), textRect.height()), text, foreColor, backColor, skipColor, fontId, alignment, borderColor, dimmed, false);
+ Buffer bitmapBuffer(bitmap.getWidth(), bitmap.getHeight(), bitmap.getPixels());
+ CelObjMem textCel(textBitmapObject);
+ textCel.draw(bitmapBuffer, textRect, Common::Point(textRect.left, textRect.top), false);
+ s->_segMan->freeHunkEntry(textBitmapObject);
return s->r_acc;
}
-// TODO: Eventually, all of the kBitmap operations should be put
-// in a separate class
+reg_t kBitmapDrawColor(EngineState *s, int argc, reg_t *argv) {
+ // called e.g. from TextView::init() and TextView::draw() in Torin's Passage, script 64890
-#define BITMAP_HEADER_SIZE 46
+ BitmapResource bitmap(argv[0]);
+ Common::Rect fillRect(
+ argv[1].toSint16(),
+ argv[2].toSint16(),
+ argv[3].toSint16() + 1,
+ argv[4].toSint16() + 1
+ );
-reg_t kBitmap(EngineState *s, int argc, reg_t *argv) {
- // Used for bitmap operations in SCI2.1 and SCI3.
- // This is the SCI2.1 version, the functionality seems to have changed in SCI3.
+ Buffer buffer(bitmap.getWidth(), bitmap.getHeight(), bitmap.getPixels());
+ buffer.fillRect(fillRect, argv[5].toSint16());
+ return s->r_acc;
+}
- switch (argv[0].toUint16()) {
- case 0: // init bitmap surface
- {
- // 6 params, called e.g. from TextView::init() in Torin's Passage,
- // script 64890 and TransView::init() in script 64884
- uint16 width = argv[1].toUint16();
- uint16 height = argv[2].toUint16();
- //uint16 skip = argv[3].toUint16();
- uint16 back = argv[4].toUint16(); // usually equals skip
- //uint16 width2 = (argc >= 6) ? argv[5].toUint16() : 0;
- //uint16 height2 = (argc >= 7) ? argv[6].toUint16() : 0;
- //uint16 transparentFlag = (argc >= 8) ? argv[7].toUint16() : 0;
-
- // TODO: skip, width2, height2, transparentFlag
- // (used for transparent bitmaps)
- int entrySize = width * height + BITMAP_HEADER_SIZE;
- reg_t memoryId = s->_segMan->allocateHunkEntry("Bitmap()", entrySize);
- byte *memoryPtr = s->_segMan->getHunkPointer(memoryId);
- memset(memoryPtr, 0, BITMAP_HEADER_SIZE); // zero out the bitmap header
- memset(memoryPtr + BITMAP_HEADER_SIZE, back, width * height);
- // Save totalWidth, totalHeight
- // TODO: Save the whole bitmap header, like SSCI does
- WRITE_LE_UINT16(memoryPtr, width);
- WRITE_LE_UINT16(memoryPtr + 2, height);
- return memoryId;
- }
- break;
- case 1: // dispose text bitmap surface
- return kDisposeTextBitmap(s, argc - 1, argv + 1);
- case 2: // dispose bitmap surface, with extra param
- // 2 params, called e.g. from MenuItem::dispose in Torin's Passage,
- // script 64893
- warning("kBitmap(2), unk1 %d, bitmap ptr %04x:%04x", argv[1].toUint16(), PRINT_REG(argv[2]));
- break;
- case 3: // tiled surface
- {
- // 6 params, called e.g. from TiledBitmap::resize() in Torin's Passage,
- // script 64869
- reg_t hunkId = argv[1]; // obtained from kBitmap(0)
- // The tiled view seems to always have 2 loops.
- // These loops need to have 1 cel in loop 0 and 8 cels in loop 1.
- uint16 viewNum = argv[2].toUint16(); // vTiles selector
- uint16 loop = argv[3].toUint16();
- uint16 cel = argv[4].toUint16();
- uint16 x = argv[5].toUint16();
- uint16 y = argv[6].toUint16();
-
- byte *memoryPtr = s->_segMan->getHunkPointer(hunkId);
- // Get totalWidth, totalHeight
- uint16 totalWidth = READ_LE_UINT16(memoryPtr);
- uint16 totalHeight = READ_LE_UINT16(memoryPtr + 2);
- byte *bitmap = memoryPtr + BITMAP_HEADER_SIZE;
-
- GfxView *view = g_sci->_gfxCache->getView(viewNum);
- uint16 tileWidth = view->getWidth(loop, cel);
- uint16 tileHeight = view->getHeight(loop, cel);
- const byte *tileBitmap = view->getBitmap(loop, cel);
- uint16 width = MIN<uint16>(totalWidth - x, tileWidth);
- uint16 height = MIN<uint16>(totalHeight - y, tileHeight);
-
- for (uint16 curY = 0; curY < height; curY++) {
- for (uint16 curX = 0; curX < width; curX++) {
- bitmap[(curY + y) * totalWidth + (curX + x)] = tileBitmap[curY * tileWidth + curX];
- }
- }
+reg_t kBitmapDrawBitmap(EngineState *s, int argc, reg_t *argv) {
+ // target bitmap, source bitmap, x, y, unknown boolean
- }
- break;
- case 4: // add text to bitmap
- {
- // 13 params, called e.g. from TextButton::createBitmap() in Torin's Passage,
- // script 64894
- reg_t hunkId = argv[1]; // obtained from kBitmap(0)
- Common::String text = s->_segMan->getString(argv[2]);
- uint16 textX = argv[3].toUint16();
- uint16 textY = argv[4].toUint16();
- //reg_t unk5 = argv[5];
- //reg_t unk6 = argv[6];
- //reg_t unk7 = argv[7]; // skip?
- //reg_t unk8 = argv[8]; // back?
- //reg_t unk9 = argv[9];
- uint16 fontId = argv[10].toUint16();
- //uint16 mode = argv[11].toUint16();
- uint16 dimmed = argv[12].toUint16();
- //warning("kBitmap(4): bitmap ptr %04x:%04x, font %d, mode %d, dimmed %d - text: \"%s\"",
- // PRINT_REG(bitmapPtr), font, mode, dimmed, text.c_str());
- uint16 foreColor = 255; // TODO
-
- byte *memoryPtr = s->_segMan->getHunkPointer(hunkId);
- // Get totalWidth, totalHeight
- uint16 totalWidth = READ_LE_UINT16(memoryPtr);
- uint16 totalHeight = READ_LE_UINT16(memoryPtr + 2);
- byte *bitmap = memoryPtr + BITMAP_HEADER_SIZE;
-
- GfxFont *font = g_sci->_gfxCache->getFont(fontId);
-
- int16 charCount = 0;
- uint16 curX = textX, curY = textY;
- const char *txt = text.c_str();
-
- while (*txt) {
- charCount = g_sci->_gfxText32->GetLongest(txt, totalWidth, font);
- if (charCount == 0)
- break;
-
- for (int i = 0; i < charCount; i++) {
- unsigned char curChar = txt[i];
- font->drawToBuffer(curChar, curY, curX, foreColor, dimmed, bitmap, totalWidth, totalHeight);
- curX += font->getCharWidth(curChar);
- }
-
- curX = textX;
- curY += font->getHeight();
- txt += charCount;
- while (*txt == ' ')
- txt++; // skip over breaking spaces
- }
+ return kStubNull(s, argc + 1, argv - 1);
+}
- }
- break;
- case 5: // fill with color
- {
- // 6 params, called e.g. from TextView::init() and TextView::draw()
- // in Torin's Passage, script 64890
- reg_t hunkId = argv[1]; // obtained from kBitmap(0)
- uint16 x = argv[2].toUint16();
- uint16 y = argv[3].toUint16();
- uint16 fillWidth = argv[4].toUint16(); // width - 1
- uint16 fillHeight = argv[5].toUint16(); // height - 1
- uint16 back = argv[6].toUint16();
-
- byte *memoryPtr = s->_segMan->getHunkPointer(hunkId);
- // Get totalWidth, totalHeight
- uint16 totalWidth = READ_LE_UINT16(memoryPtr);
- uint16 totalHeight = READ_LE_UINT16(memoryPtr + 2);
- uint16 width = MIN<uint16>(totalWidth - x, fillWidth);
- uint16 height = MIN<uint16>(totalHeight - y, fillHeight);
- byte *bitmap = memoryPtr + BITMAP_HEADER_SIZE;
-
- for (uint16 curY = 0; curY < height; curY++) {
- for (uint16 curX = 0; curX < width; curX++) {
- bitmap[(curY + y) * totalWidth + (curX + x)] = back;
- }
- }
+reg_t kBitmapInvert(EngineState *s, int argc, reg_t *argv) {
+ // bitmap, left, top, right, bottom, foreColor, backColor
- }
- break;
- default:
- kStub(s, argc, argv);
- break;
- }
+ return kStubNull(s, argc + 1, argv - 1);
+}
+reg_t kBitmapSetDisplace(EngineState *s, int argc, reg_t *argv) {
+ BitmapResource bitmap(argv[0]);
+ bitmap.setDisplace(Common::Point(argv[1].toSint16(), argv[2].toSint16()));
return s->r_acc;
}
-// Used for edit boxes in save/load dialogs. It's a rewritten version of kEditControl,
-// but it handles events on its own, using an internal loop, instead of using SCI
-// scripts for event management like kEditControl does. Called by script 64914,
-// DEdit::hilite().
-reg_t kEditText(EngineState *s, int argc, reg_t *argv) {
- reg_t controlObject = argv[0];
+reg_t kBitmapCreateFromView(EngineState *s, int argc, reg_t *argv) {
+ // viewId, loopNo, celNo, skipColor, backColor, useRemap, source overlay bitmap
- if (!controlObject.isNull()) {
- g_sci->_gfxControls32->kernelTexteditChange(controlObject);
- }
+ return kStub(s, argc + 1, argv - 1);
+}
- return s->r_acc;
+reg_t kBitmapCopyPixels(EngineState *s, int argc, reg_t *argv) {
+ // target bitmap, source bitmap
+
+ return kStubNull(s, argc + 1, argv - 1);
+}
+
+reg_t kBitmapClone(EngineState *s, int argc, reg_t *argv) {
+ // bitmap
+
+ return kStub(s, argc + 1, argv - 1);
+}
+
+reg_t kBitmapGetInfo(EngineState *s, int argc, reg_t *argv) {
+ // bitmap
+
+ // argc 1 = get width
+ // argc 2 = pixel at row 0 col n
+ // argc 3 = pixel at row n col n
+ return kStub(s, argc + 1, argv - 1);
+}
+
+reg_t kBitmapScale(EngineState *s, int argc, reg_t *argv) {
+ // TODO: SCI3
+ return kStubNull(s, argc + 1, argv - 1);
+}
+
+reg_t kBitmapCreateFromUnknown(EngineState *s, int argc, reg_t *argv) {
+ // TODO: SCI3
+ return kStub(s, argc + 1, argv - 1);
+}
+
+reg_t kEditText(EngineState *s, int argc, reg_t *argv) {
+ return g_sci->_gfxControls32->kernelEditText(argv[0]);
}
reg_t kAddLine(EngineState *s, int argc, reg_t *argv) {
+ return kStubNull(s, argc, argv); // return 0:0 for now, so that follow up calls won't create signature mismatches
+#if 0
reg_t plane = argv[0];
Common::Point startPoint(argv[1].toUint16(), argv[2].toUint16());
Common::Point endPoint(argv[3].toUint16(), argv[4].toUint16());
- // argv[5] is unknown (a number, usually 200)
+ byte priority = (byte)argv[5].toUint16();
byte color = (byte)argv[6].toUint16();
- byte priority = (byte)argv[7].toUint16();
- byte control = (byte)argv[8].toUint16();
- // argv[9] is unknown (usually a small number, 1 or 2). Thickness, perhaps?
- return g_sci->_gfxFrameout->addPlaneLine(plane, startPoint, endPoint, color, priority, control);
+ byte style = (byte)argv[7].toUint16(); // 0: solid, 1: dashed, 2: pattern
+ byte pattern = (byte)argv[8].toUint16();
+ byte thickness = (byte)argv[9].toUint16();
+// return g_sci->_gfxFrameout->addPlaneLine(plane, startPoint, endPoint, color, priority, 0);
+ return s->r_acc;
+#endif
}
reg_t kUpdateLine(EngineState *s, int argc, reg_t *argv) {
+ return kStub(s, argc, argv);
+
+#if 0
reg_t hunkId = argv[0];
reg_t plane = argv[1];
Common::Point startPoint(argv[2].toUint16(), argv[3].toUint16());
@@ -694,14 +729,18 @@ reg_t kUpdateLine(EngineState *s, int argc, reg_t *argv) {
byte priority = (byte)argv[8].toUint16();
byte control = (byte)argv[9].toUint16();
// argv[10] is unknown (usually a small number, 1 or 2). Thickness, perhaps?
- g_sci->_gfxFrameout->updatePlaneLine(plane, hunkId, startPoint, endPoint, color, priority, control);
+// g_sci->_gfxFrameout->updatePlaneLine(plane, hunkId, startPoint, endPoint, color, priority, control);
return s->r_acc;
+#endif
}
reg_t kDeleteLine(EngineState *s, int argc, reg_t *argv) {
+ return kStub(s, argc, argv);
+#if 0
reg_t hunkId = argv[0];
reg_t plane = argv[1];
- g_sci->_gfxFrameout->deletePlaneLine(plane, hunkId);
+// g_sci->_gfxFrameout->deletePlaneLine(plane, hunkId);
return s->r_acc;
+#endif
}
reg_t kSetScroll(EngineState *s, int argc, reg_t *argv) {
@@ -730,13 +769,8 @@ reg_t kSetScroll(EngineState *s, int argc, reg_t *argv) {
// Used by SQ6, script 900, the datacorder reprogramming puzzle (from room 270)
reg_t kMorphOn(EngineState *s, int argc, reg_t *argv) {
- // TODO: g_sci->_gfxManager->palMorphIsOn = true
- // This function sets the palMorphIsOn flag which causes kFrameOut to use
- // an alternative FrameOut function (GraphicsMgr::PalMorphFrameOut instead
- // of GraphicsMgr::FrameOut). At the end of the frame, kFrameOut sets the
- // palMorphIsOn flag back to false.
- kStub(s, argc, argv);
- return NULL_REG;
+ g_sci->_gfxFrameout->_palMorphIsOn = true;
+ return s->r_acc;
}
reg_t kPaletteSetFade(EngineState *s, int argc, reg_t *argv) {
@@ -744,7 +778,7 @@ reg_t kPaletteSetFade(EngineState *s, int argc, reg_t *argv) {
uint16 toColor = argv[1].toUint16();
uint16 percent = argv[2].toUint16();
g_sci->_gfxPalette32->setFade(percent, fromColor, toColor);
- return NULL_REG;
+ return s->r_acc;
}
reg_t kPalVarySetVary(EngineState *s, int argc, reg_t *argv) {
@@ -762,14 +796,14 @@ reg_t kPalVarySetVary(EngineState *s, int argc, reg_t *argv) {
}
g_sci->_gfxPalette32->kernelPalVarySet(paletteId, percent, time, fromColor, toColor);
- return NULL_REG;
+ return s->r_acc;
}
reg_t kPalVarySetPercent(EngineState *s, int argc, reg_t *argv) {
int time = argc > 0 ? argv[0].toSint16() * 60 : 0;
int16 percent = argc > 1 ? argv[1].toSint16() : 0;
g_sci->_gfxPalette32->setVaryPercent(percent, time, -1, -1);
- return NULL_REG;
+ return s->r_acc;
}
reg_t kPalVaryGetPercent(EngineState *s, int argc, reg_t *argv) {
@@ -778,7 +812,7 @@ reg_t kPalVaryGetPercent(EngineState *s, int argc, reg_t *argv) {
reg_t kPalVaryOff(EngineState *s, int argc, reg_t *argv) {
g_sci->_gfxPalette32->varyOff();
- return NULL_REG;
+ return s->r_acc;
}
reg_t kPalVaryMergeTarget(EngineState *s, int argc, reg_t *argv) {
@@ -790,7 +824,7 @@ reg_t kPalVaryMergeTarget(EngineState *s, int argc, reg_t *argv) {
reg_t kPalVarySetTime(EngineState *s, int argc, reg_t *argv) {
int time = argv[0].toSint16() * 60;
g_sci->_gfxPalette32->setVaryTime(time);
- return NULL_REG;
+ return s->r_acc;
}
reg_t kPalVarySetTarget(EngineState *s, int argc, reg_t *argv) {
@@ -875,76 +909,57 @@ 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();
+reg_t kRemapColors(EngineState *s, int argc, reg_t *argv) {
+ if (!s)
+ return make_reg(0, getSciVersion());
+ error("not supposed to call this");
+}
- 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;
+reg_t kRemapOff(EngineState *s, int argc, reg_t *argv) {
+ byte color = (argc >= 1) ? argv[0].toUint16() : 0;
+ g_sci->_gfxRemap32->remapOff(color);
+ 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->_gfxPalette32->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->_gfxPalette32->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->_gfxPalette32->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->_gfxPalette32->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->_gfxPalette32->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->_gfxPalette32->kernelSetIntensity(0, 255, intensity, true);
+reg_t kRemapByRange(EngineState *s, int argc, reg_t *argv) {
+ byte color = argv[0].toUint16();
+ byte from = argv[1].toUint16();
+ byte to = argv[2].toUint16();
+ byte base = argv[3].toUint16();
+ // The last parameter, depth, is unused
+ g_sci->_gfxRemap32->setRemappingRange(color, from, to, base);
+ return s->r_acc;
+}
- kStub(s, argc, argv);
- }
- break;
- default:
- break;
- }
+reg_t kRemapByPercent(EngineState *s, int argc, reg_t *argv) {
+ byte color = argv[0].toUint16();
+ byte percent = argv[1].toUint16();
+ // The last parameter, depth, is unused
+ g_sci->_gfxRemap32->setRemappingPercent(color, percent);
+ return s->r_acc;
+}
+
+reg_t kRemapToGray(EngineState *s, int argc, reg_t *argv) {
+ byte color = argv[0].toUint16();
+ byte gray = argv[1].toUint16();
+ // The last parameter, depth, is unused
+ g_sci->_gfxRemap32->setRemappingToGray(color, gray);
+ return s->r_acc;
+}
+
+reg_t kRemapToPercentGray(EngineState *s, int argc, reg_t *argv) {
+ byte color = argv[0].toUint16();
+ byte gray = argv[1].toUint16();
+ byte percent = argv[2].toUint16();
+ // The last parameter, depth, is unused
+ g_sci->_gfxRemap32->setRemappingToPercentGray(color, gray, percent);
+ return s->r_acc;
+}
+reg_t kRemapSetNoMatchRange(EngineState *s, int argc, reg_t *argv) {
+ byte from = argv[0].toUint16();
+ byte count = argv[1].toUint16();
+ g_sci->_gfxRemap32->setNoMatchRange(from, count);
return s->r_acc;
}
diff --git a/engines/sci/engine/kmisc.cpp b/engines/sci/engine/kmisc.cpp
index 9d47a37bca..f4bb4ff85b 100644
--- a/engines/sci/engine/kmisc.cpp
+++ b/engines/sci/engine/kmisc.cpp
@@ -268,7 +268,10 @@ reg_t kMemory(EngineState *s, int argc, reg_t *argv) {
switch (argv[0].toUint16()) {
case K_MEMORY_ALLOCATE_CRITICAL: {
int byteCount = argv[1].toUint16();
- // WORKAROUND:
+ // Sierra themselves allocated at least 2 bytes more than requested.
+ // Probably as a safety margin. And they also made size even.
+ //
+ // This behavior is required by at least these:
// - pq3 (multilingual) room 202
// when plotting crimes, allocates the returned bytes from kStrLen
// on "W" and "E" and wants to put a string in there, which doesn't
@@ -276,18 +279,22 @@ reg_t kMemory(EngineState *s, int argc, reg_t *argv) {
// - lsl5 (multilingual) room 280
// allocates memory according to a previous kStrLen for the name of
// the airport ladies (bug #3093818), which isn't enough
-
- // We always allocate 1 byte more, because of this
- byteCount++;
+ byteCount += 2 + (byteCount & 1);
if (!s->_segMan->allocDynmem(byteCount, "kMemory() critical", &s->r_acc)) {
error("Critical heap allocation failed");
}
break;
}
- case K_MEMORY_ALLOCATE_NONCRITICAL:
- s->_segMan->allocDynmem(argv[1].toUint16(), "kMemory() non-critical", &s->r_acc);
+ case K_MEMORY_ALLOCATE_NONCRITICAL: {
+ int byteCount = argv[1].toUint16();
+
+ // See above
+ byteCount += 2 + (byteCount & 1);
+
+ s->_segMan->allocDynmem(byteCount, "kMemory() non-critical", &s->r_acc);
break;
+ }
case K_MEMORY_FREE :
if (!s->_segMan->freeDynmem(argv[1])) {
if (g_sci->getGameId() == GID_QFG1VGA) {
diff --git a/engines/sci/engine/kpathing.cpp b/engines/sci/engine/kpathing.cpp
index 5b2245e84d..7ac744f584 100644
--- a/engines/sci/engine/kpathing.cpp
+++ b/engines/sci/engine/kpathing.cpp
@@ -1943,14 +1943,14 @@ static int liesBefore(const Vertex *v, const Common::Point &p1, const Common::Po
// 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;
+ uint32 indexw1;
+ uint32 indexp1;
const Vertex *vertexw1;
const Vertex *vertexp1;
Common::Point intersection1;
- unsigned int indexw2;
- unsigned int indexp2;
+ uint32 indexw2;
+ uint32 indexp2;
const Vertex *vertexw2;
const Vertex *vertexp2;
Common::Point intersection2;
@@ -1960,7 +1960,7 @@ struct Patch {
// Check if the given vertex on the work polygon is bypassed by this patch.
-static bool isVertexCovered(const Patch &p, unsigned int wi) {
+static bool isVertexCovered(const Patch &p, uint32 wi) {
// / v (outside)
// ---w1--1----p----w2--2----
@@ -2402,7 +2402,7 @@ reg_t kMergePoly(EngineState *s, int argc, reg_t *argv) {
// Copy work.vertices into arrayRef
Vertex *vertex;
- unsigned int n = 0;
+ uint32 n = 0;
CLIST_FOREACH(vertex, &work.vertices) {
if (vertex == work.vertices._head || vertex->v != vertex->_prev->v)
writePoint(arrayRef, n++, vertex->v);
diff --git a/engines/sci/engine/ksound.cpp b/engines/sci/engine/ksound.cpp
index 032191e4c1..398a623286 100644
--- a/engines/sci/engine/ksound.cpp
+++ b/engines/sci/engine/ksound.cpp
@@ -206,8 +206,15 @@ reg_t kDoAudio(EngineState *s, int argc, reg_t *argv) {
// athrxx: It seems from disasm that the original KQ5 FM-Towns loads a default language (Japanese) audio map at the beginning
// right after loading the video and audio drivers. The -1 language argument in here simply means that the original will stick
// with Japanese. Instead of doing that we switch to the language selected in the launcher.
- if (g_sci->getPlatform() == Common::kPlatformFMTowns && language == -1)
- language = (g_sci->getLanguage() == Common::JA_JPN) ? K_LANG_JAPANESE : K_LANG_ENGLISH;
+ if (g_sci->getPlatform() == Common::kPlatformFMTowns && language == -1) {
+ // FM-Towns calls us to get the current language / also set the default language
+ // This doesn't just happen right at the start, but also when the user clicks on the Sierra logo in the game menu
+ // It uses the result of this call to either show "English Voices" or "Japanese Voices".
+
+ // Language should have been set by setLauncherLanguage() already (or could have been modified by the scripts).
+ // Get this language setting, so that the chosen language will get set for resource manager.
+ language = g_sci->getSciLanguage();
+ }
debugC(kDebugLevelSound, "kDoAudio: set language to %d", language);
@@ -233,19 +240,38 @@ reg_t kDoAudio(EngineState *s, int argc, reg_t *argv) {
#endif
}
- // 3 new subops in Pharkas. kDoAudio in Pharkas sits at seg026:038C
+ // 3 new subops in Pharkas CD (including CD demo). kDoAudio in Pharkas sits at seg026:038C
case 11:
// Not sure where this is used yet
warning("kDoAudio: Unhandled case 11, %d extra arguments passed", argc - 1);
break;
case 12:
- // Seems to be some sort of audio sync, used in Pharkas. Silenced the
- // warning due to the high level of spam it produces. (takes no params)
- //warning("kDoAudio: Unhandled case 12, %d extra arguments passed", argc - 1);
+ // SSCI calls this function with no parameters from
+ // the TalkRandCycle class and branches on the return
+ // value like a boolean. The conjectured purpose of
+ // this function is to ensure that the talker's mouth
+ // does not move if there is read jitter (slow CD
+ // drive, scratched CD). The old behavior here of not
+ // doing anything caused a nonzero value to be left in
+ // the accumulator by chance. This is equivalent, but
+ // more explicit.
+
+ return make_reg(0, 1);
break;
case 13:
- // Used in Pharkas whenever a speech sample starts (takes no params)
- //warning("kDoAudio: Unhandled case 13, %d extra arguments passed", argc - 1);
+ // SSCI returns a serial number for the played audio
+ // here, used in the PointsSound class. The reason is severalfold:
+
+ // 1. SSCI does not support multiple wave effects at once
+ // 2. FPFP may disable its icon bar during the points sound.
+ // 3. Each new sound preempts any sound already playing.
+ // 4. If the points sound is interrupted before completion,
+ // the icon bar could remain disabled.
+
+ // Since points (1) and (3) do not apply to us, we can simply
+ // return a constant here. This is equivalent to the
+ // old behavior, as above.
+ return make_reg(0, 1);
break;
case 17:
// Seems to be some sort of audio sync, used in SQ6. Silenced the
diff --git a/engines/sci/engine/kstring.cpp b/engines/sci/engine/kstring.cpp
index 310e38dbd1..1c08bf597c 100644
--- a/engines/sci/engine/kstring.cpp
+++ b/engines/sci/engine/kstring.cpp
@@ -661,19 +661,6 @@ reg_t kStrSplit(EngineState *s, int argc, reg_t *argv) {
#ifdef ENABLE_SCI32
-reg_t kText(EngineState *s, int argc, reg_t *argv) {
- switch (argv[0].toUint16()) {
- case 0:
- return kTextSize(s, argc - 1, argv + 1);
- default:
- // TODO: Other subops here too, perhaps kTextColors and kTextFonts
- warning("kText(%d)", argv[0].toUint16());
- break;
- }
-
- return s->r_acc;
-}
-
// TODO: there is an unused second argument, happens at least in LSL6 right during the intro
reg_t kStringNew(EngineState *s, int argc, reg_t *argv) {
reg_t stringHandle;
@@ -778,11 +765,14 @@ reg_t kStringCopy(EngineState *s, int argc, reg_t *argv) {
}
// The original engine ignores bad copies too
- if (index2 > string2Size)
+ if (index2 >= string2Size)
return NULL_REG;
// A count of -1 means fill the rest of the array
- uint32 count = argv[4].toSint16() == -1 ? string2Size - index2 + 1 : argv[4].toUint16();
+ uint32 count = string2Size - index2;
+ if (argv[4].toSint16() != -1) {
+ count = MIN(count, (uint32)argv[4].toUint16());
+ }
// reg_t strAddress = argv[0];
SciString *string1 = s->_segMan->lookupString(argv[0]);
diff --git a/engines/sci/engine/object.cpp b/engines/sci/engine/object.cpp
index 0626c084c1..0566d6955f 100644
--- a/engines/sci/engine/object.cpp
+++ b/engines/sci/engine/object.cpp
@@ -255,6 +255,8 @@ void Object::initSelectorsSci3(const byte *buf) {
if (g_sci->getKernel()->getSelectorNamesSize() % 32)
++groups;
+ _mustSetViewVisible.resize(groups);
+
methods = properties = 0;
// Selectors are divided into groups of 32, of which the first
@@ -270,7 +272,9 @@ void Object::initSelectorsSci3(const byte *buf) {
// This object actually has selectors belonging to this group
int typeMask = READ_SCI11ENDIAN_UINT32(seeker);
- for (int bit = 2; bit < 32; ++bit) {
+ _mustSetViewVisible[groupNr] = (typeMask & 1);
+
+ for (int bit = 2; bit < 32; ++bit) {
int value = READ_SCI11ENDIAN_UINT16(seeker + bit * 2);
if (typeMask & (1 << bit)) { // Property
++properties;
@@ -281,7 +285,8 @@ void Object::initSelectorsSci3(const byte *buf) {
}
}
- }
+ } else
+ _mustSetViewVisible[groupNr] = false;
}
_variables.resize(properties);
diff --git a/engines/sci/engine/object.h b/engines/sci/engine/object.h
index 0ae7ed2cab..a7be170f4f 100644
--- a/engines/sci/engine/object.h
+++ b/engines/sci/engine/object.h
@@ -41,8 +41,21 @@ enum {
};
enum infoSelectorFlags {
- kInfoFlagClone = 0x0001,
- kInfoFlagClass = 0x8000
+ kInfoFlagClone = 0x0001,
+#ifdef ENABLE_SCI32
+ /**
+ * When set, indicates to game scripts that a screen
+ * item can be updated.
+ */
+ kInfoFlagViewVisible = 0x0008, // TODO: "dirty" ?
+
+ /**
+ * When set, the object has an associated screen item in
+ * the rendering tree.
+ */
+ kInfoFlagViewInserted = 0x0010,
+#endif
+ kInfoFlagClass = 0x8000
};
enum ObjectOffsets {
@@ -120,7 +133,24 @@ public:
_infoSelectorSci3 = info;
}
- // No setter for the -info- selector
+#ifdef ENABLE_SCI32
+ void setInfoSelectorFlag(infoSelectorFlags flag) {
+ if (getSciVersion() < SCI_VERSION_3) {
+ _variables[_offset + 2] |= flag;
+ } else {
+ _infoSelectorSci3 |= flag;
+ }
+ }
+
+ // NOTE: In real engine, -info- is treated as byte size
+ void clearInfoSelectorFlag(infoSelectorFlags flag) {
+ if (getSciVersion() < SCI_VERSION_3) {
+ _variables[_offset + 2] &= ~flag;
+ } else {
+ _infoSelectorSci3 &= ~flag;
+ }
+ }
+#endif
reg_t getNameSelector() const {
if (getSciVersion() < SCI_VERSION_3)
@@ -232,6 +262,8 @@ public:
bool initBaseObject(SegManager *segMan, reg_t addr, bool doInitSuperClass = true);
void syncBaseObject(const byte *ptr) { _baseObj = ptr; }
+ bool mustSetViewVisibleSci3(int selector) const { return _mustSetViewVisible[selector/32]; }
+
private:
void initSelectorsSci3(const byte *buf);
@@ -248,6 +280,7 @@ private:
reg_t _superClassPosSci3; /**< reg_t pointing to superclass for SCI3 */
reg_t _speciesSelectorSci3; /**< reg_t containing species "selector" for SCI3 */
reg_t _infoSelectorSci3; /**< reg_t containing info "selector" for SCI3 */
+ Common::Array<bool> _mustSetViewVisible; /** cached bit of info to make lookup fast, SCI3 only */
};
diff --git a/engines/sci/engine/savegame.cpp b/engines/sci/engine/savegame.cpp
index 165c147c1c..fcb65157d8 100644
--- a/engines/sci/engine/savegame.cpp
+++ b/engines/sci/engine/savegame.cpp
@@ -39,6 +39,7 @@
#include "sci/engine/vm_types.h"
#include "sci/engine/script.h" // for SCI_OBJ_EXPORTS and SCI_OBJ_SYNONYMS
#include "sci/graphics/helpers.h"
+#include "sci/graphics/menu.h"
#include "sci/graphics/palette.h"
#include "sci/graphics/ports.h"
#include "sci/graphics/screen.h"
@@ -957,7 +958,8 @@ bool gamestate_save(EngineState *s, Common::WriteStream *fh, const Common::Strin
extern void showScummVMDialog(const Common::String &message);
void gamestate_delayedrestore(EngineState *s) {
- Common::String fileName = g_sci->getSavegameName(s->_delayedRestoreGameId);
+ int savegameId = s->_delayedRestoreGameId; // delayedRestoreGameId gets destroyed within gamestate_restore()!
+ Common::String fileName = g_sci->getSavegameName(savegameId);
Common::SeekableReadStream *in = g_sci->getSaveFileManager()->openForLoading(fileName);
if (in) {
@@ -965,6 +967,7 @@ void gamestate_delayedrestore(EngineState *s) {
gamestate_restore(s, in);
delete in;
if (s->r_acc != make_reg(0, 1)) {
+ gamestate_afterRestoreFixUp(s, savegameId);
return;
}
}
@@ -972,6 +975,73 @@ void gamestate_delayedrestore(EngineState *s) {
error("Restoring gamestate '%s' failed", fileName.c_str());
}
+void gamestate_afterRestoreFixUp(EngineState *s, int savegameId) {
+ switch (g_sci->getGameId()) {
+ case GID_MOTHERGOOSE:
+ // WORKAROUND: Mother Goose SCI0
+ // Script 200 / rm200::newRoom will set global C5h directly right after creating a child to the
+ // current number of children plus 1.
+ // We can't trust that global, that's why we set the actual savedgame id right here directly after
+ // restoring a saved game.
+ // If we didn't, the game would always save to a new slot
+ s->variables[VAR_GLOBAL][0xC5].setOffset(SAVEGAMEID_OFFICIALRANGE_START + savegameId);
+ break;
+ case GID_MOTHERGOOSE256:
+ // WORKAROUND: Mother Goose SCI1/SCI1.1 does some weird things for
+ // saving a previously restored game.
+ // We set the current savedgame-id directly and remove the script
+ // code concerning this via script patch.
+ s->variables[VAR_GLOBAL][0xB3].setOffset(SAVEGAMEID_OFFICIALRANGE_START + savegameId);
+ break;
+ case GID_JONES:
+ // HACK: The code that enables certain menu items isn't called when a game is restored from the
+ // launcher, or the "Restore game" option in the game's main menu - bugs #6537 and #6723.
+ // These menu entries are disabled when the game is launched, and are enabled when a new game is
+ // started. The code for enabling these entries is is all in script 1, room1::init, but that code
+ // path is never followed in these two cases (restoring game from the menu, or restoring a game
+ // from the ScummVM launcher). Thus, we perform the calls to enable the menus ourselves here.
+ // These two are needed when restoring from the launcher
+ // FIXME: The original interpreter saves and restores the menu state, so these attributes
+ // are automatically reset there. We may want to do the same.
+ g_sci->_gfxMenu->kernelSetAttribute(257 >> 8, 257 & 0xFF, SCI_MENU_ATTRIBUTE_ENABLED, TRUE_REG); // Sierra -> About Jones
+ g_sci->_gfxMenu->kernelSetAttribute(258 >> 8, 258 & 0xFF, SCI_MENU_ATTRIBUTE_ENABLED, TRUE_REG); // Sierra -> Help
+ // The rest are normally enabled from room1::init
+ g_sci->_gfxMenu->kernelSetAttribute(769 >> 8, 769 & 0xFF, SCI_MENU_ATTRIBUTE_ENABLED, TRUE_REG); // Options -> Delete current player
+ g_sci->_gfxMenu->kernelSetAttribute(513 >> 8, 513 & 0xFF, SCI_MENU_ATTRIBUTE_ENABLED, TRUE_REG); // Game -> Save Game
+ g_sci->_gfxMenu->kernelSetAttribute(515 >> 8, 515 & 0xFF, SCI_MENU_ATTRIBUTE_ENABLED, TRUE_REG); // Game -> Restore Game
+ g_sci->_gfxMenu->kernelSetAttribute(1025 >> 8, 1025 & 0xFF, SCI_MENU_ATTRIBUTE_ENABLED, TRUE_REG); // Status -> Statistics
+ g_sci->_gfxMenu->kernelSetAttribute(1026 >> 8, 1026 & 0xFF, SCI_MENU_ATTRIBUTE_ENABLED, TRUE_REG); // Status -> Goals
+ break;
+ case GID_KQ6:
+ if (g_sci->isCD()) {
+ // WORKAROUND:
+ // For the CD version of King's Quest 6, set global depending on current hires/lowres state
+ // The game sets a global at the start depending on it and some things check that global
+ // instead of checking platform like for example the game action menu.
+ // This never happened in the original interpreter, because the original DOS interpreter
+ // was only capable of lowres graphics and the original Windows 3.11 interpreter was only capable
+ // of hires graphics. Saved games were not compatible between those two.
+ // Which means saving during lowres mode, then going into hires mode and restoring that saved game,
+ // will result in some graphics being incorrect (lowres).
+ // That's why we are setting the global after restoring a saved game depending on hires/lowres state.
+ // The CD demo of KQ6 does the same and uses the exact same global.
+ if ((g_sci->getPlatform() == Common::kPlatformWindows) || (g_sci->forceHiresGraphics())) {
+ s->variables[VAR_GLOBAL][0xA9].setOffset(1);
+ } else {
+ s->variables[VAR_GLOBAL][0xA9].setOffset(0);
+ }
+ }
+ break;
+ case GID_PQ2:
+ // HACK: Same as above - enable the save game menu option when loading in PQ2 (bug #6875).
+ // It gets disabled in the game's death screen.
+ g_sci->_gfxMenu->kernelSetAttribute(2, 1, SCI_MENU_ATTRIBUTE_ENABLED, TRUE_REG); // Game -> Save Game
+ break;
+ default:
+ break;
+ }
+}
+
void gamestate_restore(EngineState *s, Common::SeekableReadStream *fh) {
SavegameMetadata meta;
@@ -1013,13 +1083,31 @@ void gamestate_restore(EngineState *s, Common::SeekableReadStream *fh) {
if (g_sci->_gfxPorts)
g_sci->_gfxPorts->reset();
// clear screen
- if (g_sci->_gfxScreen)
- g_sci->_gfxScreen->clearForRestoreGame();
+ if (getSciVersion() <= SCI_VERSION_1_1) {
+ // Only do clearing the screen for SCI16
+ // Both SCI16 + SCI32 did not clear the screen.
+ // We basically do it for SCI16, because of KQ6.
+ // When hires portraits are shown and the user restores during that time, the portraits
+ // wouldn't get fully removed. In original SCI, the user wasn't able to restore during that time,
+ // so this is basically a workaround, so that ScummVM features work properly.
+ // For SCI32, behavior was verified in DOSBox, that SCI32 does not clear and also not redraw the screen.
+ // It only redraws elements that have changed in comparison to the state before the restore.
+ // If we cleared the screen for SCI32, we would have issues because of this behavior.
+ if (g_sci->_gfxScreen)
+ g_sci->_gfxScreen->clearForRestoreGame();
+ }
#ifdef ENABLE_SCI32
- // Also clear any SCI32 planes/screen items currently showing so they
- // don't show up after the load.
- if (getSciVersion() >= SCI_VERSION_2)
- g_sci->_gfxFrameout->clear();
+ // Delete current planes/elements of actively loaded VM, only when our ScummVM dialogs are patched in
+ // We MUST NOT delete all planes/screen items. At least Space Quest 6 has a few in memory like for example
+ // the options plane, which are not re-added and are in memory all the time right from the start of the
+ // game. Sierra SCI32 did not clear planes, only scripts cleared the ones inside planes::elements.
+ if (getSciVersion() >= SCI_VERSION_2) {
+ if (!s->_delayedRestoreFromLauncher) {
+ // Only do it, when we are restoring regulary and not from launcher
+ // As it could result in option planes etc. on the screen (happens in gk1)
+ g_sci->_gfxFrameout->syncWithScripts(false);
+ }
+ }
#endif
s->reset(true);
@@ -1044,6 +1132,13 @@ void gamestate_restore(EngineState *s, Common::SeekableReadStream *fh) {
if (g_sci->_gfxPorts)
g_sci->_gfxPorts->saveLoadWithSerializer(ser);
+ // SCI32:
+ // Current planes/screen elements of freshly loaded VM are re-added by scripts in [gameID]::replay
+ // We don't have to do that in here.
+ // But we may have to do it ourselves in case we ever implement some soft-error handling in case
+ // a saved game can't be restored. That way we can restore the game screen.
+ // see _gfxFrameout->syncWithScripts()
+
Vocabulary *voc = g_sci->getVocabulary();
if (ser.getVersion() >= 30 && voc)
voc->saveLoadWithSerializer(ser);
@@ -1061,6 +1156,8 @@ void gamestate_restore(EngineState *s, Common::SeekableReadStream *fh) {
// signal restored game to game scripts
s->gameIsRestarting = GAMEISRESTARTING_RESTORE;
+
+ s->_delayedRestoreFromLauncher = false;
}
bool get_savegame_metadata(Common::SeekableReadStream *stream, SavegameMetadata *meta) {
diff --git a/engines/sci/engine/savegame.h b/engines/sci/engine/savegame.h
index bb555434c9..459e992e24 100644
--- a/engines/sci/engine/savegame.h
+++ b/engines/sci/engine/savegame.h
@@ -87,6 +87,9 @@ bool gamestate_save(EngineState *s, Common::WriteStream *save, const Common::Str
// does a delayed saved game restore, used by ScummVM game menu - see detection.cpp / SciEngine::loadGameState()
void gamestate_delayedrestore(EngineState *s);
+// does a few fixups right after restoring a saved game
+void gamestate_afterRestoreFixUp(EngineState *s, int savegameId);
+
/**
* Restores a game state from a directory.
* @param s An older state from the same game
diff --git a/engines/sci/engine/script_patches.cpp b/engines/sci/engine/script_patches.cpp
index 6915e12a0e..8039c5f282 100644
--- a/engines/sci/engine/script_patches.cpp
+++ b/engines/sci/engine/script_patches.cpp
@@ -101,6 +101,8 @@ static const char *const selectorNameTable[] = {
"startText", // King's Quest 6 CD / Laura Bow 2 CD for audio+text support
"startAudio", // King's Quest 6 CD / Laura Bow 2 CD for audio+text support
"modNum", // King's Quest 6 CD / Laura Bow 2 CD for audio+text support
+ "cycler", // Space Quest 4 / system selector
+ "setLoop", // Laura Bow 1 Colonel's Bequest
NULL
};
@@ -127,7 +129,9 @@ enum ScriptPatcherSelectors {
SELECTOR_timesShownID,
SELECTOR_startText,
SELECTOR_startAudio,
- SELECTOR_modNum
+ SELECTOR_modNum,
+ SELECTOR_cycler,
+ SELECTOR_setLoop
};
// ===========================================================================
@@ -404,6 +408,59 @@ static const SciScriptPatcherEntry fanmadeSignatures[] = {
};
// ===========================================================================
+
+// WORKAROUND
+// Freddy Pharkas intro screen
+// Sierra used inner loops for the scaling of the 2 title views.
+// Those inner loops don't call kGameIsRestarting, which is why
+// we do not update the screen and we also do not throttle.
+//
+// This patch fixes this and makes it work.
+// Applies to at least: English PC-CD
+// Responsible method: sTownScript::changeState(1), sTownScript::changeState(3) (script 110)
+static const uint16 freddypharkasSignatureIntroScaling[] = {
+ 0x38, SIG_ADDTOOFFSET(+2), // pushi (setLoop) (009b for PC CD)
+ 0x78, // push1
+ PATCH_ADDTOOFFSET(1), // push0 for first code, push1 for second code
+ 0x38, SIG_ADDTOOFFSET(+2), // pushi (setStep) (0143 for PC CD)
+ 0x7a, // push2
+ 0x39, 0x05, // pushi 05
+ 0x3c, // dup
+ 0x72, SIG_ADDTOOFFSET(+2), // lofsa (view)
+ SIG_MAGICDWORD,
+ 0x4a, 0x1e, // send 1e
+ 0x35, 0x0a, // ldi 0a
+ 0xa3, 0x02, // sal local[2]
+ // start of inner loop
+ 0x8b, 0x02, // lsl local[2]
+ SIG_ADDTOOFFSET(+43), // skip almost all of inner loop
+ 0xa3, 0x02, // sal local[2]
+ 0x33, 0xcf, // jmp [inner loop start]
+ SIG_END
+};
+
+static const uint16 freddypharkasPatchIntroScaling[] = {
+ // remove setLoop(), objects in heap are already prepared, saves 5 bytes
+ 0x38,
+ PATCH_GETORIGINALBYTE(+6),
+ PATCH_GETORIGINALBYTE(+7), // pushi (setStep)
+ 0x7a, // push2
+ 0x39, 0x05, // pushi 05
+ 0x3c, // dup
+ 0x72,
+ PATCH_GETORIGINALBYTE(+13),
+ PATCH_GETORIGINALBYTE(+14), // lofsa (view)
+ 0x4a, 0x18, // send 18 - adjusted
+ 0x35, 0x0a, // ldi 0a
+ 0xa3, 0x02, // sal local[2]
+ // start of new inner loop
+ 0x39, 0x00, // pushi 00
+ 0x43, 0x2c, 0x00, // callk GameIsRestarting <-- add this so that our speed throttler is triggered
+ SIG_ADDTOOFFSET(+47), // skip almost all of inner loop
+ 0x33, 0xca, // jmp [inner loop start]
+ PATCH_END
+};
+
// script 0 of freddy pharkas/CD PointsSound::check waits for a signal and if
// no signal received will call kDoSound(0xD) which is a dummy in sierra sci
// and ScummVM and will use acc (which is not set by the dummy) to trigger
@@ -522,6 +579,7 @@ static const uint16 freddypharkasPatchMacInventory[] = {
static const SciScriptPatcherEntry freddypharkasSignatures[] = {
{ true, 0, "CD: score early disposal", 1, freddypharkasSignatureScoreDisposal, freddypharkasPatchScoreDisposal },
{ true, 15, "Mac: broken inventory", 1, freddypharkasSignatureMacInventory, freddypharkasPatchMacInventory },
+ { true, 110, "intro scaling workaround", 2, freddypharkasSignatureIntroScaling, freddypharkasPatchIntroScaling },
{ true, 235, "CD: canister pickup hang", 3, freddypharkasSignatureCanisterHang, freddypharkasPatchCanisterHang },
{ true, 320, "ladder event issue", 2, freddypharkasSignatureLadderEvent, freddypharkasPatchLadderEvent },
SCI_SIGNATUREENTRY_TERMINATOR
@@ -755,6 +813,32 @@ static const uint16 kq5PatchWitchCageInit[] = {
PATCH_END
};
+// The multilingual releases of KQ5 hang right at the end during the magic battle with Mordack.
+// It seems additional code was added to wait for signals, but the signals are never set and thus
+// the game hangs. We disable that code, so that the battle works again.
+// This also happened in the original interpreter.
+// We must not change similar code, that happens before.
+
+// Applies to at least: French PC floppy, German PC floppy, Spanish PC floppy
+// Responsible method: stingScript::changeState, dragonScript::changeState, snakeScript::changeState
+static const uint16 kq5SignatureMultilingualEndingGlitch[] = {
+ SIG_MAGICDWORD,
+ 0x89, 0x57, // lsg global[57h]
+ 0x35, 0x00, // ldi 0
+ 0x1a, // eq?
+ 0x18, // not
+ 0x30, SIG_UINT16(0x0011), // bnt [skip signal check]
+ SIG_ADDTOOFFSET(+8), // skip globalSound::prevSignal get code
+ 0x36, // push
+ 0x35, 0x0a, // ldi 0Ah
+ SIG_END
+};
+
+static const uint16 kq5PatchMultilingualEndingGlitch[] = {
+ PATCH_ADDTOOFFSET(+6),
+ 0x32, // change BNT into JMP
+ PATCH_END
+};
// In the final battle, the DOS version uses signals in the music to handle
// timing, while in the Windows version another method is used and the GM
@@ -785,9 +869,10 @@ static const uint16 kq5PatchWinGMSignals[] = {
// script, description, signature patch
static const SciScriptPatcherEntry kq5Signatures[] = {
- { true, 0, "CD: harpy volume change", 1, kq5SignatureCdHarpyVolume, kq5PatchCdHarpyVolume },
- { true, 200, "CD: witch cage init", 1, kq5SignatureWitchCageInit, kq5PatchWitchCageInit },
- { false, 124, "Win: GM Music signal checks", 4, kq5SignatureWinGMSignals, kq5PatchWinGMSignals },
+ { true, 0, "CD: harpy volume change", 1, kq5SignatureCdHarpyVolume, kq5PatchCdHarpyVolume },
+ { true, 200, "CD: witch cage init", 1, kq5SignatureWitchCageInit, kq5PatchWitchCageInit },
+ { true, 124, "Multilingual: Ending glitching out", 3, kq5SignatureMultilingualEndingGlitch, kq5PatchMultilingualEndingGlitch },
+ { false, 124, "Win: GM Music signal checks", 4, kq5SignatureWinGMSignals, kq5PatchWinGMSignals },
SCI_SIGNATUREENTRY_TERMINATOR
};
@@ -1201,8 +1286,10 @@ static const uint16 kq6CDPatchAudioTextSupportJumpAlways[] = {
};
// Fixes "Girl In The Tower" to get played in dual mode as well
+// Also changes credits to use CD audio for dual mode.
+//
// Applies to at least: PC-CD
-// Patched method: rm740::cue
+// Patched method: rm740::cue (script 740), sCredits::init (script 52)
static const uint16 kq6CDSignatureAudioTextSupportGirlInTheTower[] = {
SIG_MAGICDWORD,
0x89, 0x5a, // lsg global[5a]
@@ -1329,6 +1416,7 @@ static const SciScriptPatcherEntry kq6Signatures[] = {
{ false, 928, "CD: audio + text support KQ6 4", 1, kq6CDSignatureAudioTextSupport4, kq6CDPatchAudioTextSupport4 },
{ false, 1009, "CD: audio + text support KQ6 Guards", 2, kq6CDSignatureAudioTextSupportGuards, kq6CDPatchAudioTextSupportGuards },
{ false, 1027, "CD: audio + text support KQ6 Stepmother", 1, kq6CDSignatureAudioTextSupportStepmother, kq6CDPatchAudioTextSupportJumpAlways },
+ { false, 52, "CD: audio + text support KQ6 Girl In The Tower", 1, kq6CDSignatureAudioTextSupportGirlInTheTower, kq6CDPatchAudioTextSupportGirlInTheTower },
{ false, 740, "CD: audio + text support KQ6 Girl In The Tower", 1, kq6CDSignatureAudioTextSupportGirlInTheTower, kq6CDPatchAudioTextSupportGirlInTheTower },
{ false, 370, "CD: audio + text support KQ6 Azure & Ariel", 6, kq6CDSignatureAudioTextSupportAzureAriel, kq6CDPatchAudioTextSupportAzureAriel },
{ false, 903, "CD: audio + text support KQ6 menu", 1, kq6CDSignatureAudioTextMenuSupport, kq6CDPatchAudioTextMenuSupport },
@@ -1536,6 +1624,43 @@ static const SciScriptPatcherEntry larry6Signatures[] = {
};
// ===========================================================================
+// Laura Bow 1 - Colonel's Bequest
+//
+// This is basically just a broken easter egg in Colonel's Bequest.
+// A plane can show up in room 4, but that only happens really rarely.
+// Anyway the Sierra developer seems to have just entered the wrong loop,
+// which is why the statue view is used instead (loop 0).
+// We fix it to use the correct loop.
+//
+// This is only broken in the PC version. It was fixed for Amiga + Atari ST.
+//
+// Credits to OmerMor, for finding it.
+
+// Applies to at least: English PC Floppy
+// Responsible method: room4::init
+static const uint16 laurabow1SignatureEasterEggViewFix[] = {
+ 0x78, // push1
+ 0x76, // push0
+ SIG_MAGICDWORD,
+ 0x38, SIG_SELECTOR16(setLoop), // pushi "setLoop"
+ 0x78, // push1
+ 0x39, 0x03, // pushi 3 (loop 3, view only has 3 loops)
+ SIG_END
+};
+
+static const uint16 laurabow1PatchEasterEggViewFix[] = {
+ PATCH_ADDTOOFFSET(+7),
+ 0x02, // change loop to 2
+ PATCH_END
+};
+
+// script, description, signature patch
+static const SciScriptPatcherEntry laurabow1Signatures[] = {
+ { true, 4, "easter egg view fix", 1, laurabow1SignatureEasterEggViewFix, laurabow1PatchEasterEggViewFix },
+ SCI_SIGNATUREENTRY_TERMINATOR
+};
+
+// ===========================================================================
// Laura Bow 2
//
// Moving away the painting in the room with the hidden safe is problematic
@@ -1840,15 +1965,103 @@ static const SciScriptPatcherEntry laurabow2Signatures[] = {
// MG::replay somewhat calculates the savedgame-id used when saving again
// this doesn't work right and we remove the code completely.
// We set the savedgame-id directly right after restoring in kRestoreGame.
+// We also draw the background picture in here instead.
+// This Mixed Up Mother Goose draws the background picture before restoring,
+// instead of doing it properly in MG::replay. This fixes graphic issues,
+// when restoring from GMM.
+//
+// Applies to at least: English SCI1 CD, English SCI1.1 floppy, Japanese FM-Towns
+// Responsible method: MG::replay (script 0)
static const uint16 mothergoose256SignatureReplay[] = {
+ 0x7a, // push2
+ 0x78, // push1
+ 0x5b, 0x00, 0xbe, // lea global[BEh]
+ 0x36, // push
+ 0x43, 0x70, 0x04, // callk MemorySegment
+ 0x7a, // push2
+ 0x5b, 0x00, 0xbe, // lea global[BEh]
+ 0x36, // push
+ 0x76, // push0
+ 0x43, 0x62, 0x04, // callk StrAt
+ 0xa1, 0xaa, // sag global[AAh]
+ 0x7a, // push2
+ 0x5b, 0x00, 0xbe, // lea global[BEh]
+ 0x36, // push
+ 0x78, // push1
+ 0x43, 0x62, 0x04, // callk StrAt
+ 0x36, // push
+ 0x35, 0x20, // ldi 20
+ 0x04, // sub
+ 0xa1, SIG_ADDTOOFFSET(+1), // sag global[57h] -> FM-Towns [9Dh]
+ // 35 bytes
+ 0x39, 0x03, // pushi 03
+ 0x89, SIG_ADDTOOFFSET(+1), // lsg global[1Dh] -> FM-Towns [1Eh]
+ 0x76, // push0
+ 0x7a, // push2
+ 0x5b, 0x00, 0xbe, // lea global[BEh]
+ 0x36, // push
+ 0x7a, // push2
+ 0x43, 0x62, 0x04, // callk StrAt
+ 0x36, // push
+ 0x35, 0x01, // ldi 01
+ 0x04, // sub
+ 0x36, // push
+ 0x43, 0x62, 0x06, // callk StrAt
+ // 22 bytes
+ 0x7a, // push2
+ 0x5b, 0x00, 0xbe, // lea global[BE]
+ 0x36, // push
+ 0x39, 0x03, // pushi 03
+ 0x43, 0x62, 0x04, // callk StrAt
+ // 10 bytes
0x36, // push
0x35, SIG_MAGICDWORD, 0x20, // ldi 20
0x04, // sub
0xa1, 0xb3, // sag global[b3]
+ // 6 bytes
SIG_END
};
static const uint16 mothergoose256PatchReplay[] = {
+ 0x39, 0x06, // pushi 06
+ 0x76, // push0
+ 0x76, // push0
+ 0x38, PATCH_UINT16(200), // pushi 200d
+ 0x38, PATCH_UINT16(320), // pushi 320d
+ 0x76, // push0
+ 0x76, // push0
+ 0x43, 0x15, 0x0c, // callk SetPort -> set picture port to full screen
+ // 15 bytes
+ 0x39, 0x04, // pushi 04
+ 0x3c, // dup
+ 0x76, // push0
+ 0x38, PATCH_UINT16(255), // pushi 255d
+ 0x76, // push0
+ 0x43, 0x6f, 0x08, // callk Palette -> set intensity to 0 for all colors
+ // 11 bytes
+ 0x7a, // push2
+ 0x38, PATCH_UINT16(800), // pushi 800
+ 0x76, // push0
+ 0x43, 0x08, 0x04, // callk DrawPic -> draw picture 800
+ // 8 bytes
+ 0x39, 0x06, // pushi 06
+ 0x39, 0x0c, // pushi 0Ch
+ 0x76, // push0
+ 0x76, // push0
+ 0x38, PATCH_UINT16(200), // push 200
+ 0x38, PATCH_UINT16(320), // push 320
+ 0x78, // push1
+ 0x43, 0x6c, 0x0c, // callk Graph -> send everything to screen
+ // 16 bytes
+ 0x39, 0x06, // pushi 06
+ 0x76, // push0
+ 0x76, // push0
+ 0x38, PATCH_UINT16(156), // pushi 156d
+ 0x38, PATCH_UINT16(258), // pushi 258d
+ 0x39, 0x03, // pushi 03
+ 0x39, 0x04, // pushi 04
+ 0x43, 0x15, 0x0c, // callk SetPort -> set picture port back
+ // 17 bytes
0x34, PATCH_UINT16(0x0000), // ldi 0000 (dummy)
0x34, PATCH_UINT16(0x0000), // ldi 0000 (dummy)
PATCH_END
@@ -1856,6 +2069,9 @@ static const uint16 mothergoose256PatchReplay[] = {
// when saving, it also checks if the savegame ID is below 13.
// we change this to check if below 113 instead
+//
+// Applies to at least: English SCI1 CD, English SCI1.1 floppy, Japanese FM-Towns
+// Responsible method: Game::save (script 994 for SCI1), MG::save (script 0 for SCI1.1)
static const uint16 mothergoose256SignatureSaveLimit[] = {
0x89, SIG_MAGICDWORD, 0xb3, // lsg global[b3]
0x35, 0x0d, // ldi 0d
@@ -1879,6 +2095,50 @@ static const SciScriptPatcherEntry mothergoose256Signatures[] = {
// ===========================================================================
// Police Quest 1 VGA
+
+// When briefing is about to start in room 15, other officers will get into the room too.
+// When one of those officers gets into the way of ego, they will tell the player to sit down.
+// But control will be disabled right at that point. Ego may then go to his seat by himself,
+// or more often than not will just stand there. The player is unable to do anything.
+//
+// Sergeant Dooley will then enter the room. Tell the player to sit down 3 times and after
+// that it's game over.
+//
+// Because the Sergeant is telling the player to sit down, one has to assume that the player
+// is meant to still be in control. Which is why this script patch removes disabling of player control.
+//
+// The script also tries to make ego walk to the chair, but it fails because it gets stuck with other
+// actors. So I guess the safest way is to remove all of that and let the player do it manually.
+//
+// The responsible method seems to use a few hardcoded texts, which is why I have to assume that it's
+// not used anywhere else. I also checked all scripts and couldn't find any other calls to it.
+//
+// This of course also happens when using the original interpreter.
+//
+// Scripts work like this: manX::doit (script 134) triggers gab::changeState, which then triggers rm015::notify
+//
+// Applies to at least: English floppy
+// Responsible method: gab::changeState (script 152)
+// Fixes bug: #5865
+static const uint16 pq1vgaSignatureBriefingGettingStuck[] = {
+ 0x76, // push0
+ 0x45, 0x02, 0x00, // call export 2 of script 0 (disable control)
+ 0x38, SIG_ADDTOOFFSET(+2), // pushi notify
+ 0x76, // push0
+ 0x81, 0x02, // lag global[2] (get current room)
+ 0x4a, 0x04, // send 04
+ SIG_MAGICDWORD,
+ 0x8b, 0x02, // lsl local[2]
+ 0x35, 0x01, // ldi 01
+ 0x02, // add
+ SIG_END
+};
+
+static const uint16 pq1vgaPatchBriefingGettingStuck[] = {
+ 0x33, 0x0a, // jmp to lsl local[2], skip over export 2 and ::notify
+ PATCH_END // rm015::notify would try to make ego walk to the chair
+};
+
// When at the police station, you can put or get your gun from your locker.
// The script, that handles this, is buggy. It disposes the gun as soon as
// you click, but then waits 2 seconds before it also closes the locker.
@@ -1966,10 +2226,11 @@ static const uint16 pq1vgaPatchMapSaveRestoreBug[] = {
PATCH_END
};
-// script, description, signature patch
+// script, description, signature patch
static const SciScriptPatcherEntry pq1vgaSignatures[] = {
- { true, 341, "put gun in locker bug", 1, pq1vgaSignaturePutGunInLockerBug, pq1vgaPatchPutGunInLockerBug },
- { true, 500, "map save/restore bug", 2, pq1vgaSignatureMapSaveRestoreBug, pq1vgaPatchMapSaveRestoreBug },
+ { true, 152, "getting stuck while briefing is about to start", 1, pq1vgaSignatureBriefingGettingStuck, pq1vgaPatchBriefingGettingStuck },
+ { true, 341, "put gun in locker bug", 1, pq1vgaSignaturePutGunInLockerBug, pq1vgaPatchPutGunInLockerBug },
+ { true, 500, "map save/restore bug", 2, pq1vgaSignatureMapSaveRestoreBug, pq1vgaPatchMapSaveRestoreBug },
SCI_SIGNATUREENTRY_TERMINATOR
};
@@ -2673,14 +2934,75 @@ static const uint16 qfg3PatchChiefPriority[] = {
PATCH_END
};
+// Partly WORKAROUND:
+// During combat, the game is not properly throttled. That's because the game uses
+// an inner loop for combat and does not iterate through the main loop.
+// It also doesn't call kGameIsRestarting. This may get fixed properly at some point
+// by rewriting the speed throttler.
+//
+// Additionally Sierra set the cycle speed of the hero to 0. Which explains
+// why the actions of the hero are so incredibly fast. This issue also happened
+// in the original interpreter, when the computer was too powerful.
+//
+// Applies to at least: English, French, German, Italian, Spanish PC floppy
+// Responsible method: combatControls::dispatchEvent (script 550) + WarriorObj in heap
+// Fixes bug #6247
+static const uint16 qfg3SignatureCombatSpeedThrottling1[] = {
+ 0x31, 0x0d, // bnt [skip code]
+ SIG_MAGICDWORD,
+ 0x89, 0xd2, // lsg global[D2h]
+ 0x35, 0x00, // ldi 0
+ 0x1e, // gt?
+ 0x31, 0x06, // bnt [skip code]
+ 0xe1, 0xd2, // -ag global[D2h] (jump skips over this)
+ 0x81, 0x58, // lag global[58h]
+ 0xa3, 0x01, // sal local[01]
+ SIG_END
+};
+
+static const uint16 qfg3PatchCombatSpeedThrottling1[] = {
+ 0x80, 0xd2, // lsg global[D2h]
+ 0x14, // or
+ 0x31, 0x06, // bnt [skip code] - saves 4 bytes
+ 0xe1, 0xd2, // -ag global[D2h]
+ 0x81, 0x58, // lag global[58h]
+ 0xa3, 0x01, // sal local[01] (jump skips over this)
+ // our code
+ 0x76, // push0
+ 0x43, 0x2c, 0x00, // callk GameIsRestarting <-- add this so that our speed throttler is triggered
+ PATCH_END
+};
+
+static const uint16 qfg3SignatureCombatSpeedThrottling2[] = {
+ SIG_MAGICDWORD,
+ SIG_UINT16(12), // priority 12
+ SIG_UINT16(0), // underbits 0
+ SIG_UINT16(0x4010), // signal 4010h
+ SIG_ADDTOOFFSET(+18),
+ SIG_UINT16(0), // scaleSignal 0
+ SIG_UINT16(128), // scaleX
+ SIG_UINT16(128), // scaleY
+ SIG_UINT16(128), // maxScale
+ SIG_UINT16(0), // cycleSpeed
+ SIG_END
+};
+
+static const uint16 qfg3PatchCombatSpeedThrottling2[] = {
+ PATCH_ADDTOOFFSET(+32),
+ PATCH_UINT16(5), // set cycleSpeed to 5
+ PATCH_END
+};
+
// script, description, signature patch
static const SciScriptPatcherEntry qfg3Signatures[] = {
- { true, 944, "import dialog continuous calls", 1, qfg3SignatureImportDialog, qfg3PatchImportDialog },
- { true, 440, "dialog crash when asking about Woo", 1, qfg3SignatureWooDialog, qfg3PatchWooDialog },
- { true, 440, "dialog crash when asking about Woo", 1, qfg3SignatureWooDialogAlt, qfg3PatchWooDialogAlt },
- { true, 52, "export character save bug", 2, qfg3SignatureExportChar, qfg3PatchExportChar },
- { true, 54, "import character from QfG1 bug", 1, qfg3SignatureImportQfG1Char, qfg3PatchImportQfG1Char },
- { true, 640, "chief in hut priority fix", 1, qfg3SignatureChiefPriority, qfg3PatchChiefPriority },
+ { true, 944, "import dialog continuous calls", 1, qfg3SignatureImportDialog, qfg3PatchImportDialog },
+ { true, 440, "dialog crash when asking about Woo", 1, qfg3SignatureWooDialog, qfg3PatchWooDialog },
+ { true, 440, "dialog crash when asking about Woo", 1, qfg3SignatureWooDialogAlt, qfg3PatchWooDialogAlt },
+ { true, 52, "export character save bug", 2, qfg3SignatureExportChar, qfg3PatchExportChar },
+ { true, 54, "import character from QfG1 bug", 1, qfg3SignatureImportQfG1Char, qfg3PatchImportQfG1Char },
+ { true, 640, "chief in hut priority fix", 1, qfg3SignatureChiefPriority, qfg3PatchChiefPriority },
+ { true, 550, "combat speed throttling script", 1, qfg3SignatureCombatSpeedThrottling1, qfg3PatchCombatSpeedThrottling1 },
+ { true, 550, "combat speed throttling heap", 1, qfg3SignatureCombatSpeedThrottling2, qfg3PatchCombatSpeedThrottling2 },
SCI_SIGNATUREENTRY_TERMINATOR
};
@@ -2836,6 +3158,66 @@ static const uint16 sq4CdPatchGetPointsForChangingBackClothes[] = {
PATCH_END
};
+
+// For Space Quest 4 CD, Sierra added a pick up animation for Roger, when he picks up the rope.
+//
+// When the player is detected by the zombie right at the start of the game, while picking up the rope,
+// scripts bomb out. This also happens, when using the original interpreter.
+//
+// This is caused by code, that's supposed to make Roger face the arriving drone.
+// We fix it, by checking if ego::cycler is actually set before calling that code.
+//
+// Applies to at least: English PC CD
+// Responsible method: droidShoots::changeState(3)
+// Fixes bug: #6076
+static const uint16 sq4CdSignatureGettingShotWhileGettingRope[] = {
+ 0x35, 0x02, // ldi 02
+ 0x65, 0x1a, // aTop cycles
+ 0x32, SIG_UINT16(0x02fa), // jmp [end]
+ SIG_MAGICDWORD,
+ 0x3c, // dup
+ 0x35, 0x02, // ldi 02
+ 0x1a, // eq?
+ 0x31, 0x0b, // bnt [state 3 check]
+ 0x76, // push0
+ 0x45, 0x02, 0x00, // call export 2 of script 0 -> disable controls
+ 0x35, 0x02, // ldi 02
+ 0x65, 0x1a, // aTop cycles
+ 0x32, SIG_UINT16(0x02e9), // jmp [end]
+ 0x3c, // dup
+ 0x35, 0x03, // ldi 03
+ 0x1a, // eq?
+ 0x31, 0x1e, // bnt [state 4 check]
+ 0x76, // push0
+ 0x45, 0x02, 0x00, // call export 2 of script 0 -> disable controls again??
+ 0x7a, // push2
+ 0x89, 0x00, // lsg global[0]
+ 0x72, SIG_UINT16(0x0242), // lofsa deathDroid
+ 0x36, // push
+ 0x45, 0x0d, 0x04, // call export 13 of script 0 -> set heading of ego to face droid
+ SIG_END
+};
+
+static const uint16 sq4CdPatchGettingShotWhileGettingRope[] = {
+ PATCH_ADDTOOFFSET(+11),
+ // this makes state 2 only do the 2 cycles wait, controls should always be disabled already at this point
+ 0x2f, 0xf3, // bt [previous state aTop cycles code]
+ // Now we check for state 3, this change saves us 11 bytes
+ 0x3c, // dup
+ 0x35, 0x03, // ldi 03
+ 0x1a, // eq?
+ 0x31, 0x29, // bnt [state 4 check]
+ // new state 3 code
+ 0x76, // push0
+ 0x45, 0x02, 0x00, // call export 2 of script 0 (disable controls, actually not needed)
+ 0x38, PATCH_SELECTOR16(cycler), // pushi cycler
+ 0x76, // push0
+ 0x81, 0x00, // lag global[0]
+ 0x4a, 0x04, // send 04 (get ego::cycler)
+ 0x30, PATCH_UINT16(10), // bnt [jump over heading call]
+ PATCH_END
+};
+
// The scripts in SQ4CD support simultaneous playing of speech and subtitles,
// but this was not available as an option. The following two patches enable
// this functionality in the game's GUI options dialog.
@@ -2932,6 +3314,7 @@ static const SciScriptPatcherEntry sq4Signatures[] = {
{ true, 700, "Floppy: throw stuff at sequel police bug", 1, sq4FloppySignatureThrowStuffAtSequelPoliceBug, sq4FloppyPatchThrowStuffAtSequelPoliceBug },
{ true, 45, "CD: walk in from below for room 45 fix", 1, sq4CdSignatureWalkInFromBelowRoom45, sq4CdPatchWalkInFromBelowRoom45 },
{ true, 396, "CD: get points for changing back clothes fix",1, sq4CdSignatureGetPointsForChangingBackClothes, sq4CdPatchGetPointsForChangingBackClothes },
+ { true, 701, "CD: getting shot, while getting rope", 1, sq4CdSignatureGettingShotWhileGettingRope, sq4CdPatchGettingShotWhileGettingRope },
{ true, 0, "CD: Babble icon speech and subtitles fix", 1, sq4CdSignatureBabbleIcon, sq4CdPatchBabbleIcon },
{ true, 818, "CD: Speech and subtitles option", 1, sq4CdSignatureTextOptions, sq4CdPatchTextOptions },
{ true, 818, "CD: Speech and subtitles option button", 1, sq4CdSignatureTextOptionsButton, sq4CdPatchTextOptionsButton },
@@ -3374,20 +3757,20 @@ bool ScriptPatcher::verifySignature(uint32 byteOffset, const uint16 *signatureDa
}
// will return -1 if no match was found, otherwise an offset to the start of the signature match
-int32 ScriptPatcher::findSignature(const SciScriptPatcherEntry *patchEntry, SciScriptPatcherRuntimeEntry *runtimeEntry, const byte *scriptData, const uint32 scriptSize) {
+int32 ScriptPatcher::findSignature(uint32 magicDWord, int magicOffset, const uint16 *signatureData, const char *patchDescription, const byte *scriptData, const uint32 scriptSize) {
if (scriptSize < 4) // we need to find a DWORD, so less than 4 bytes is not okay
return -1;
- const uint32 magicDWord = runtimeEntry->magicDWord; // is platform-specific BE/LE form, so that the later match will work
+ // magicDWord is in platform-specific BE/LE form, so that the later match will work, this was done for performance
const uint32 searchLimit = scriptSize - 3;
uint32 DWordOffset = 0;
// first search for the magic DWORD
while (DWordOffset < searchLimit) {
if (magicDWord == READ_UINT32(scriptData + DWordOffset)) {
// magic DWORD found, check if actual signature matches
- uint32 offset = DWordOffset + runtimeEntry->magicOffset;
+ uint32 offset = DWordOffset + magicOffset;
- if (verifySignature(offset, patchEntry->signatureData, patchEntry->description, scriptData, scriptSize))
+ if (verifySignature(offset, signatureData, patchDescription, scriptData, scriptSize))
return offset;
}
DWordOffset++;
@@ -3396,22 +3779,146 @@ int32 ScriptPatcher::findSignature(const SciScriptPatcherEntry *patchEntry, SciS
return -1;
}
-// This method calculates the magic DWORD for each entry in the signature table
-// and it also initializes the selector table for selectors used in the signatures/patches of the current game
-void ScriptPatcher::initSignature(const SciScriptPatcherEntry *patchTable) {
- const SciScriptPatcherEntry *curEntry = patchTable;
- SciScriptPatcherRuntimeEntry *curRuntimeEntry;
+int32 ScriptPatcher::findSignature(const SciScriptPatcherEntry *patchEntry, const SciScriptPatcherRuntimeEntry *runtimeEntry, const byte *scriptData, const uint32 scriptSize) {
+ return findSignature(runtimeEntry->magicDWord, runtimeEntry->magicOffset, patchEntry->signatureData, patchEntry->description, scriptData, scriptSize);
+}
+
+// Attention: Magic DWord is returns using platform specific byte order. This is done on purpose for performance.
+void ScriptPatcher::calculateMagicDWordAndVerify(const char *signatureDescription, const uint16 *signatureData, bool magicDWordIncluded, uint32 &calculatedMagicDWord, int &calculatedMagicDWordOffset) {
Selector curSelector = -1;
- int step;
int magicOffset;
byte magicDWord[4];
int magicDWordLeft = 0;
- const uint16 *curData;
uint16 curWord;
uint16 curCommand;
uint32 curValue;
byte byte1 = 0;
byte byte2 = 0;
+
+ memset(magicDWord, 0, sizeof(magicDWord));
+
+ curWord = *signatureData;
+ magicOffset = 0;
+ while (curWord != SIG_END) {
+ curCommand = curWord & SIG_COMMANDMASK;
+ curValue = curWord & SIG_VALUEMASK;
+ switch (curCommand) {
+ case SIG_MAGICDWORD: {
+ if (magicDWordIncluded) {
+ if ((calculatedMagicDWord) || (magicDWordLeft))
+ error("Script-Patcher: Magic-DWORD specified multiple times in signature\nFaulty patch: '%s'", signatureDescription);
+ magicDWordLeft = 4;
+ calculatedMagicDWordOffset = magicOffset;
+ } else {
+ error("Script-Patcher: Magic-DWORD sequence found in patch data\nFaulty patch: '%s'", signatureDescription);
+ }
+ break;
+ }
+ case SIG_CODE_ADDTOOFFSET: {
+ magicOffset -= curValue;
+ if (magicDWordLeft)
+ error("Script-Patcher: Magic-DWORD contains AddToOffset command\nFaulty patch: '%s'", signatureDescription);
+ break;
+ }
+ case SIG_CODE_UINT16:
+ case SIG_CODE_SELECTOR16: {
+ // UINT16 or 1
+ switch (curCommand) {
+ case SIG_CODE_UINT16: {
+ signatureData++; curWord = *signatureData;
+ if (curWord & SIG_COMMANDMASK)
+ error("Script-Patcher: signature entry inconsistent\nFaulty patch: '%s'", signatureDescription);
+ if (!_isMacSci11) {
+ byte1 = curValue;
+ byte2 = curWord & SIG_BYTEMASK;
+ } else {
+ byte1 = curWord & SIG_BYTEMASK;
+ byte2 = curValue;
+ }
+ break;
+ }
+ case SIG_CODE_SELECTOR16: {
+ curSelector = _selectorIdTable[curValue];
+ if (curSelector == -1) {
+ curSelector = g_sci->getKernel()->findSelector(selectorNameTable[curValue]);
+ _selectorIdTable[curValue] = curSelector;
+ }
+ if (!_isMacSci11) {
+ byte1 = curSelector & 0x00FF;
+ byte2 = curSelector >> 8;
+ } else {
+ byte1 = curSelector >> 8;
+ byte2 = curSelector & 0x00FF;
+ }
+ break;
+ }
+ }
+ magicOffset -= 2;
+ if (magicDWordLeft) {
+ // Remember current word for Magic DWORD
+ magicDWord[4 - magicDWordLeft] = byte1;
+ magicDWordLeft--;
+ if (magicDWordLeft) {
+ magicDWord[4 - magicDWordLeft] = byte2;
+ magicDWordLeft--;
+ }
+ if (!magicDWordLeft) {
+ // Magic DWORD is now known, convert to platform specific byte order
+ calculatedMagicDWord = READ_LE_UINT32(magicDWord);
+ }
+ }
+ break;
+ }
+ case SIG_CODE_BYTE:
+ case SIG_CODE_SELECTOR8: {
+ if (curCommand == SIG_CODE_SELECTOR8) {
+ curSelector = _selectorIdTable[curValue];
+ if (curSelector == -1) {
+ curSelector = g_sci->getKernel()->findSelector(selectorNameTable[curValue]);
+ _selectorIdTable[curValue] = curSelector;
+ if (curSelector != -1) {
+ if (curSelector & 0xFF00)
+ error("Script-Patcher: 8 bit selector required, game uses 16 bit selector\nFaulty patch: '%s'", signatureDescription);
+ }
+ }
+ curValue = curSelector;
+ }
+ magicOffset--;
+ if (magicDWordLeft) {
+ // Remember current byte for Magic DWORD
+ magicDWord[4 - magicDWordLeft] = (byte)curValue;
+ magicDWordLeft--;
+ if (!magicDWordLeft) {
+ calculatedMagicDWord = READ_LE_UINT32(magicDWord);
+ }
+ }
+ break;
+ }
+ case PATCH_CODE_GETORIGINALBYTEADJUST: {
+ signatureData++; // skip over extra uint16
+ break;
+ }
+ default:
+ break;
+ }
+ signatureData++;
+ curWord = *signatureData;
+ }
+
+ if (magicDWordLeft)
+ error("Script-Patcher: Magic-DWORD beyond End-Of-Signature\nFaulty patch: '%s'", signatureDescription);
+ if (magicDWordIncluded) {
+ if (!calculatedMagicDWord) {
+ error("Script-Patcher: Magic-DWORD not specified in signature\nFaulty patch: '%s'", signatureDescription);
+ }
+ }
+}
+
+// This method calculates the magic DWORD for each entry in the signature table
+// and it also initializes the selector table for selectors used in the signatures/patches of the current game
+void ScriptPatcher::initSignature(const SciScriptPatcherEntry *patchTable) {
+ const SciScriptPatcherEntry *curEntry = patchTable;
+ SciScriptPatcherRuntimeEntry *curRuntimeEntry;
int patchEntryCount = 0;
// Count entries and allocate runtime data
@@ -3425,120 +3932,14 @@ void ScriptPatcher::initSignature(const SciScriptPatcherEntry *patchTable) {
curRuntimeEntry = _runtimeTable;
while (curEntry->signatureData) {
// process signature
- memset(magicDWord, 0, sizeof(magicDWord));
-
curRuntimeEntry->active = curEntry->defaultActive;
curRuntimeEntry->magicDWord = 0;
curRuntimeEntry->magicOffset = 0;
- for (step = 0; step < 2; step++) {
- switch (step) {
- case 0: curData = curEntry->signatureData; break;
- case 1: curData = curEntry->patchData; break;
- }
-
- curWord = *curData;
- magicOffset = 0;
- while (curWord != SIG_END) {
- curCommand = curWord & SIG_COMMANDMASK;
- curValue = curWord & SIG_VALUEMASK;
- switch (curCommand) {
- case SIG_MAGICDWORD: {
- if (step == 0) {
- if ((curRuntimeEntry->magicDWord) || (magicDWordLeft))
- error("Script-Patcher: Magic-DWORD specified multiple times in signature\nFaulty patch: '%s'", curEntry->description);
- magicDWordLeft = 4;
- curRuntimeEntry->magicOffset = magicOffset;
- }
- break;
- }
- case SIG_CODE_ADDTOOFFSET: {
- magicOffset -= curValue;
- if (magicDWordLeft)
- error("Script-Patcher: Magic-DWORD contains AddToOffset command\nFaulty patch: '%s'", curEntry->description);
- break;
- }
- case SIG_CODE_UINT16:
- case SIG_CODE_SELECTOR16: {
- // UINT16 or 1
- switch (curCommand) {
- case SIG_CODE_UINT16: {
- curData++; curWord = *curData;
- if (curWord & SIG_COMMANDMASK)
- error("Script-Patcher: signature entry inconsistent\nFaulty patch: '%s'", curEntry->description);
- if (!_isMacSci11) {
- byte1 = curValue;
- byte2 = curWord & SIG_BYTEMASK;
- } else {
- byte1 = curWord & SIG_BYTEMASK;
- byte2 = curValue;
- }
- break;
- }
- case SIG_CODE_SELECTOR16: {
- curSelector = _selectorIdTable[curValue];
- if (curSelector == -1) {
- curSelector = g_sci->getKernel()->findSelector(selectorNameTable[curValue]);
- _selectorIdTable[curValue] = curSelector;
- }
- if (!_isMacSci11) {
- byte1 = curSelector & 0x00FF;
- byte2 = curSelector >> 8;
- } else {
- byte1 = curSelector >> 8;
- byte2 = curSelector & 0x00FF;
- }
- break;
- }
- }
- magicOffset -= 2;
- if (magicDWordLeft) {
- // Remember current word for Magic DWORD
- magicDWord[4 - magicDWordLeft] = byte1;
- magicDWordLeft--;
- if (magicDWordLeft) {
- magicDWord[4 - magicDWordLeft] = byte2;
- magicDWordLeft--;
- }
- if (!magicDWordLeft) {
- curRuntimeEntry->magicDWord = READ_LE_UINT32(magicDWord);
- }
- }
- break;
- }
- case SIG_CODE_BYTE:
- case SIG_CODE_SELECTOR8: {
- if (curCommand == SIG_CODE_SELECTOR8) {
- curSelector = _selectorIdTable[curValue];
- if (curSelector == -1) {
- curSelector = g_sci->getKernel()->findSelector(selectorNameTable[curValue]);
- _selectorIdTable[curValue] = curSelector;
- if (curSelector != -1) {
- if (curSelector & 0xFF00)
- error("Script-Patcher: 8 bit selector required, game uses 16 bit selector\nFaulty patch: '%s'", curEntry->description);
- }
- }
- curValue = curSelector;
- }
- magicOffset--;
- if (magicDWordLeft) {
- // Remember current byte for Magic DWORD
- magicDWord[4 - magicDWordLeft] = (byte)curValue;
- magicDWordLeft--;
- if (!magicDWordLeft) {
- curRuntimeEntry->magicDWord = READ_LE_UINT32(magicDWord);
- }
- }
- }
- }
- curData++;
- curWord = *curData;
- }
- }
- if (magicDWordLeft)
- error("Script-Patcher: Magic-DWORD beyond End-Of-Signature\nFaulty patch: '%s'", curEntry->description);
- if (!curRuntimeEntry->magicDWord)
- error("Script-Patcher: Magic-DWORD not specified in signature\nFaulty patch: '%s'", curEntry->description);
+ // We verify the signature data and remember the calculated magic DWord from the signature data
+ calculateMagicDWordAndVerify(curEntry->description, curEntry->signatureData, true, curRuntimeEntry->magicDWord, curRuntimeEntry->magicOffset);
+ // We verify the patch data
+ calculateMagicDWordAndVerify(curEntry->description, curEntry->patchData, false, curRuntimeEntry->magicDWord, curRuntimeEntry->magicOffset);
curEntry++; curRuntimeEntry++;
}
@@ -3596,6 +3997,9 @@ void ScriptPatcher::processScript(uint16 scriptNr, byte *scriptData, const uint3
case GID_KQ6:
signatureTable = kq6Signatures;
break;
+ case GID_LAURABOW:
+ signatureTable = laurabow1Signatures;
+ break;
case GID_LAURABOW2:
signatureTable = laurabow2Signatures;
break;
diff --git a/engines/sci/engine/script_patches.h b/engines/sci/engine/script_patches.h
index d15fce321b..645e0946b3 100644
--- a/engines/sci/engine/script_patches.h
+++ b/engines/sci/engine/script_patches.h
@@ -90,13 +90,32 @@ public:
ScriptPatcher();
~ScriptPatcher();
+ // Calculates the magic DWord for fast search and verifies signature/patch data
+ // Returns the magic DWord in platform-specific byte-order. This is done on purpose for performance.
+ void calculateMagicDWordAndVerify(const char *signatureDescription, const uint16 *signatureData, bool magicDWordIncluded, uint32 &calculatedMagicDWord, int &calculatedMagicDWordOffset);
+
+ // Called when a script is loaded to check for signature matches and apply patches in such cases
void processScript(uint16 scriptNr, byte *scriptData, const uint32 scriptSize);
+
+ // Verifies, if a given signature matches the given script data (pointed to by additional byte offset)
bool verifySignature(uint32 byteOffset, const uint16 *signatureData, const char *signatureDescription, const byte *scriptData, const uint32 scriptSize);
+ // searches for a given signature inside script data
+ // returns -1 in case it was not found or an offset to the matching data
+ int32 findSignature(uint32 magicDWord, int magicOffset, const uint16 *signatureData, const char *patchDescription, const byte *scriptData, const uint32 scriptSize);
+
private:
+ // Initializes a patch table and creates run time information for it (for enabling/disabling), also calculates magic DWORD)
void initSignature(const SciScriptPatcherEntry *patchTable);
+
+ // Enables a patch inside the patch table (used for optional patches like CD+Text support for KQ6 & LB2)
void enablePatch(const SciScriptPatcherEntry *patchTable, const char *searchDescription);
- int32 findSignature(const SciScriptPatcherEntry *patchEntry, SciScriptPatcherRuntimeEntry *runtimeEntry, const byte *scriptData, const uint32 scriptSize);
+
+ // Searches for a given signature entry inside script data
+ // returns -1 in case it was not found or an offset to the matching data
+ int32 findSignature(const SciScriptPatcherEntry *patchEntry, const SciScriptPatcherRuntimeEntry *runtimeEntry, const byte *scriptData, const uint32 scriptSize);
+
+ // Applies a patch to a given script + offset (overwrites parts)
void applyPatch(const SciScriptPatcherEntry *patchEntry, byte *scriptData, const uint32 scriptSize, int32 signatureOffset);
Selector *_selectorIdTable;
diff --git a/engines/sci/engine/scriptdebug.cpp b/engines/sci/engine/scriptdebug.cpp
index f0157a6569..7d70f30d55 100644
--- a/engines/sci/engine/scriptdebug.cpp
+++ b/engines/sci/engine/scriptdebug.cpp
@@ -499,7 +499,7 @@ void Kernel::dumpScriptClass(char *data, int seeker, int objsize) {
void Kernel::dissectScript(int scriptNumber, Vocabulary *vocab) {
int objectctr[11] = {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0};
- unsigned int _seeker = 0;
+ uint32 _seeker = 0;
Resource *script = _resMan->findResource(ResourceId(kResourceTypeScript, scriptNumber), 0);
if (!script) {
@@ -510,7 +510,7 @@ void Kernel::dissectScript(int scriptNumber, Vocabulary *vocab) {
while (_seeker < script->size) {
int objType = (int16)READ_SCI11ENDIAN_UINT16(script->data + _seeker);
int objsize;
- unsigned int seeker = _seeker + 4;
+ uint32 seeker = _seeker + 4;
if (!objType) {
debugN("End of script object (#0) encountered.\n");
diff --git a/engines/sci/engine/seg_manager.cpp b/engines/sci/engine/seg_manager.cpp
index 23b1fdae23..8090b1861d 100644
--- a/engines/sci/engine/seg_manager.cpp
+++ b/engines/sci/engine/seg_manager.cpp
@@ -837,10 +837,13 @@ byte *SegManager::allocDynmem(int size, const char *descr, reg_t *addr) {
d._size = size;
- if (size == 0)
+ // Original SCI only zeroed out heap memory on initialize
+ // They didn't do it again for every allocation
+ if (size) {
+ d._buf = (byte *)calloc(size, 1);
+ } else {
d._buf = NULL;
- else
- d._buf = (byte *)malloc(size);
+ }
d._description = descr;
diff --git a/engines/sci/engine/segment.h b/engines/sci/engine/segment.h
index de7f60ac16..2699bc2e5b 100644
--- a/engines/sci/engine/segment.h
+++ b/engines/sci/engine/segment.h
@@ -203,7 +203,7 @@ struct List {
struct Hunk {
void *mem;
- unsigned int size;
+ uint32 size;
const char *type;
};
diff --git a/engines/sci/engine/selector.cpp b/engines/sci/engine/selector.cpp
index 910f1f885f..ac621f58ae 100644
--- a/engines/sci/engine/selector.cpp
+++ b/engines/sci/engine/selector.cpp
@@ -179,6 +179,8 @@ void Kernel::mapSelectors() {
FIND_SELECTOR(fore);
FIND_SELECTOR(back);
FIND_SELECTOR(skip);
+ FIND_SELECTOR(borderColor);
+ FIND_SELECTOR(width);
FIND_SELECTOR(fixPriority);
FIND_SELECTOR(mirrored);
FIND_SELECTOR(visible);
@@ -187,6 +189,17 @@ void Kernel::mapSelectors() {
FIND_SELECTOR(inLeft);
FIND_SELECTOR(inBottom);
FIND_SELECTOR(inRight);
+ FIND_SELECTOR(textTop);
+ FIND_SELECTOR(textLeft);
+ FIND_SELECTOR(textBottom);
+ FIND_SELECTOR(textRight);
+ FIND_SELECTOR(title);
+ FIND_SELECTOR(titleFont);
+ FIND_SELECTOR(titleFore);
+ FIND_SELECTOR(titleBack);
+ FIND_SELECTOR(magnifier);
+ FIND_SELECTOR(frameOut);
+ FIND_SELECTOR(casts);
#endif
}
@@ -199,6 +212,16 @@ reg_t readSelector(SegManager *segMan, reg_t object, Selector selectorId) {
return *address.getPointer(segMan);
}
+#ifdef ENABLE_SCI32
+void updateInfoFlagViewVisible(Object *obj, int index) {
+ // TODO: Make this correct for all SCI versions
+ // Selectors 26 through 44 are selectors for View script objects in SQ6
+ if (index >= 26 && index <= 44 && getSciVersion() >= SCI_VERSION_2) {
+ obj->setInfoSelectorFlag(kInfoFlagViewVisible);
+ }
+}
+#endif
+
void writeSelector(SegManager *segMan, reg_t object, Selector selectorId, reg_t value) {
ObjVarRef address;
@@ -211,8 +234,12 @@ void writeSelector(SegManager *segMan, reg_t object, Selector selectorId, reg_t
if (lookupSelector(segMan, object, selectorId, &address, NULL) != kSelectorVariable)
error("Selector '%s' of object at %04x:%04x could not be"
" written to", g_sci->getKernel()->getSelectorName(selectorId).c_str(), PRINT_REG(object));
- else
+ else {
*address.getPointer(segMan) = value;
+#ifdef ENABLE_SCI32
+ updateInfoFlagViewVisible(segMan->getObject(object), selectorId);
+#endif
+ }
}
void invokeSelector(EngineState *s, reg_t object, int selectorId,
diff --git a/engines/sci/engine/selector.h b/engines/sci/engine/selector.h
index b3dd393708..f2d06d1cf4 100644
--- a/engines/sci/engine/selector.h
+++ b/engines/sci/engine/selector.h
@@ -146,6 +146,8 @@ struct SelectorCache {
Selector back;
Selector skip;
Selector dimmed;
+ Selector borderColor;
+ Selector width;
Selector fixPriority;
Selector mirrored;
@@ -153,6 +155,12 @@ struct SelectorCache {
Selector useInsetRect;
Selector inTop, inLeft, inBottom, inRight;
+ Selector textTop, textLeft, textBottom, textRight;
+ Selector title, titleFont, titleFore, titleBack;
+
+ Selector magnifier;
+ Selector frameOut;
+ Selector casts; // needed for sync'ing screen items/planes with scripts, when our save/restore code is patched in (see GfxFrameout::syncWithScripts)
#endif
};
@@ -191,6 +199,16 @@ void writeSelector(SegManager *segMan, reg_t object, Selector selectorId, reg_t
void invokeSelector(EngineState *s, reg_t object, int selectorId,
int k_argc, StackPtr k_argp, int argc = 0, const reg_t *argv = 0);
+#ifdef ENABLE_SCI32
+/**
+ * SCI32 set kInfoFlagViewVisible in the -info- selector if a certain
+ * range of properties was written to.
+ * This function checks if index is in the right range, and sets the flag
+ * on obj.-info- if it is.
+ */
+void updateInfoFlagViewVisible(Object *obj, int index);
+#endif
+
} // End of namespace Sci
#endif // SCI_ENGINE_KERNEL_H
diff --git a/engines/sci/engine/state.cpp b/engines/sci/engine/state.cpp
index d53e6b48c8..fda78317b5 100644
--- a/engines/sci/engine/state.cpp
+++ b/engines/sci/engine/state.cpp
@@ -95,6 +95,7 @@ void EngineState::reset(bool isRestoring) {
// reset delayed restore game functionality
_delayedRestoreGame = false;
_delayedRestoreGameId = 0;
+ _delayedRestoreFromLauncher = false;
executionStackBase = 0;
_executionStackPosChanged = false;
diff --git a/engines/sci/engine/state.h b/engines/sci/engine/state.h
index 0f04e32fe5..cf9a753f5c 100644
--- a/engines/sci/engine/state.h
+++ b/engines/sci/engine/state.h
@@ -138,10 +138,12 @@ public:
// see detection.cpp / SciEngine::loadGameState()
bool _delayedRestoreGame; // boolean, that triggers delayed restore (triggered by ScummVM menu)
int _delayedRestoreGameId; // the saved game id, that it supposed to get restored (triggered by ScummVM menu)
+ bool _delayedRestoreFromLauncher; // is set, when the the delayed restore game was triggered from launcher
uint _chosenQfGImportItem; // Remembers the item selected in QfG import rooms
bool _cursorWorkaroundActive; // Refer to GfxCursor::setPosition()
+ int16 _cursorWorkaroundPosCount; // When the cursor is reported to be at the previously set coordinate, we won't disable the workaround unless it happened for this many times
Common::Point _cursorWorkaroundPoint;
Common::Rect _cursorWorkaroundRect;
diff --git a/engines/sci/engine/vm.cpp b/engines/sci/engine/vm.cpp
index 3729fc5236..64e6c045db 100644
--- a/engines/sci/engine/vm.cpp
+++ b/engines/sci/engine/vm.cpp
@@ -258,6 +258,10 @@ static void _exec_varselectors(EngineState *s) {
if (xs.argc) { // write?
*var = xs.variables_argp[1];
+#ifdef ENABLE_SCI32
+ updateInfoFlagViewVisible(s->_segMan->getObject(xs.addr.varp.obj), xs.addr.varp.varindex);
+#endif
+
} else // No, read
s->r_acc = *var;
}
@@ -1095,6 +1099,9 @@ void run_vm(EngineState *s) {
case op_aTop: // 0x32 (50)
// Accumulator To Property
validate_property(s, obj, opparams[0]) = s->r_acc;
+#ifdef ENABLE_SCI32
+ updateInfoFlagViewVisible(obj, opparams[0]>>1);
+#endif
break;
case op_pTos: // 0x33 (51)
@@ -1105,6 +1112,9 @@ void run_vm(EngineState *s) {
case op_sTop: // 0x34 (52)
// Stack To Property
validate_property(s, obj, opparams[0]) = POP32();
+#ifdef ENABLE_SCI32
+ updateInfoFlagViewVisible(obj, opparams[0]>>1);
+#endif
break;
case op_ipToa: // 0x35 (53)
@@ -1119,7 +1129,9 @@ void run_vm(EngineState *s) {
opProperty += 1;
else
opProperty -= 1;
-
+#ifdef ENABLE_SCI32
+ updateInfoFlagViewVisible(obj, opparams[0]>>1);
+#endif
if (opcode == op_ipToa || opcode == op_dpToa)
s->r_acc = opProperty;
else
diff --git a/engines/sci/engine/vm_types.cpp b/engines/sci/engine/vm_types.cpp
index cf008c45e1..d74e2b194c 100644
--- a/engines/sci/engine/vm_types.cpp
+++ b/engines/sci/engine/vm_types.cpp
@@ -210,12 +210,30 @@ reg_t reg_t::operator^(const reg_t right) const {
return lookForWorkaround(right, "bitwise XOR");
}
+#ifdef ENABLE_SCI32
+reg_t reg_t::operator&(int16 right) const {
+ return *this & make_reg(0, right);
+}
+
+reg_t reg_t::operator|(int16 right) const {
+ return *this | make_reg(0, right);
+}
+
+reg_t reg_t::operator^(int16 right) const {
+ return *this ^ make_reg(0, right);
+}
+#endif
+
int reg_t::cmp(const reg_t right, bool treatAsUnsigned) const {
if (getSegment() == right.getSegment()) { // can compare things in the same segment
if (treatAsUnsigned || !isNumber())
return toUint16() - right.toUint16();
else
return toSint16() - right.toSint16();
+#ifdef ENABLE_SCI32
+ } else if (getSciVersion() >= SCI_VERSION_2) {
+ return sci32Comparison(right);
+#endif
} else if (pointerComparisonWithInteger(right)) {
return 1;
} else if (right.pointerComparisonWithInteger(*this)) {
@@ -224,6 +242,26 @@ int reg_t::cmp(const reg_t right, bool treatAsUnsigned) const {
return lookForWorkaround(right, "comparison").toSint16();
}
+#ifdef ENABLE_SCI32
+int reg_t::sci32Comparison(const reg_t right) const {
+ // In SCI32, MemIDs are normally indexes into the memory manager's handle
+ // list, but the engine reserves indexes at and above 20000 for objects
+ // that were created inside the engine (as opposed to inside the VM). The
+ // engine compares these as a tiebreaker for graphics objects that are at
+ // the same priority, and it is necessary to at least minimally handle
+ // this situation.
+ // This is obviously a bogus comparision, but then, this entire thing is
+ // bogus. For the moment, it just needs to be deterministic.
+ if (isNumber() && !right.isNumber()) {
+ return 1;
+ } else if (right.isNumber() && !isNumber()) {
+ return -1;
+ }
+
+ return getOffset() - right.getOffset();
+}
+#endif
+
bool reg_t::pointerComparisonWithInteger(const reg_t right) const {
// This function handles the case where a script tries to compare a pointer
// to a number. Normally, we would not want to allow that. However, SCI0 -
diff --git a/engines/sci/engine/vm_types.h b/engines/sci/engine/vm_types.h
index af78bd0b84..e60f52e85c 100644
--- a/engines/sci/engine/vm_types.h
+++ b/engines/sci/engine/vm_types.h
@@ -136,6 +136,19 @@ struct reg_t {
reg_t operator|(const reg_t right) const;
reg_t operator^(const reg_t right) const;
+#ifdef ENABLE_SCI32
+ reg_t operator&(int16 right) const;
+ reg_t operator|(int16 right) const;
+ reg_t operator^(int16 right) const;
+
+ void operator&=(const reg_t &right) { *this = *this & right; }
+ void operator|=(const reg_t &right) { *this = *this | right; }
+ void operator^=(const reg_t &right) { *this = *this ^ right; }
+ void operator&=(int16 right) { *this = *this & right; }
+ void operator|=(int16 right) { *this = *this | right; }
+ void operator^=(int16 right) { *this = *this ^ right; }
+#endif
+
private:
/**
* Compares two reg_t's.
@@ -147,6 +160,10 @@ private:
int cmp(const reg_t right, bool treatAsUnsigned) const;
reg_t lookForWorkaround(const reg_t right, const char *operation) const;
bool pointerComparisonWithInteger(const reg_t right) const;
+
+#ifdef ENABLE_SCI32
+ int sci32Comparison(const reg_t right) const;
+#endif
};
static inline reg_t make_reg(SegmentId segment, uint16 offset) {
diff --git a/engines/sci/engine/workarounds.cpp b/engines/sci/engine/workarounds.cpp
index aab32032f7..3832f4cf04 100644
--- a/engines/sci/engine/workarounds.cpp
+++ b/engines/sci/engine/workarounds.cpp
@@ -378,6 +378,7 @@ const SciWorkaroundEntry uninitializedReadWorkarounds[] = {
{ GID_SQ6, -1, 64950, -1, "Feature", "handleEvent", NULL, 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", NULL, 1, { WORKAROUND_FAKE, 0 } }, // during the game
{ GID_TORIN, -1, 64017, 0, "oFlags", "clear", NULL, 0, { WORKAROUND_FAKE, 0 } }, // entering Torin's home in the French version
+ { GID_TORIN, 10000, 64029, 0, "oMessager", "nextMsg", NULL, 3, { WORKAROUND_FAKE, 0 } }, // start of chapter one
SCI_WORKAROUNDENTRY_TERMINATOR
};
@@ -630,7 +631,7 @@ const SciWorkaroundEntry kGraphUpdateBox_workarounds[] = {
// gameID, room,script,lvl, object-name, method-name, local-call-signature, index, workaround
const SciWorkaroundEntry kIsObject_workarounds[] = {
- { GID_GK1, 50, 999, 0, "List", "eachElementDo", NULL, 0, { WORKAROUND_FAKE, 0 } }, // GK1 demo, when asking Grace for messages it gets called with an invalid parameter (type "error") - bug #4950
+ { GID_GK1DEMO, 50, 999, 0, "List", "eachElementDo", NULL, 0, { WORKAROUND_FAKE, 0 } }, // GK1 demo, when asking Grace for messages it gets called with an invalid parameter (type "error") - bug #4950
{ GID_ISLANDBRAIN, -1, 999, 0, "List", "eachElementDo", NULL, 0, { WORKAROUND_FAKE, 0 } }, // when going to the game options, choosing "Info" and selecting anything from the list, gets called with an invalid parameter (type "error") - bug #4989
{ GID_QFG3, -1, 999, 0, "List", "eachElementDo", NULL, 0, { WORKAROUND_FAKE, 0 } }, // when asking for something, gets called with type error parameter
SCI_WORKAROUNDENTRY_TERMINATOR
@@ -656,6 +657,12 @@ const SciWorkaroundEntry kNewWindow_workarounds[] = {
};
// gameID, room,script,lvl, object-name, method-name, local-call-signature, index, workaround
+const SciWorkaroundEntry kPalVarySetPercent_workarounds[] = {
+ { GID_GK1, 370, 370, 0, "graceComeOut", "changeState", NULL, 0, { WORKAROUND_STILLCALL, 0 } }, // there's an extra parameter in GK1, when changing chapters. This extra parameter seems to be a bug or just unimplemented functionality, as there's no visible change from the original in the chapter change room
+ SCI_WORKAROUNDENTRY_TERMINATOR
+};
+
+// gameID, room,script,lvl, object-name, method-name, local-call-signature, index, workaround
const SciWorkaroundEntry kReadNumber_workarounds[] = {
{ GID_CNICK_LAURABOW,100, 101, 0, "dominoes.opt", "doit", NULL, 0, { WORKAROUND_STILLCALL, 0 } }, // When dominoes.opt is present, the game scripts call kReadNumber with an extra integer parameter - bug #6425
{ GID_HOYLE3, 100, 101, 0, "dominoes.opt", "doit", NULL, 0, { WORKAROUND_STILLCALL, 0 } }, // When dominoes.opt is present, the game scripts call kReadNumber with an extra integer parameter - bug #6425
@@ -664,7 +671,7 @@ const SciWorkaroundEntry kReadNumber_workarounds[] = {
// gameID, room,script,lvl, object-name, method-name, local-call-signature, index, workaround
const SciWorkaroundEntry kPaletteUnsetFlag_workarounds[] = {
- { GID_QFG4, 100, 100, 0, "doMovie", "changeState", NULL, 0, { WORKAROUND_IGNORE, 0 } }, // after the Sierra logo, no flags are passed, thus the call is meaningless - bug #4947
+ { GID_QFG4DEMO, 100, 100, 0, "doMovie", "changeState", NULL, 0, { WORKAROUND_IGNORE, 0 } }, // after the Sierra logo, no flags are passed, thus the call is meaningless - bug #4947
SCI_WORKAROUNDENTRY_TERMINATOR
};
diff --git a/engines/sci/engine/workarounds.h b/engines/sci/engine/workarounds.h
index 46059a175c..8f519a8c9c 100644
--- a/engines/sci/engine/workarounds.h
+++ b/engines/sci/engine/workarounds.h
@@ -89,6 +89,7 @@ extern const SciWorkaroundEntry kIsObject_workarounds[];
extern const SciWorkaroundEntry kMemory_workarounds[];
extern const SciWorkaroundEntry kMoveCursor_workarounds[];
extern const SciWorkaroundEntry kNewWindow_workarounds[];
+extern const SciWorkaroundEntry kPalVarySetPercent_workarounds[];
extern const SciWorkaroundEntry kReadNumber_workarounds[];
extern const SciWorkaroundEntry kPaletteUnsetFlag_workarounds[];
extern const SciWorkaroundEntry kSetCursor_workarounds[];
diff --git a/engines/sci/event.cpp b/engines/sci/event.cpp
index f8285c26f2..34f1618514 100644
--- a/engines/sci/event.cpp
+++ b/engines/sci/event.cpp
@@ -29,6 +29,9 @@
#include "sci/console.h"
#include "sci/engine/state.h"
#include "sci/engine/kernel.h"
+#ifdef ENABLE_SCI32
+#include "sci/graphics/frameout.h"
+#endif
#include "sci/graphics/screen.h"
namespace Sci {
@@ -44,7 +47,7 @@ static const ScancodeRow scancodeAltifyRows[] = {
{ 0x2c, "ZXCVBNM,./" }
};
-static const byte codepagemap_88591toDOS[0x80] = {
+static const byte codePageMap88591ToDOS[0x80] = {
'?', '?', '?', '?', '?', '?', '?', '?', '?', '?', '?', '?', '?', '?', '?', '?', // 0x8x
'?', '?', '?', '?', '?', '?', '?', '?', '?', '?', '?', '?', '?', '?', '?', '?', // 0x9x
'?', 0xad, 0x9b, 0x9c, '?', 0x9d, '?', 0x9e, '?', '?', 0xa6, 0xae, 0xaa, '?', '?', '?', // 0xAx
@@ -133,8 +136,13 @@ static int altify(int ch) {
}
SciEvent EventManager::getScummVMEvent() {
- SciEvent input = { SCI_EVENT_NONE, 0, 0, Common::Point(0, 0) };
- SciEvent noEvent = { SCI_EVENT_NONE, 0, 0, Common::Point(0, 0) };
+#ifdef ENABLE_SCI32
+ SciEvent input = { SCI_EVENT_NONE, 0, 0, Common::Point(), Common::Point() };
+ SciEvent noEvent = { SCI_EVENT_NONE, 0, 0, Common::Point(), Common::Point() };
+#else
+ SciEvent input = { SCI_EVENT_NONE, 0, 0, Common::Point() };
+ SciEvent noEvent = { SCI_EVENT_NONE, 0, 0, Common::Point() };
+#endif
Common::EventManager *em = g_system->getEventManager();
Common::Event ev;
@@ -155,7 +163,20 @@ SciEvent EventManager::getScummVMEvent() {
// via pollEvent.
// We also adjust the position based on the scaling of the screen.
Common::Point mousePos = em->getMousePos();
- g_sci->_gfxScreen->adjustBackUpscaledCoordinates(mousePos.y, mousePos.x);
+
+#if ENABLE_SCI32
+ if (getSciVersion() >= SCI_VERSION_2) {
+ Buffer &screen = g_sci->_gfxFrameout->getCurrentBuffer();
+
+ Common::Point mousePosSci = mousePos;
+ mulru(mousePosSci, Ratio(screen.scriptWidth, screen.screenWidth), Ratio(screen.scriptHeight, screen.screenHeight));
+ noEvent.mousePosSci = input.mousePosSci = mousePosSci;
+ } else {
+#endif
+ g_sci->_gfxScreen->adjustBackUpscaledCoordinates(mousePos.y, mousePos.x);
+#if ENABLE_SCI32
+ }
+#endif
noEvent.mousePos = input.mousePos = mousePos;
@@ -173,7 +194,7 @@ SciEvent EventManager::getScummVMEvent() {
return input;
}
- int scummVMKeyFlags;
+ int scummVMKeyFlags;
switch (ev.type) {
case Common::EVENT_KEYDOWN:
@@ -265,7 +286,7 @@ SciEvent EventManager::getScummVMEvent() {
return noEvent;
// Convert 8859-1 characters to DOS (cp850/437) for
// multilingual SCI01 games
- input.character = codepagemap_88591toDOS[input.character & 0x7f];
+ input.character = codePageMap88591ToDOS[input.character & 0x7f];
}
if (scummVMKeycode == Common::KEYCODE_TAB) {
input.character = SCI_KEY_TAB;
@@ -302,6 +323,11 @@ SciEvent EventManager::getScummVMEvent() {
input.character = altify(input.character);
if (getSciVersion() <= SCI_VERSION_1_MIDDLE && (scummVMKeyFlags & Common::KBD_CTRL) && input.character > 0 && input.character < 27)
input.character += 96; // 0x01 -> 'a'
+#ifdef ENABLE_SCI32
+ if (getSciVersion() >= SCI_VERSION_2 && (scummVMKeyFlags & Common::KBD_CTRL) && input.character == 'c') {
+ input.character = SCI_KEY_ETX;
+ }
+#endif
// If no actual key was pressed (e.g. if only a modifier key was pressed),
// ignore the event
@@ -328,8 +354,12 @@ void EventManager::updateScreen() {
}
}
-SciEvent EventManager::getSciEvent(unsigned int mask) {
- SciEvent event = { SCI_EVENT_NONE, 0, 0, Common::Point(0, 0) };
+SciEvent EventManager::getSciEvent(uint32 mask) {
+#ifdef ENABLE_SCI32
+ SciEvent event = { SCI_EVENT_NONE, 0, 0, Common::Point(), Common::Point() };
+#else
+ SciEvent event = { SCI_EVENT_NONE, 0, 0, Common::Point() };
+#endif
EventManager::updateScreen();
@@ -342,7 +372,7 @@ SciEvent EventManager::getSciEvent(unsigned int mask) {
// Search for matching event in queue
Common::List<SciEvent>::iterator iter = _events.begin();
- while (iter != _events.end() && !((*iter).type & mask))
+ while (iter != _events.end() && !(iter->type & mask))
++iter;
if (iter != _events.end()) {
@@ -364,17 +394,17 @@ SciEvent EventManager::getSciEvent(unsigned int mask) {
void SciEngine::sleep(uint32 msecs) {
uint32 time;
- const uint32 wakeup_time = g_system->getMillis() + msecs;
+ const uint32 wakeUpTime = g_system->getMillis() + msecs;
while (true) {
// let backend process events and update the screen
_eventMan->getSciEvent(SCI_EVENT_PEEK);
time = g_system->getMillis();
- if (time + 10 < wakeup_time) {
+ if (time + 10 < wakeUpTime) {
g_system->delayMillis(10);
} else {
- if (time < wakeup_time)
- g_system->delayMillis(wakeup_time - time);
+ if (time < wakeUpTime)
+ g_system->delayMillis(wakeUpTime - time);
break;
}
diff --git a/engines/sci/event.h b/engines/sci/event.h
index 76c884aba4..15a94b3e73 100644
--- a/engines/sci/event.h
+++ b/engines/sci/event.h
@@ -39,11 +39,18 @@ struct SciEvent {
uint16 character;
/**
- * The mouse position at the time the event was created.
- *
- * These are display coordinates!
+ * The mouse position at the time the event was created,
+ * in display coordinates.
*/
Common::Point mousePos;
+
+#ifdef ENABLE_SCI32
+ /**
+ * The mouse position at the time the event was created,
+ * in script coordinates.
+ */
+ Common::Point mousePosSci;
+#endif
};
/*Values for type*/
@@ -59,6 +66,9 @@ struct SciEvent {
#define SCI_EVENT_ANY 0x7fff
/* Keycodes of special keys: */
+#ifdef ENABLE_SCI32
+#define SCI_KEY_ETX 3
+#endif
#define SCI_KEY_ESC 27
#define SCI_KEY_BACKSPACE 8
#define SCI_KEY_ENTER 13
@@ -121,7 +131,7 @@ public:
~EventManager();
void updateScreen();
- SciEvent getSciEvent(unsigned int mask);
+ SciEvent getSciEvent(uint32 mask);
private:
SciEvent getScummVMEvent();
diff --git a/engines/sci/graphics/animate.cpp b/engines/sci/graphics/animate.cpp
index 7957ed6a55..98278397b7 100644
--- a/engines/sci/graphics/animate.cpp
+++ b/engines/sci/graphics/animate.cpp
@@ -28,6 +28,7 @@
#include "sci/sci.h"
#include "sci/event.h"
#include "sci/engine/kernel.h"
+#include "sci/engine/script_patches.h"
#include "sci/engine/state.h"
#include "sci/engine/selector.h"
#include "sci/engine/vm.h"
@@ -44,8 +45,8 @@
namespace Sci {
-GfxAnimate::GfxAnimate(EngineState *state, GfxCache *cache, GfxPorts *ports, GfxPaint16 *paint16, GfxScreen *screen, GfxPalette *palette, GfxCursor *cursor, GfxTransitions *transitions)
- : _s(state), _cache(cache), _ports(ports), _paint16(paint16), _screen(screen), _palette(palette), _cursor(cursor), _transitions(transitions) {
+GfxAnimate::GfxAnimate(EngineState *state, ScriptPatcher *scriptPatcher, GfxCache *cache, GfxPorts *ports, GfxPaint16 *paint16, GfxScreen *screen, GfxPalette *palette, GfxCursor *cursor, GfxTransitions *transitions)
+ : _s(state), _scriptPatcher(scriptPatcher), _cache(cache), _ports(ports), _paint16(paint16), _screen(screen), _palette(palette), _cursor(cursor), _transitions(transitions) {
init();
}
@@ -55,16 +56,77 @@ GfxAnimate::~GfxAnimate() {
void GfxAnimate::init() {
_lastCastData.clear();
- _ignoreFastCast = false;
- // fastCast object is not found in any SCI games prior SCI1
- if (getSciVersion() <= SCI_VERSION_01)
- _ignoreFastCast = true;
- // Also if fastCast object exists at gamestartup, we can assume that the interpreter doesnt do kAnimate aborts
- // (found in Larry 1)
- if (getSciVersion() > SCI_VERSION_0_EARLY) {
- if (!_s->_segMan->findObjectByName("fastCast").isNull())
- _ignoreFastCast = true;
+ _fastCastEnabled = false;
+ if (getSciVersion() == SCI_VERSION_1_1) {
+ // Seems to have been available for all SCI1.1 games
+ _fastCastEnabled = true;
+ } else if (getSciVersion() >= SCI_VERSION_1_EARLY) {
+ // fastCast only exists for some games between SCI1 early and SCI1 late
+ // Try to detect it by code signature
+ // It's extremely important, that we only enable it for games that actually need it
+ if (detectFastCast()) {
+ _fastCastEnabled = true;
+ }
+ }
+}
+
+// Signature for fastCast detection
+static const uint16 fastCastSignature[] = {
+ SIG_MAGICDWORD,
+ 0x35, 0x00, // ldi 00
+ 0xa1, 84, // sag global[84d]
+ SIG_END
+};
+
+// Fast cast in games:
+
+// SCI1 Early:
+// KQ5 - no fastcast, LSL1 (demo) - no fastcast, Mixed Up Fairy Tales - *has fastcast*, XMas Card 1990 - no fastcast,
+// SQ4Floppy - no fastcast, Mixed Up Mother Goose - no fastcast
+//
+// SCI1 Middle:
+// LSL5 demo - no fastfast, Conquest of the Longbow demo - no fastcast, LSL1 - no fastcast,
+// Astro Chicken II - no fastcast
+//
+// SCI1 Late:
+// Castle of Dr. Brain demo - has fastcast, Castle of Dr. Brain - has fastcast,
+// Conquests of the Longbow - has fastcast, Space Quest 1 EGA - has fastcast,
+// King's Quest 5 multilingual - *NO* fastcast, Police Quest 3 demo - *NO* fastcast,
+// LSL5 multilingual - has fastcast, Police Quest 3 - has fastcast,
+// EcoQuest 1 - has fastcast, Mixed Up Fairy Tales demo - has fastcast,
+// Space Quest 4 multilingual - *NO* fastcast
+//
+// SCI1.1
+// Quest for Glory 3 demo - has fastcast, Police Quest 1 - hast fastcast, Quest for Glory 1 - has fastcast
+// Laura Bow 2 Floppy - has fastcast, Mixed Up Mother Goose - has fastcast, Quest for Glory 3 - has fastcast
+// Island of Dr. Brain - has fastcast, King's Quest 6 - has fastcast, Space Quest 5 - has fastcast
+// Hoyle 4 - has fastcast, Laura Bow 2 CD - has fastcast, Freddy Pharkas CD - has fastcast
+bool GfxAnimate::detectFastCast() {
+ SegManager *segMan = _s->_segMan;
+ const reg_t gameVMObject = g_sci->getGameObject();
+ reg_t gameSuperVMObject = segMan->getObject(gameVMObject)->getSuperClassSelector();
+ uint32 magicDWord = 0; // platform-specific BE/LE for performance
+ int magicDWordOffset = 0;
+
+ if (gameSuperVMObject.isNull()) {
+ gameSuperVMObject = gameVMObject; // Just in case. According to sci.cpp this may happen in KQ5CD, when loading saved games before r54510
+ }
+
+ Script *objectScript = segMan->getScript(gameSuperVMObject.getSegment());
+ byte *scriptData = const_cast<byte *>(objectScript->getBuf(0));
+ uint32 scriptSize = objectScript->getBufSize();
+
+ _scriptPatcher->calculateMagicDWordAndVerify("fast cast detection", fastCastSignature, true, magicDWord, magicDWordOffset);
+
+ // Signature is found for multilingual King's Quest 5 too, but it looks as if the fast cast global is never set
+ // within that game. Which means even though we detect it as having the capability, it's never actually used.
+ // The original multilingual KQ5 interpreter did have this feature disabled.
+ // Sierra probably used latest system scripts and that's why we detect it.
+ if (_scriptPatcher->findSignature(magicDWord, magicDWordOffset, fastCastSignature, "fast cast detection", scriptData, scriptSize) >= 0) {
+ // Signature found, game seems to use fast cast for kAnimate
+ return true;
}
+ return false;
}
void GfxAnimate::disposeLastCast() {
@@ -80,12 +142,14 @@ bool GfxAnimate::invoke(List *list, int argc, reg_t *argv) {
while (curNode) {
curObject = curNode->value;
- if (!_ignoreFastCast) {
+ if (_fastCastEnabled) {
// Check if the game has a fastCast object set
// if we don't abort kAnimate processing, at least in kq5 there will be animation cels drawn into speech boxes.
if (!_s->variables[VAR_GLOBAL][84].isNull()) {
- if (!strcmp(_s->_segMan->getObjectName(_s->variables[VAR_GLOBAL][84]), "fastCast"))
- return false;
+ // This normally points to an object called "fastCast",
+ // but for example in Eco Quest 1 it may also point to an object called "EventHandler" (see bug #5170)
+ // Original SCI only checked, if this global was not 0.
+ return false;
}
}
diff --git a/engines/sci/graphics/animate.h b/engines/sci/graphics/animate.h
index 6c1822c903..ac7078093c 100644
--- a/engines/sci/graphics/animate.h
+++ b/engines/sci/graphics/animate.h
@@ -87,9 +87,13 @@ class GfxView;
*/
class GfxAnimate {
public:
- GfxAnimate(EngineState *state, GfxCache *cache, GfxPorts *ports, GfxPaint16 *paint16, GfxScreen *screen, GfxPalette *palette, GfxCursor *cursor, GfxTransitions *transitions);
+ GfxAnimate(EngineState *state, ScriptPatcher *scriptPatcher, GfxCache *cache, GfxPorts *ports, GfxPaint16 *paint16, GfxScreen *screen, GfxPalette *palette, GfxCursor *cursor, GfxTransitions *transitions);
virtual ~GfxAnimate();
+ bool isFastCastEnabled() {
+ return _fastCastEnabled;
+ }
+
void disposeLastCast();
bool invoke(List *list, int argc, reg_t *argv);
void makeSortedList(List *list);
@@ -110,6 +114,7 @@ public:
private:
void init();
+ bool detectFastCast();
void addToPicSetPicNotValid();
void animateShowPic();
@@ -119,6 +124,7 @@ private:
void setNsRect(GfxView *view, AnimateList::iterator it);
EngineState *_s;
+ ScriptPatcher *_scriptPatcher;
GfxCache *_cache;
GfxPorts *_ports;
GfxPaint16 *_paint16;
@@ -130,7 +136,7 @@ private:
AnimateList _list;
AnimateArray _lastCastData;
- bool _ignoreFastCast;
+ bool _fastCastEnabled;
};
} // End of namespace Sci
diff --git a/engines/sci/graphics/cache.cpp b/engines/sci/graphics/cache.cpp
index 59af8334eb..fb1f557ad6 100644
--- a/engines/sci/graphics/cache.cpp
+++ b/engines/sci/graphics/cache.cpp
@@ -102,8 +102,4 @@ int16 GfxCache::kernelViewGetCelCount(GuiResourceId viewId, int16 loopNo) {
return getView(viewId)->getCelCount(loopNo);
}
-byte GfxCache::kernelViewGetColorAtCoordinate(GuiResourceId viewId, int16 loopNo, int16 celNo, int16 x, int16 y) {
- return getView(viewId)->getColorAtCoordinate(loopNo, celNo, x, y);
-}
-
} // End of namespace Sci
diff --git a/engines/sci/graphics/cache.h b/engines/sci/graphics/cache.h
index 33fa4fe399..61952718a9 100644
--- a/engines/sci/graphics/cache.h
+++ b/engines/sci/graphics/cache.h
@@ -49,8 +49,6 @@ public:
int16 kernelViewGetLoopCount(GuiResourceId viewId);
int16 kernelViewGetCelCount(GuiResourceId viewId, int16 loopNo);
- byte kernelViewGetColorAtCoordinate(GuiResourceId viewId, int16 loopNo, int16 celNo, int16 x, int16 y);
-
private:
void purgeFontCache();
void purgeViewCache();
diff --git a/engines/sci/graphics/celobj32.cpp b/engines/sci/graphics/celobj32.cpp
new file mode 100644
index 0000000000..693bc5f196
--- /dev/null
+++ b/engines/sci/graphics/celobj32.cpp
@@ -0,0 +1,1050 @@
+/* ScummVM - Graphic Adventure Engine
+ *
+ * ScummVM is the legal property of its developers, whose names
+ * are too numerous to list here. Please refer to the COPYRIGHT
+ * file distributed with this source distribution.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ *
+ */
+
+#include "sci/resource.h"
+#include "sci/engine/seg_manager.h"
+#include "sci/engine/state.h"
+#include "sci/graphics/celobj32.h"
+#include "sci/graphics/frameout.h"
+#include "sci/graphics/palette32.h"
+#include "sci/graphics/picture.h"
+#include "sci/graphics/remap.h"
+#include "sci/graphics/text32.h"
+#include "sci/graphics/view.h"
+
+namespace Sci {
+#pragma mark CelScaler
+CelScaler *CelObj::_scaler = nullptr;
+
+void CelScaler::activateScaleTables(const Ratio &scaleX, const Ratio &scaleY) {
+ const int16 screenWidth = g_sci->_gfxFrameout->getCurrentBuffer().screenWidth;
+ const int16 screenHeight = g_sci->_gfxFrameout->getCurrentBuffer().screenHeight;
+
+ for (int i = 0; i < ARRAYSIZE(_scaleTables); ++i) {
+ if (_scaleTables[i].scaleX == scaleX && _scaleTables[i].scaleY == scaleY) {
+ _activeIndex = i;
+ return;
+ }
+ }
+
+ int i = 1 - _activeIndex;
+ _activeIndex = i;
+ CelScalerTable &table = _scaleTables[i];
+
+ if (table.scaleX != scaleX) {
+ assert(screenWidth <= ARRAYSIZE(table.valuesX));
+ buildLookupTable(table.valuesX, scaleX, screenWidth);
+ table.scaleX = scaleX;
+ }
+
+ if (table.scaleY != scaleY) {
+ assert(screenHeight <= ARRAYSIZE(table.valuesY));
+ buildLookupTable(table.valuesY, scaleY, screenHeight);
+ table.scaleY = scaleY;
+ }
+}
+
+void CelScaler::buildLookupTable(int *table, const Ratio &ratio, const int size) {
+ int value = 0;
+ int remainder = 0;
+ int num = ratio.getNumerator();
+ for (int i = 0; i < size; ++i) {
+ *table++ = value;
+ remainder += ratio.getDenominator();
+ if (remainder >= num) {
+ value += remainder / num;
+ remainder %= num;
+ }
+ }
+}
+
+const CelScalerTable *CelScaler::getScalerTable(const Ratio &scaleX, const Ratio &scaleY) {
+ activateScaleTables(scaleX, scaleY);
+ return &_scaleTables[_activeIndex];
+}
+
+#pragma mark -
+#pragma mark CelObj
+
+void CelObj::init() {
+ CelObj::deinit();
+ _nextCacheId = 1;
+ _scaler = new CelScaler();
+ _cache = new CelCache;
+ _cache->resize(100);
+}
+
+void CelObj::deinit() {
+ delete _scaler;
+ _scaler = nullptr;
+ if (_cache != nullptr) {
+ for (CelCache::iterator it = _cache->begin(); it != _cache->end(); ++it) {
+ delete it->celObj;
+ }
+ }
+ delete _cache;
+ _cache = nullptr;
+}
+
+#pragma mark -
+#pragma mark CelObj - Scalers
+
+template<bool FLIP, typename READER>
+struct SCALER_NoScale {
+#ifndef NDEBUG
+ const byte *_rowEdge;
+#endif
+ const byte *_row;
+ READER _reader;
+ const int16 _lastIndex;
+ const int16 _sourceX;
+ const int16 _sourceY;
+
+ SCALER_NoScale(const CelObj &celObj, const int16 maxWidth, const Common::Point &scaledPosition) :
+ _reader(celObj, FLIP ? celObj._width : maxWidth),
+ _lastIndex(celObj._width - 1),
+ _sourceX(scaledPosition.x),
+ _sourceY(scaledPosition.y) {}
+
+ inline void setTarget(const int16 x, const int16 y) {
+ _row = _reader.getRow(y - _sourceY);
+
+ if (FLIP) {
+#ifndef NDEBUG
+ _rowEdge = _row - 1;
+#endif
+ _row += _lastIndex - (x - _sourceX);
+ assert(_row > _rowEdge);
+ } else {
+#ifndef NDEBUG
+ _rowEdge = _row + _lastIndex + 1;
+#endif
+ _row += x - _sourceX;
+ assert(_row < _rowEdge);
+ }
+ }
+
+ inline byte read() {
+ assert(_row != _rowEdge);
+
+ if (FLIP) {
+ return *_row--;
+ } else {
+ return *_row++;
+ }
+ }
+};
+
+template<bool FLIP, typename READER>
+struct SCALER_Scale {
+#ifndef NDEBUG
+ int16 _maxX;
+#endif
+ const byte *_row;
+ READER _reader;
+ int16 _x;
+ static int16 _valuesX[1024];
+ static int16 _valuesY[1024];
+
+ SCALER_Scale(const CelObj &celObj, const Common::Rect &targetRect, const Common::Point &scaledPosition, const Ratio scaleX, const Ratio scaleY) :
+#ifndef NDEBUG
+ _maxX(targetRect.right - 1),
+#endif
+ // The maximum width of the scaled object may not be as
+ // wide as the source data it requires if downscaling,
+ // so just always make the reader decompress an entire
+ // line of source data when scaling
+ _reader(celObj, celObj._width) {
+ // In order for scaling ratios to apply equally across objects that
+ // start at different positions on the screen, the pixels that are
+ // read from the source bitmap must all use the same pattern of
+ // division. In other words, cels must follow the same scaling pattern
+ // as if they were drawn starting at an even multiple of the scaling
+ // ratio, even if they were not.
+ //
+ // To get the correct source pixel when reading out through the scaler,
+ // the engine creates a lookup table for each axis that translates
+ // directly from target positions to the indexes of source pixels using
+ // the global cadence for the given scaling ratio.
+
+ const CelScalerTable *table = CelObj::_scaler->getScalerTable(scaleX, scaleY);
+
+ const int16 unscaledX = (scaledPosition.x / scaleX).toInt();
+ if (FLIP) {
+ int lastIndex = celObj._width - 1;
+ for (int16 x = targetRect.left; x < targetRect.right; ++x) {
+ _valuesX[x] = lastIndex - (table->valuesX[x] - unscaledX);
+ }
+ } else {
+ for (int16 x = targetRect.left; x < targetRect.right; ++x) {
+ _valuesX[x] = table->valuesX[x] - unscaledX;
+ }
+ }
+
+ const int16 unscaledY = (scaledPosition.y / scaleY).toInt();
+ for (int16 y = targetRect.top; y < targetRect.bottom; ++y) {
+ _valuesY[y] = table->valuesY[y] - unscaledY;
+ }
+ }
+
+ inline void setTarget(const int16 x, const int16 y) {
+ _row = _reader.getRow(_valuesY[y]);
+ _x = x;
+ assert(_x >= 0 && _x <= _maxX);
+ }
+
+ inline byte read() {
+ assert(_x >= 0 && _x <= _maxX);
+ return _row[_valuesX[_x++]];
+ }
+};
+
+template<bool FLIP, typename READER>
+int16 SCALER_Scale<FLIP, READER>::_valuesX[1024];
+template<bool FLIP, typename READER>
+int16 SCALER_Scale<FLIP, READER>::_valuesY[1024];
+
+#pragma mark -
+#pragma mark CelObj - Resource readers
+
+struct READER_Uncompressed {
+private:
+#ifndef NDEBUG
+ const int16 _sourceHeight;
+#endif
+ byte *_pixels;
+ const int16 _sourceWidth;
+
+public:
+ READER_Uncompressed(const CelObj &celObj, const int16) :
+#ifndef NDEBUG
+ _sourceHeight(celObj._height),
+#endif
+ _sourceWidth(celObj._width) {
+ byte *resource = celObj.getResPointer();
+ _pixels = resource + READ_SCI11ENDIAN_UINT32(resource + celObj._celHeaderOffset + 24);
+ }
+
+ inline const byte *getRow(const int16 y) const {
+ assert(y >= 0 && y < _sourceHeight);
+ return _pixels + y * _sourceWidth;
+ }
+};
+
+struct READER_Compressed {
+private:
+ byte *_resource;
+ byte _buffer[1024];
+ uint32 _controlOffset;
+ uint32 _dataOffset;
+ uint32 _uncompressedDataOffset;
+ int16 _y;
+ const int16 _sourceHeight;
+ const uint8 _transparentColor;
+ const int16 _maxWidth;
+
+public:
+ READER_Compressed(const CelObj &celObj, const int16 maxWidth) :
+ _resource(celObj.getResPointer()),
+ _y(-1),
+ _sourceHeight(celObj._height),
+ _transparentColor(celObj._transparentColor),
+ _maxWidth(maxWidth) {
+ assert(maxWidth <= celObj._width);
+
+ byte *celHeader = _resource + celObj._celHeaderOffset;
+ _dataOffset = READ_SCI11ENDIAN_UINT32(celHeader + 24);
+ _uncompressedDataOffset = READ_SCI11ENDIAN_UINT32(celHeader + 28);
+ _controlOffset = READ_SCI11ENDIAN_UINT32(celHeader + 32);
+ }
+
+ inline const byte *getRow(const int16 y) {
+ assert(y >= 0 && y < _sourceHeight);
+ if (y != _y) {
+ // compressed data segment for row
+ byte *row = _resource + _dataOffset + READ_SCI11ENDIAN_UINT32(_resource + _controlOffset + y * 4);
+
+ // uncompressed data segment for row
+ byte *literal = _resource + _uncompressedDataOffset + READ_SCI11ENDIAN_UINT32(_resource + _controlOffset + _sourceHeight * 4 + y * 4);
+
+ uint8 length;
+ for (int16 i = 0; i < _maxWidth; i += length) {
+ byte controlByte = *row++;
+ length = controlByte;
+
+ // Run-length encoded
+ if (controlByte & 0x80) {
+ length &= 0x3F;
+ assert(i + length < (int)sizeof(_buffer));
+
+ // Fill with skip color
+ if (controlByte & 0x40) {
+ memset(_buffer + i, _transparentColor, length);
+ // Next value is fill color
+ } else {
+ memset(_buffer + i, *literal, length);
+ ++literal;
+ }
+ // Uncompressed
+ } else {
+ assert(i + length < (int)sizeof(_buffer));
+ memcpy(_buffer + i, literal, length);
+ literal += length;
+ }
+ }
+ _y = y;
+ }
+
+ return _buffer;
+ }
+};
+
+#pragma mark -
+#pragma mark CelObj - Remappers
+
+struct MAPPER_NoMD {
+ inline void draw(byte *target, const byte pixel, const uint8 skipColor) const {
+ if (pixel != skipColor) {
+ *target = pixel;
+ }
+ }
+};
+struct MAPPER_NoMDNoSkip {
+ inline void draw(byte *target, const byte pixel, const uint8) const {
+ *target = pixel;
+ }
+};
+
+struct MAPPER_Map {
+ inline void draw(byte *target, const byte pixel, const uint8 skipColor) const {
+ if (pixel != skipColor) {
+ if (pixel < g_sci->_gfxRemap32->getStartColor()) {
+ *target = pixel;
+ } else {
+ if (g_sci->_gfxRemap32->remapEnabled(pixel))
+ *target = g_sci->_gfxRemap32->remapColor(pixel, *target);
+ }
+ }
+ }
+};
+
+void CelObj::draw(Buffer &target, const ScreenItem &screenItem, const Common::Rect &targetRect) const {
+ const Common::Point &scaledPosition = screenItem._scaledPosition;
+ const Ratio &scaleX = screenItem._ratioX;
+ const Ratio &scaleY = screenItem._ratioY;
+
+ if (_remap) {
+ // NOTE: In the original code this check was `g_Remap_numActiveRemaps && _remap`,
+ // but since we are already in a `_remap` branch, there is no reason to check it
+ // again
+ if (g_sci->_gfxRemap32->getRemapCount()) {
+ if (scaleX.isOne() && scaleY.isOne()) {
+ if (_compressionType == kCelCompressionNone) {
+ if (_drawMirrored) {
+ drawUncompHzFlipMap(target, targetRect, scaledPosition);
+ } else {
+ drawUncompNoFlipMap(target, targetRect, scaledPosition);
+ }
+ } else {
+ if (_drawMirrored) {
+ drawHzFlipMap(target, targetRect, scaledPosition);
+ } else {
+ drawNoFlipMap(target, targetRect, scaledPosition);
+ }
+ }
+ } else {
+ if (_compressionType == kCelCompressionNone) {
+ scaleDrawUncompMap(target, scaleX, scaleY, targetRect, scaledPosition);
+ } else {
+ scaleDrawMap(target, scaleX, scaleY, targetRect, scaledPosition);
+ }
+ }
+ } else {
+ if (scaleX.isOne() && scaleY.isOne()) {
+ if (_compressionType == kCelCompressionNone) {
+ if (_drawMirrored) {
+ drawUncompHzFlip(target, targetRect, scaledPosition);
+ } else {
+ drawUncompNoFlip(target, targetRect, scaledPosition);
+ }
+ } else {
+ if (_drawMirrored) {
+ drawHzFlip(target, targetRect, scaledPosition);
+ } else {
+ drawNoFlip(target, targetRect, scaledPosition);
+ }
+ }
+ } else {
+ if (_compressionType == kCelCompressionNone) {
+ scaleDrawUncomp(target, scaleX, scaleY, targetRect, scaledPosition);
+ } else {
+ scaleDraw(target, scaleX, scaleY, targetRect, scaledPosition);
+ }
+ }
+ }
+ } else {
+ if (scaleX.isOne() && scaleY.isOne()) {
+ if (_compressionType == kCelCompressionNone) {
+ if (_transparent) {
+ if (_drawMirrored) {
+ drawUncompHzFlipNoMD(target, targetRect, scaledPosition);
+ } else {
+ drawUncompNoFlipNoMD(target, targetRect, scaledPosition);
+ }
+ } else {
+ if (_drawMirrored) {
+ drawUncompHzFlipNoMDNoSkip(target, targetRect, scaledPosition);
+ } else {
+ drawUncompNoFlipNoMDNoSkip(target, targetRect, scaledPosition);
+ }
+ }
+ } else {
+ if (_drawMirrored) {
+ drawHzFlipNoMD(target, targetRect, scaledPosition);
+ } else {
+ drawNoFlipNoMD(target, targetRect, scaledPosition);
+ }
+ }
+ } else {
+ if (_compressionType == kCelCompressionNone) {
+ scaleDrawUncompNoMD(target, scaleX, scaleY, targetRect, scaledPosition);
+ } else {
+ scaleDrawNoMD(target, scaleX, scaleY, targetRect, scaledPosition);
+ }
+ }
+ }
+}
+
+void CelObj::draw(Buffer &target, const ScreenItem &screenItem, const Common::Rect &targetRect, bool mirrorX) {
+ _drawMirrored = mirrorX;
+ draw(target, screenItem, targetRect);
+}
+
+void CelObj::draw(Buffer &target, const Common::Rect &targetRect, const Common::Point &scaledPosition, const bool mirrorX) {
+ _drawMirrored = mirrorX;
+ Ratio square;
+ drawTo(target, targetRect, scaledPosition, square, square);
+}
+
+void CelObj::drawTo(Buffer &target, Common::Rect const &targetRect, Common::Point const &scaledPosition, Ratio const &scaleX, Ratio const &scaleY) const {
+ if (_remap) {
+ if (scaleX.isOne() && scaleY.isOne()) {
+ if (_compressionType == kCelCompressionNone) {
+ if (_drawMirrored) {
+ drawUncompHzFlipMap(target, targetRect, scaledPosition);
+ } else {
+ drawUncompNoFlipMap(target, targetRect, scaledPosition);
+ }
+ } else {
+ if (_drawMirrored) {
+ drawHzFlipMap(target, targetRect, scaledPosition);
+ } else {
+ drawNoFlipMap(target, targetRect, scaledPosition);
+ }
+ }
+ } else {
+ if (_compressionType == kCelCompressionNone) {
+ scaleDrawUncompMap(target, scaleX, scaleY, targetRect, scaledPosition);
+ } else {
+ scaleDrawMap(target, scaleX, scaleY, targetRect, scaledPosition);
+ }
+ }
+ } else {
+ if (scaleX.isOne() && scaleY.isOne()) {
+ if (_compressionType == kCelCompressionNone) {
+ if (_drawMirrored) {
+ drawUncompHzFlipNoMD(target, targetRect, scaledPosition);
+ } else {
+ drawUncompNoFlipNoMD(target, targetRect, scaledPosition);
+ }
+ } else {
+ if (_drawMirrored) {
+ drawHzFlipNoMD(target, targetRect, scaledPosition);
+ } else {
+ drawNoFlipNoMD(target, targetRect, scaledPosition);
+ }
+ }
+ } else {
+ if (_compressionType == kCelCompressionNone) {
+ scaleDrawUncompNoMD(target, scaleX, scaleY, targetRect, scaledPosition);
+ } else {
+ scaleDrawNoMD(target, scaleX, scaleY, targetRect, scaledPosition);
+ }
+ }
+ }
+}
+
+uint8 CelObj::readPixel(uint16 x, const uint16 y, bool mirrorX) const {
+ if (mirrorX) {
+ x = _width - x - 1;
+ }
+
+ if (_compressionType == kCelCompressionNone) {
+ READER_Uncompressed reader(*this, x + 1);
+ return reader.getRow(y)[x];
+ } else {
+ READER_Compressed reader(*this, x + 1);
+ return reader.getRow(y)[x];
+ }
+}
+
+void CelObj::submitPalette() const {
+ if (_hunkPaletteOffset) {
+ Palette palette;
+
+ byte *res = getResPointer();
+ // NOTE: In SCI engine this uses HunkPalette::Init.
+ // TODO: Use a better size value
+ g_sci->_gfxPalette32->createFromData(res + _hunkPaletteOffset, 999999, &palette);
+ g_sci->_gfxPalette32->submit(palette);
+ }
+}
+
+#pragma mark -
+#pragma mark CelObj - Caching
+int CelObj::_nextCacheId = 1;
+CelCache *CelObj::_cache = nullptr;
+
+int CelObj::searchCache(const CelInfo32 &celInfo, int *nextInsertIndex) const {
+ int oldestId = _nextCacheId + 1;
+ int oldestIndex = -1;
+
+ for (int i = 0, len = _cache->size(); i < len; ++i) {
+ CelCacheEntry &entry = (*_cache)[i];
+
+ if (entry.celObj != nullptr) {
+ if (entry.celObj->_info == celInfo) {
+ entry.id = ++_nextCacheId;
+ return i;
+ }
+
+ if (oldestId > entry.id) {
+ oldestId = entry.id;
+ oldestIndex = i;
+ }
+ } else if (oldestIndex == -1) {
+ oldestIndex = i;
+ }
+ }
+
+ // NOTE: Unlike the original SCI engine code, the out-param
+ // here is only updated if there was not a cache hit.
+ *nextInsertIndex = oldestIndex;
+ return -1;
+}
+
+void CelObj::putCopyInCache(const int cacheIndex) const {
+ if (cacheIndex == -1) {
+ error("Invalid cache index");
+ }
+
+ CelCacheEntry &entry = (*_cache)[cacheIndex];
+
+ if (entry.celObj != nullptr) {
+ delete entry.celObj;
+ }
+
+ entry.celObj = duplicate();
+ entry.id = ++_nextCacheId;
+}
+
+#pragma mark -
+#pragma mark CelObj - Drawing
+
+template<typename MAPPER, typename SCALER>
+struct RENDERER {
+ MAPPER &_mapper;
+ SCALER &_scaler;
+ const uint8 _skipColor;
+
+ RENDERER(MAPPER &mapper, SCALER &scaler, const uint8 skipColor) :
+ _mapper(mapper),
+ _scaler(scaler),
+ _skipColor(skipColor) {}
+
+ inline void draw(Buffer &target, const Common::Rect &targetRect, const Common::Point &scaledPosition) const {
+ byte *targetPixel = (byte *)target.getPixels() + target.screenWidth * targetRect.top + targetRect.left;
+
+ const int16 skipStride = target.screenWidth - targetRect.width();
+ const int16 targetWidth = targetRect.width();
+ const int16 targetHeight = targetRect.height();
+ for (int16 y = 0; y < targetHeight; ++y) {
+ _scaler.setTarget(targetRect.left, targetRect.top + y);
+
+ for (int16 x = 0; x < targetWidth; ++x) {
+ _mapper.draw(targetPixel++, _scaler.read(), _skipColor);
+ }
+
+ targetPixel += skipStride;
+ }
+ }
+};
+
+template<typename MAPPER, typename SCALER>
+void CelObj::render(Buffer &target, const Common::Rect &targetRect, const Common::Point &scaledPosition) const {
+
+ MAPPER mapper;
+ SCALER scaler(*this, targetRect.left - scaledPosition.x + targetRect.width(), scaledPosition);
+ RENDERER<MAPPER, SCALER> renderer(mapper, scaler, _transparentColor);
+ renderer.draw(target, targetRect, scaledPosition);
+}
+
+template<typename MAPPER, typename SCALER>
+void CelObj::render(Buffer &target, const Common::Rect &targetRect, const Common::Point &scaledPosition, const Ratio &scaleX, const Ratio &scaleY) const {
+
+ MAPPER mapper;
+ SCALER scaler(*this, targetRect, scaledPosition, scaleX, scaleY);
+ RENDERER<MAPPER, SCALER> renderer(mapper, scaler, _transparentColor);
+ renderer.draw(target, targetRect, scaledPosition);
+}
+
+void dummyFill(Buffer &target, const Common::Rect &targetRect) {
+ target.fillRect(targetRect, 250);
+}
+
+void CelObj::drawHzFlip(Buffer &target, const Common::Rect &targetRect, const Common::Point &scaledPosition) const {
+ debug("drawHzFlip");
+ dummyFill(target, targetRect);
+}
+
+void CelObj::drawNoFlip(Buffer &target, const Common::Rect &targetRect, const Common::Point &scaledPosition) const {
+ debug("drawNoFlip");
+ dummyFill(target, targetRect);
+}
+
+void CelObj::drawUncompNoFlip(Buffer &target, const Common::Rect &targetRect, const Common::Point &scaledPosition) const {
+ debug("drawUncompNoFlip");
+ dummyFill(target, targetRect);
+}
+
+void CelObj::drawUncompHzFlip(Buffer &target, const Common::Rect &targetRect, const Common::Point &scaledPosition) const {
+ debug("drawUncompHzFlip");
+ dummyFill(target, targetRect);
+}
+
+void CelObj::scaleDraw(Buffer &target, const Ratio &scaleX, const Ratio &scaleY, const Common::Rect &targetRect, const Common::Point &scaledPosition) const {
+ debug("scaleDraw");
+ dummyFill(target, targetRect);
+}
+
+void CelObj::scaleDrawUncomp(Buffer &target, const Ratio &scaleX, const Ratio &scaleY, const Common::Rect &targetRect, const Common::Point &scaledPosition) const {
+ debug("scaleDrawUncomp");
+ dummyFill(target, targetRect);
+}
+
+void CelObj::drawHzFlipMap(Buffer &target, const Common::Rect &targetRect, const Common::Point &scaledPosition) const {
+ render<MAPPER_Map, SCALER_NoScale<true, READER_Compressed> >(target, targetRect, scaledPosition);
+}
+
+void CelObj::drawNoFlipMap(Buffer &target, const Common::Rect &targetRect, const Common::Point &scaledPosition) const {
+ render<MAPPER_Map, SCALER_NoScale<false, READER_Compressed> >(target, targetRect, scaledPosition);
+}
+
+void CelObj::drawUncompNoFlipMap(Buffer &target, const Common::Rect &targetRect, const Common::Point &scaledPosition) const {
+ render<MAPPER_Map, SCALER_NoScale<false, READER_Uncompressed> >(target, targetRect, scaledPosition);
+}
+
+void CelObj::drawUncompHzFlipMap(Buffer &target, const Common::Rect &targetRect, const Common::Point &scaledPosition) const {
+ render<MAPPER_Map, SCALER_NoScale<true, READER_Uncompressed> >(target, targetRect, scaledPosition);
+}
+
+void CelObj::scaleDrawMap(Buffer &target, const Ratio &scaleX, const Ratio &scaleY, const Common::Rect &targetRect, const Common::Point &scaledPosition) const {
+ if (_drawMirrored)
+ render<MAPPER_Map, SCALER_Scale<true, READER_Compressed> >(target, targetRect, scaledPosition, scaleX, scaleY);
+ else
+ render<MAPPER_Map, SCALER_Scale<false, READER_Compressed> >(target, targetRect, scaledPosition, scaleX, scaleY);
+}
+
+void CelObj::scaleDrawUncompMap(Buffer &target, const Ratio &scaleX, const Ratio &scaleY, const Common::Rect &targetRect, const Common::Point &scaledPosition) const {
+ if (_drawMirrored)
+ render<MAPPER_Map, SCALER_Scale<true, READER_Uncompressed> >(target, targetRect, scaledPosition, scaleX, scaleY);
+ else
+ render<MAPPER_Map, SCALER_Scale<false, READER_Uncompressed> >(target, targetRect, scaledPosition, scaleX, scaleY);
+}
+
+void CelObj::drawNoFlipNoMD(Buffer &target, const Common::Rect &targetRect, const Common::Point &scaledPosition) const {
+ render<MAPPER_NoMD, SCALER_NoScale<false, READER_Compressed> >(target, targetRect, scaledPosition);
+}
+
+void CelObj::drawHzFlipNoMD(Buffer &target, const Common::Rect &targetRect, const Common::Point &scaledPosition) const {
+ render<MAPPER_NoMD, SCALER_NoScale<true, READER_Compressed> >(target, targetRect, scaledPosition);
+}
+
+void CelObj::drawUncompNoFlipNoMD(Buffer &target, const Common::Rect &targetRect, const Common::Point &scaledPosition) const {
+ render<MAPPER_NoMD, SCALER_NoScale<false, READER_Uncompressed> >(target, targetRect, scaledPosition);
+}
+
+void CelObj::drawUncompNoFlipNoMDNoSkip(Buffer &target, const Common::Rect &targetRect, const Common::Point &scaledPosition) const {
+ render<MAPPER_NoMDNoSkip, SCALER_NoScale<false, READER_Uncompressed> >(target, targetRect, scaledPosition);
+}
+
+void CelObj::drawUncompHzFlipNoMD(Buffer &target, const Common::Rect &targetRect, const Common::Point &scaledPosition) const {
+ render<MAPPER_NoMD, SCALER_NoScale<true, READER_Uncompressed> >(target, targetRect, scaledPosition);
+}
+
+void CelObj::drawUncompHzFlipNoMDNoSkip(Buffer &target, const Common::Rect &targetRect, const Common::Point &scaledPosition) const {
+ render<MAPPER_NoMDNoSkip, SCALER_NoScale<true, READER_Uncompressed> >(target, targetRect, scaledPosition);
+}
+
+void CelObj::scaleDrawNoMD(Buffer &target, const Ratio &scaleX, const Ratio &scaleY, const Common::Rect &targetRect, const Common::Point &scaledPosition) const {
+ if (_drawMirrored)
+ render<MAPPER_NoMD, SCALER_Scale<true, READER_Compressed> >(target, targetRect, scaledPosition, scaleX, scaleY);
+ else
+ render<MAPPER_NoMD, SCALER_Scale<false, READER_Compressed> >(target, targetRect, scaledPosition, scaleX, scaleY);
+}
+
+void CelObj::scaleDrawUncompNoMD(Buffer &target, const Ratio &scaleX, const Ratio &scaleY, const Common::Rect &targetRect, const Common::Point &scaledPosition) const {
+ if (_drawMirrored)
+ render<MAPPER_NoMD, SCALER_Scale<true, READER_Uncompressed> >(target, targetRect, scaledPosition, scaleX, scaleY);
+ else
+ render<MAPPER_NoMD, SCALER_Scale<false, READER_Uncompressed> >(target, targetRect, scaledPosition, scaleX, scaleY);
+}
+
+#pragma mark -
+#pragma mark CelObjView
+CelObjView::CelObjView(const GuiResourceId viewId, const int16 loopNo, const int16 celNo) {
+ _info.type = kCelTypeView;
+ _info.resourceId = viewId;
+ _info.loopNo = loopNo;
+ _info.celNo = celNo;
+ _mirrorX = false;
+ _compressionType = kCelCompressionInvalid;
+ _transparent = true;
+
+ int cacheInsertIndex;
+ int cacheIndex = searchCache(_info, &cacheInsertIndex);
+ if (cacheIndex != -1) {
+ CelCacheEntry &entry = (*_cache)[cacheIndex];
+ *this = *dynamic_cast<CelObjView *>(entry.celObj);
+ entry.id = ++_nextCacheId;
+ return;
+ }
+
+ // TODO: The next code should be moved to a common file that
+ // generates view resource metadata for both SCI16 and SCI32
+ // implementations
+
+ Resource *resource = g_sci->getResMan()->findResource(ResourceId(kResourceTypeView, viewId), false);
+
+ // NOTE: SCI2.1/SQ6 just silently returns here.
+ if (!resource) {
+ warning("View resource %d not loaded", viewId);
+ return;
+ }
+
+ byte *data = resource->data;
+
+ _scaledWidth = READ_SCI11ENDIAN_UINT16(data + 14);
+ _scaledHeight = READ_SCI11ENDIAN_UINT16(data + 16);
+
+ if (_scaledWidth == 0 || _scaledHeight == 0) {
+ byte sizeFlag = data[5];
+ if (sizeFlag == 0) {
+ _scaledWidth = 320;
+ _scaledHeight = 200;
+ } else if (sizeFlag == 1) {
+ _scaledWidth = 640;
+ _scaledHeight = 480;
+ } else if (sizeFlag == 2) {
+ _scaledWidth = 640;
+ _scaledHeight = 400;
+ }
+ }
+
+ uint16 loopCount = data[2];
+ if (_info.loopNo >= loopCount) {
+ _info.loopNo = loopCount - 1;
+ }
+
+ // NOTE: This is the actual check, in the actual location,
+ // from SCI engine.
+ if (loopNo < 0) {
+ error("Loop is less than 0!");
+ }
+
+ const uint16 viewHeaderSize = READ_SCI11ENDIAN_UINT16(data);
+ const uint8 loopHeaderSize = data[12];
+ const uint8 viewHeaderFieldSize = 2;
+
+ byte *loopHeader = data + viewHeaderFieldSize + viewHeaderSize + (loopHeaderSize * _info.loopNo);
+
+ if ((int8)loopHeader[0] != -1) {
+ if (loopHeader[1] == 1) {
+ _mirrorX = true;
+ }
+
+ loopHeader = data + viewHeaderFieldSize + viewHeaderSize + (loopHeaderSize * (int8)loopHeader[0]);
+ }
+
+ uint8 celCount = loopHeader[2];
+ if (_info.celNo >= celCount) {
+ _info.celNo = celCount - 1;
+ }
+
+ _hunkPaletteOffset = READ_SCI11ENDIAN_UINT32(data + 8);
+ _celHeaderOffset = READ_SCI11ENDIAN_UINT32(loopHeader + 12) + (data[13] * _info.celNo);
+
+ byte *celHeader = data + _celHeaderOffset;
+
+ _width = READ_SCI11ENDIAN_UINT16(celHeader);
+ _height = READ_SCI11ENDIAN_UINT16(celHeader + 2);
+ _displace.x = _width / 2 - (int16)READ_SCI11ENDIAN_UINT16(celHeader + 4);
+ _displace.y = _height - (int16)READ_SCI11ENDIAN_UINT16(celHeader + 6) - 1;
+ _transparentColor = celHeader[8];
+ _compressionType = (CelCompressionType)celHeader[9];
+
+ if (_compressionType != kCelCompressionNone && _compressionType != kCelCompressionRLE) {
+ error("Compression type not supported - V: %d L: %d C: %d", _info.resourceId, _info.loopNo, _info.celNo);
+ }
+
+ if (celHeader[10] & 128) {
+ // NOTE: This is correct according to SCI2.1/SQ6/DOS;
+ // the engine re-reads the byte value as a word value
+ uint16 flags = READ_SCI11ENDIAN_UINT16(celHeader + 10);
+ _transparent = flags & 1 ? true : false;
+ _remap = flags & 2 ? true : false;
+ } else if (_compressionType == kCelCompressionNone) {
+ _remap = analyzeUncompressedForRemap();
+ } else {
+ _remap = analyzeForRemap();
+ }
+
+ putCopyInCache(cacheInsertIndex);
+}
+
+bool CelObjView::analyzeUncompressedForRemap() const {
+ byte *pixels = getResPointer() + READ_SCI11ENDIAN_UINT32(getResPointer() + _celHeaderOffset + 24);
+ for (int i = 0; i < _width * _height; ++i) {
+ byte pixel = pixels[i];
+ if (pixel >= g_sci->_gfxRemap32->getStartColor() && pixel <= g_sci->_gfxRemap32->getEndColor() && pixel != _transparentColor) {
+ return true;
+ }
+ }
+ return false;
+}
+
+bool CelObjView::analyzeForRemap() const {
+ READER_Compressed reader(*this, _width);
+ for (int y = 0; y < _height; y++) {
+ const byte *curRow = reader.getRow(y);
+ for (int x = 0; x < _width; x++) {
+ byte pixel = curRow[x];
+ if (pixel >= g_sci->_gfxRemap32->getStartColor() && pixel <= g_sci->_gfxRemap32->getEndColor() && pixel != _transparentColor) {
+ return true;
+ }
+ }
+ }
+ return false;
+}
+
+void CelObjView::draw(Buffer &target, const Common::Rect &targetRect, const Common::Point &scaledPosition, bool mirrorX, const Ratio &scaleX, const Ratio &scaleY) {
+ _drawMirrored = mirrorX;
+ drawTo(target, targetRect, scaledPosition, scaleX, scaleY);
+}
+
+CelObjView *CelObjView::duplicate() const {
+ return new CelObjView(*this);
+}
+
+byte *CelObjView::getResPointer() const {
+ return g_sci->getResMan()->findResource(ResourceId(kResourceTypeView, _info.resourceId), false)->data;
+}
+
+#pragma mark -
+#pragma mark CelObjPic
+CelObjPic::CelObjPic(const GuiResourceId picId, const int16 celNo) {
+ _info.type = kCelTypePic;
+ _info.resourceId = picId;
+ _info.loopNo = 0;
+ _info.celNo = celNo;
+ _mirrorX = false;
+ _compressionType = kCelCompressionInvalid;
+ _transparent = true;
+ _remap = false;
+
+ int cacheInsertIndex;
+ int cacheIndex = searchCache(_info, &cacheInsertIndex);
+ if (cacheIndex != -1) {
+ CelCacheEntry &entry = (*_cache)[cacheIndex];
+ *this = *dynamic_cast<CelObjPic *>(entry.celObj);
+ entry.id = ++_nextCacheId;
+ return;
+ }
+
+ Resource *resource = g_sci->getResMan()->findResource(ResourceId(kResourceTypePic, picId), false);
+
+ // NOTE: SCI2.1/SQ6 just silently returns here.
+ if (!resource) {
+ warning("Pic resource %d not loaded", picId);
+ return;
+ }
+
+ byte *data = resource->data;
+
+ _celCount = data[2];
+
+ if (_info.celNo >= _celCount) {
+ error("Cel number %d greater than cel count %d", _info.celNo, _celCount);
+ }
+
+ _celHeaderOffset = READ_SCI11ENDIAN_UINT16(data) + (READ_SCI11ENDIAN_UINT16(data + 4) * _info.celNo);
+ _hunkPaletteOffset = READ_SCI11ENDIAN_UINT32(data + 6);
+
+ byte *celHeader = data + _celHeaderOffset;
+
+ _width = READ_SCI11ENDIAN_UINT16(celHeader);
+ _height = READ_SCI11ENDIAN_UINT16(celHeader + 2);
+ _displace.x = (int16)READ_SCI11ENDIAN_UINT16(celHeader + 4);
+ _displace.y = (int16)READ_SCI11ENDIAN_UINT16(celHeader + 6);
+ _transparentColor = celHeader[8];
+ _compressionType = (CelCompressionType)celHeader[9];
+ _priority = READ_SCI11ENDIAN_UINT16(celHeader + 36);
+ _relativePosition.x = (int16)READ_SCI11ENDIAN_UINT16(celHeader + 38);
+ _relativePosition.y = (int16)READ_SCI11ENDIAN_UINT16(celHeader + 40);
+
+ uint16 sizeFlag1 = READ_SCI11ENDIAN_UINT16(data + 10);
+ uint16 sizeFlag2 = READ_SCI11ENDIAN_UINT16(data + 12);
+
+ if (sizeFlag2) {
+ _scaledWidth = sizeFlag1;
+ _scaledHeight = sizeFlag2;
+ } else if (sizeFlag1 == 0) {
+ _scaledWidth = 320;
+ _scaledHeight = 200;
+ } else if (sizeFlag1 == 1) {
+ _scaledWidth = 640;
+ _scaledHeight = 480;
+ } else if (sizeFlag1 == 2) {
+ _scaledWidth = 640;
+ _scaledHeight = 400;
+ }
+
+ if (celHeader[10] & 128) {
+ // NOTE: This is correct according to SCI2.1/SQ6/DOS;
+ // the engine re-reads the byte value as a word value
+ uint16 flags = READ_SCI11ENDIAN_UINT16(celHeader + 10);
+ _transparent = flags & 1 ? true : false;
+ _remap = flags & 2 ? true : false;
+ } else {
+ _transparent = _compressionType != kCelCompressionNone ? true : analyzeUncompressedForSkip();
+
+ if (_compressionType != kCelCompressionNone && _compressionType != kCelCompressionRLE) {
+ error("Compression type not supported - P: %d C: %d", picId, celNo);
+ }
+ }
+
+ putCopyInCache(cacheInsertIndex);
+}
+
+bool CelObjPic::analyzeUncompressedForSkip() const {
+ byte *resource = getResPointer();
+ byte *pixels = resource + READ_SCI11ENDIAN_UINT32(resource + _celHeaderOffset + 24);
+ for (int i = 0; i < _width * _height; ++i) {
+ uint8 pixel = pixels[i];
+ if (pixel == _transparentColor) {
+ return true;
+ }
+ }
+
+ return false;
+}
+
+void CelObjPic::draw(Buffer &target, const Common::Rect &targetRect, const Common::Point &scaledPosition, const bool mirrorX) {
+ Ratio square;
+ _drawMirrored = mirrorX;
+ drawTo(target, targetRect, scaledPosition, square, square);
+}
+
+CelObjPic *CelObjPic::duplicate() const {
+ return new CelObjPic(*this);
+}
+
+byte *CelObjPic::getResPointer() const {
+ return g_sci->getResMan()->findResource(ResourceId(kResourceTypePic, _info.resourceId), false)->data;
+}
+
+#pragma mark -
+#pragma mark CelObjMem
+CelObjMem::CelObjMem(const reg_t bitmapObject) {
+ _info.type = kCelTypeMem;
+ _info.bitmap = bitmapObject;
+ _mirrorX = false;
+ _compressionType = kCelCompressionNone;
+ _celHeaderOffset = 0;
+ _transparent = true;
+
+ BitmapResource bitmap(bitmapObject);
+ _width = bitmap.getWidth();
+ _height = bitmap.getHeight();
+ _displace = bitmap.getDisplace();
+ _transparentColor = bitmap.getSkipColor();
+ _scaledWidth = bitmap.getScaledWidth();
+ _scaledHeight = bitmap.getScaledHeight();
+ _hunkPaletteOffset = bitmap.getHunkPaletteOffset();
+ _remap = bitmap.getRemap();
+}
+
+CelObjMem *CelObjMem::duplicate() const {
+ return new CelObjMem(*this);
+}
+
+byte *CelObjMem::getResPointer() const {
+ return g_sci->getEngineState()->_segMan->getHunkPointer(_info.bitmap);
+}
+
+#pragma mark -
+#pragma mark CelObjColor
+CelObjColor::CelObjColor(const uint8 color, const int16 width, const int16 height) {
+ _info.type = kCelTypeColor;
+ _info.color = color;
+ _displace.x = 0;
+ _displace.y = 0;
+ _scaledWidth = g_sci->_gfxFrameout->getCurrentBuffer().scriptWidth;
+ _scaledHeight = g_sci->_gfxFrameout->getCurrentBuffer().scriptHeight;
+ _hunkPaletteOffset = 0;
+ _mirrorX = false;
+ _remap = false;
+ _width = width;
+ _height = height;
+}
+
+void CelObjColor::draw(Buffer &target, const ScreenItem &screenItem, const Common::Rect &targetRect, const bool mirrorX) {
+ // TODO: The original engine sets this flag but why? One cannot
+ // draw a solid color mirrored.
+ _drawMirrored = mirrorX;
+ draw(target, targetRect);
+}
+void CelObjColor::draw(Buffer &target, const Common::Rect &targetRect, const Common::Point &scaledPosition, bool mirrorX) {
+ error("Unsupported method");
+}
+void CelObjColor::draw(Buffer &target, const Common::Rect &targetRect) const {
+ target.fillRect(targetRect, _info.color);
+}
+
+CelObjColor *CelObjColor::duplicate() const {
+ return new CelObjColor(*this);
+}
+
+byte *CelObjColor::getResPointer() const {
+ error("Unsupported method");
+}
+} // End of namespace Sci
diff --git a/engines/sci/graphics/celobj32.h b/engines/sci/graphics/celobj32.h
new file mode 100644
index 0000000000..0bb4b03ae2
--- /dev/null
+++ b/engines/sci/graphics/celobj32.h
@@ -0,0 +1,581 @@
+/* ScummVM - Graphic Adventure Engine
+ *
+ * ScummVM is the legal property of its developers, whose names
+ * are too numerous to list here. Please refer to the COPYRIGHT
+ * file distributed with this source distribution.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ *
+ */
+
+#ifndef SCI_GRAPHICS_CELOBJ32_H
+#define SCI_GRAPHICS_CELOBJ32_H
+
+#include "common/rational.h"
+#include "common/rect.h"
+#include "sci/resource.h"
+#include "sci/engine/vm_types.h"
+
+namespace Sci {
+typedef Common::Rational Ratio;
+
+enum CelType {
+ kCelTypeView = 0,
+ kCelTypePic = 1,
+ kCelTypeMem = 2,
+ kCelTypeColor = 3
+};
+
+enum CelCompressionType {
+ kCelCompressionNone = 0,
+ kCelCompressionRLE = 138,
+ kCelCompressionInvalid = 1000
+};
+
+/**
+ * A CelInfo32 object describes the basic properties of a
+ * cel object.
+ */
+struct CelInfo32 {
+ /**
+ * The type of the cel object.
+ */
+ CelType type;
+
+ /**
+ * For cel objects that draw from resources, the ID of
+ * the resource to load.
+ */
+ GuiResourceId resourceId;
+
+ /**
+ * For CelObjView, the loop number to draw from the
+ * view resource.
+ */
+ int16 loopNo;
+
+ /**
+ * For CelObjView and CelObjPic, the cel number to draw
+ * from the view or pic resource.
+ */
+ int16 celNo;
+
+ /**
+ * For CelObjMem, a segment register pointing to a heap
+ * resource containing headered bitmap data.
+ */
+ reg_t bitmap;
+
+ /**
+ * For CelObjColor, the fill color.
+ */
+ uint8 color;
+
+ // NOTE: In at least SCI2.1/SQ6, color is left
+ // uninitialised.
+ CelInfo32() :
+ type(kCelTypeMem),
+ resourceId(0),
+ loopNo(0),
+ celNo(0),
+ bitmap(NULL_REG) {}
+
+ // NOTE: This is the equivalence criteria used by
+ // CelObj::searchCache in at least SCI2.1/SQ6. Notably,
+ // it does not check the color field.
+ inline bool operator==(const CelInfo32 &other) {
+ return (
+ type == other.type &&
+ resourceId == other.resourceId &&
+ loopNo == other.loopNo &&
+ celNo == other.celNo &&
+ bitmap == other.bitmap
+ );
+ }
+
+ inline bool operator!=(const CelInfo32 &other) {
+ return !(*this == other);
+ }
+};
+
+class CelObj;
+struct CelCacheEntry {
+ /**
+ * A monotonically increasing cache ID used to identify
+ * the least recently used item in the cache for
+ * replacement.
+ */
+ int id;
+ CelObj *celObj;
+ CelCacheEntry() : id(0), celObj(nullptr) {}
+};
+
+typedef Common::Array<CelCacheEntry> CelCache;
+
+#pragma mark -
+#pragma mark CelScaler
+
+struct CelScalerTable {
+ /**
+ * A lookup table of indexes that should be used to find
+ * the correct column to read from the source bitmap
+ * when drawing a scaled version of the source bitmap.
+ */
+ int valuesX[1024];
+
+ /**
+ * The ratio used to generate the x-values.
+ */
+ Ratio scaleX;
+
+ /**
+ * A lookup table of indexes that should be used to find
+ * the correct row to read from a source bitmap when
+ * drawing a scaled version of the source bitmap.
+ */
+ int valuesY[1024];
+
+ /**
+ * The ratio used to generate the y-values.
+ */
+ Ratio scaleY;
+};
+
+class CelScaler {
+ /**
+ * Cached scale tables.
+ */
+ CelScalerTable _scaleTables[2];
+
+ /**
+ * The index of the most recently used scale table.
+ */
+ int _activeIndex;
+
+ /**
+ * Activates a scale table for the given X and Y ratios.
+ * If there is no table that matches the given ratios,
+ * the least most recently used table will be replaced
+ * and activated.
+ */
+ void activateScaleTables(const Ratio &scaleX, const Ratio &scaleY);
+
+ /**
+ * Builds a pixel lookup table in `table` for the given
+ * ratio. The table will be filled up to the specified
+ * size, which should be large enough to draw across the
+ * entire target buffer.
+ */
+ void buildLookupTable(int *table, const Ratio &ratio, const int size);
+
+public:
+ CelScaler() :
+ _scaleTables(),
+ _activeIndex(0) {
+ CelScalerTable &table = _scaleTables[0];
+ table.scaleX = Ratio();
+ table.scaleY = Ratio();
+ for (int i = 0; i < ARRAYSIZE(table.valuesX); ++i) {
+ table.valuesX[i] = i;
+ table.valuesY[i] = i;
+ }
+ for (int i = 1; i < ARRAYSIZE(_scaleTables); ++i) {
+ _scaleTables[i] = _scaleTables[0];
+ }
+ }
+
+ /**
+ * Retrieves scaler tables for the given X and Y ratios.
+ */
+ const CelScalerTable *getScalerTable(const Ratio &scaleX, const Ratio &scaleY);
+};
+
+#pragma mark -
+#pragma mark CelObj
+
+class ScreenItem;
+/**
+ * A cel object is the lowest-level rendering primitive in
+ * the SCI engine and draws itself directly to a target
+ * pixel buffer.
+ */
+class CelObj {
+protected:
+ /**
+ * When true, this cel will be horizontally mirrored
+ * when it is drawn. This is an internal flag that is
+ * set by draw methods based on the combination of the
+ * cel's `_mirrorX` property and the owner screen item's
+ * `_mirrorX` property.
+ */
+ bool _drawMirrored;
+
+public:
+ static CelScaler *_scaler;
+
+ /**
+ * The basic identifying information for this cel. This
+ * information effectively acts as a composite key for
+ * a cel object, and any cel object can be recreated
+ * from this data alone.
+ */
+ CelInfo32 _info;
+
+ /**
+ * The offset to the cel header for this cel within the
+ * raw resource data.
+ */
+ uint32 _celHeaderOffset;
+
+ /**
+ * The offset to the embedded palette for this cel
+ * within the raw resource data.
+ */
+ uint32 _hunkPaletteOffset;
+
+ /**
+ * The natural dimensions of the cel.
+ */
+ uint16 _width, _height;
+
+ /**
+ * TODO: Documentation
+ */
+ Common::Point _displace;
+
+ /**
+ * The dimensions of the original coordinate system for
+ * the cel. Used to scale cels from their native size
+ * to the correct size on screen.
+ *
+ * @note This is set to scriptWidth/Height for
+ * CelObjColor. For other cel objects, the value comes
+ * from the raw resource data. For text bitmaps, this is
+ * the width/height of the coordinate system used to
+ * generate the text, which also defaults to
+ * scriptWidth/Height but seems to typically be changed
+ * to more closely match the native screen resolution.
+ */
+ uint16 _scaledWidth, _scaledHeight;
+
+ /**
+ * The skip (transparent) color for the cel. When
+ * compositing, any pixels matching this color will not
+ * be copied to the buffer.
+ */
+ uint8 _transparentColor;
+
+ /**
+ * Whether or not this cel has any transparent regions.
+ * This is used for optimised drawing of non-transparent
+ * cels.
+ */
+ bool _transparent; // TODO: probably "skip"?
+
+ /**
+ * The compression type for the pixel data for this cel.
+ */
+ CelCompressionType _compressionType;
+
+ /**
+ * Whether or not this cel should be palette-remapped?
+ */
+ bool _remap;
+
+ /**
+ * If true, the cel contains pre-mirrored picture data.
+ * This value comes directly from the resource data and
+ * is XORed with the `_mirrorX` property of the owner
+ * screen item when rendering.
+ */
+ bool _mirrorX;
+
+ /**
+ * Initialises static CelObj members.
+ */
+ static void init();
+
+ /**
+ * Frees static CelObj members.
+ */
+ static void deinit();
+
+ virtual ~CelObj() {};
+
+ /**
+ * Draws the cel to the target buffer using the priority
+ * and positioning information from the given screen
+ * item. The mirroring of the cel will be unchanged from
+ * any previous call to draw.
+ */
+ void draw(Buffer &target, const ScreenItem &screenItem, const Common::Rect &targetRect) const;
+
+ /**
+ * Draws the cel to the target buffer using the priority
+ * and positioning information from the given screen
+ * item and the given mirror flag.
+ *
+ * @note In SCI engine, this function was a virtual
+ * function, but CelObjView, CelObjPic, and CelObjMem
+ * all used the same function and the compiler
+ * deduplicated the copies; we deduplicate the source by
+ * putting the implementation on CelObj instead of
+ * copying it to 3/4 of the subclasses.
+ */
+ virtual void draw(Buffer &target, const ScreenItem &screenItem, const Common::Rect &targetRect, const bool mirrorX);
+
+ /**
+ * Draws the cel to the target buffer using the
+ * positioning and mirroring information from the
+ * provided arguments.
+ *
+ * @note In SCI engine, this function was a virtual
+ * function, but CelObjView, CelObjPic, and CelObjMem
+ * all used the same function and the compiler
+ * deduplicated the copies; we deduplicate the source by
+ * putting the implementation on CelObj instead of
+ * copying it to 3/4 of the subclasses.
+ */
+ virtual void draw(Buffer &target, const Common::Rect &targetRect, const Common::Point &scaledPosition, const bool mirrorX);
+
+ /**
+ * Draws the cel to the target buffer using the given
+ * position and scaling parameters. The mirroring of the
+ * cel will be unchanged from any previous call to draw.
+ */
+ void drawTo(Buffer &target, const Common::Rect &targetRect, const Common::Point &scaledPosition, const Ratio &scaleX, const Ratio &scaleY) const;
+
+ /**
+ * Creates a copy of this cel on the free store and
+ * returns a pointer to the new object. The new cel will
+ * point to a shared copy of bitmap/resource data.
+ */
+ virtual CelObj *duplicate() const = 0;
+
+ /**
+ * Retrieves a pointer to the raw resource data for this
+ * cel. This method cannot be used with a CelObjColor.
+ */
+ virtual byte *getResPointer() const = 0;
+
+ /**
+ * Reads the pixel at the given coordinates. This method
+ * is valid only for CelObjView and CelObjPic.
+ */
+ virtual uint8 readPixel(uint16 x, uint16 y, bool mirrorX) const;
+
+ /**
+ * Submits the palette from this cel to the palette
+ * manager for integration into the master screen
+ * palette.
+ */
+ void submitPalette() const;
+
+#pragma mark -
+#pragma mark CelObj - Drawing
+private:
+ template<typename MAPPER, typename SCALER>
+ void render(Buffer &target, const Common::Rect &targetRect, const Common::Point &scaledPosition) const;
+
+ template<typename MAPPER, typename SCALER>
+ void render(Buffer &target, const Common::Rect &targetRect, const Common::Point &scaledPosition, const Ratio &scaleX, const Ratio &scaleY) const;
+
+ void drawHzFlip(Buffer &target, const Common::Rect &targetRect, const Common::Point &scaledPosition) const;
+ void drawNoFlip(Buffer &target, const Common::Rect &targetRect, const Common::Point &scaledPosition) const;
+ void drawUncompNoFlip(Buffer &target, const Common::Rect &targetRect, const Common::Point &scaledPosition) const;
+ void drawUncompHzFlip(Buffer &target, const Common::Rect &targetRect, const Common::Point &scaledPosition) const;
+ void scaleDraw(Buffer &target, const Ratio &scaleX, const Ratio &scaleY, const Common::Rect &targetRect, const Common::Point &scaledPosition) const;
+ void scaleDrawUncomp(Buffer &target, const Ratio &scaleX, const Ratio &scaleY, const Common::Rect &targetRect, const Common::Point &scaledPosition) const;
+
+ void drawHzFlipMap(Buffer &target, const Common::Rect &targetRect, const Common::Point &scaledPosition) const;
+ void drawNoFlipMap(Buffer &target, const Common::Rect &targetRect, const Common::Point &scaledPosition) const;
+ void drawUncompNoFlipMap(Buffer &target, const Common::Rect &targetRect, const Common::Point &scaledPosition) const;
+ void drawUncompHzFlipMap(Buffer &target, const Common::Rect &targetRect, const Common::Point &scaledPosition) const;
+ void scaleDrawMap(Buffer &target, const Ratio &scaleX, const Ratio &scaleY, const Common::Rect &targetRect, const Common::Point &scaledPosition) const;
+ void scaleDrawUncompMap(Buffer &target, const Ratio &scaleX, const Ratio &scaleY, const Common::Rect &targetRect, const Common::Point &scaledPosition) const;
+ // NOTE: The original includes versions of the above functions with priority parameters, which were not actually used in SCI32
+
+ void drawHzFlipNoMD(Buffer &target, const Common::Rect &targetRect, const Common::Point &scaledPosition) const;
+ void drawNoFlipNoMD(Buffer &target, const Common::Rect &targetRect, const Common::Point &scaledPosition) const;
+ void drawUncompNoFlipNoMD(Buffer &target, const Common::Rect &targetRect, const Common::Point &scaledPosition) const;
+ void drawUncompNoFlipNoMDNoSkip(Buffer &target, const Common::Rect &targetRect, const Common::Point &scaledPosition) const;
+ void drawUncompHzFlipNoMD(Buffer &target, const Common::Rect &targetRect, const Common::Point &scaledPosition) const;
+ void drawUncompHzFlipNoMDNoSkip(Buffer &target, const Common::Rect &targetRect, const Common::Point &scaledPosition) const;
+ void scaleDrawNoMD(Buffer &target, const Ratio &scaleX, const Ratio &scaleY, const Common::Rect &targetRect, const Common::Point &scaledPosition) const;
+ void scaleDrawUncompNoMD(Buffer &target, const Ratio &scaleX, const Ratio &scaleY, const Common::Rect &targetRect, const Common::Point &scaledPosition) const;
+ // NOTE: The original includes versions of the above functions with priority parameters, which were not actually used in SCI32
+
+#pragma mark -
+#pragma mark CelObj - Caching
+protected:
+ /**
+ * A monotonically increasing cache ID used to identify
+ * the least recently used item in the cache for
+ * replacement.
+ */
+ static int _nextCacheId;
+
+ /**
+ * A cache of cel objects used to avoid reinitialisation
+ * overhead for cels with the same CelInfo32.
+ */
+ // NOTE: At least SQ6 uses a fixed cache size of 100.
+ static CelCache *_cache;
+
+ /**
+ * Searches the cel cache for a CelObj matching the
+ * provided CelInfo32. If not found, -1 is returned.
+ * nextInsertIndex will receive the index of the oldest
+ * item in the cache, which can be used to replace
+ * the oldest item with a newer item.
+ */
+ int searchCache(const CelInfo32 &celInfo, int *nextInsertIndex) const;
+
+ /**
+ * Puts a copy of this CelObj into the cache at the
+ * given cache index.
+ */
+ void putCopyInCache(int index) const;
+};
+
+#pragma mark -
+#pragma mark CelObjView
+
+/**
+ * A CelObjView is the drawing primitive for a View type
+ * resource. Each CelObjView corresponds to a single cel
+ * within a single loop of a view.
+ */
+class CelObjView : public CelObj {
+private:
+ /**
+ * Analyses resources without baked-in remap flags
+ * to determine whether or not they should be remapped.
+ */
+ bool analyzeUncompressedForRemap() const;
+
+ /**
+ * Analyses compressed resources without baked-in remap
+ * flags to determine whether or not they should be
+ * remapped.
+ */
+ bool analyzeForRemap() const;
+
+public:
+ CelObjView(GuiResourceId viewId, int16 loopNo, int16 celNo);
+ virtual ~CelObjView() override {};
+
+ using CelObj::draw;
+
+ /**
+ * Draws the cel to the target buffer using the
+ * positioning, mirroring, and scaling information from
+ * the provided arguments.
+ */
+ void draw(Buffer &target, const Common::Rect &targetRect, const Common::Point &scaledPosition, bool mirrorX, const Ratio &scaleX, const Ratio &scaleY);
+
+ virtual CelObjView *duplicate() const override;
+ virtual byte *getResPointer() const override;
+};
+
+#pragma mark -
+#pragma mark CelObjPic
+
+/**
+ * A CelObjPic is the drawing primitive for a Picture type
+ * resource. Each CelObjPic corresponds to a single cel
+ * within a picture.
+ */
+class CelObjPic : public CelObj {
+private:
+ /**
+ * Analyses uncompressed resources without baked-in skip
+ * flags to determine whether or not they can use fast
+ * blitting.
+ */
+ bool analyzeUncompressedForSkip() const;
+
+public:
+ /**
+ * The number of cels in the original picture resource.
+ */
+ uint8 _celCount;
+
+ /**
+ * The position of this cel relative to the top-left
+ * corner of the picture.
+ */
+ Common::Point _relativePosition;
+
+ /**
+ * The z-buffer priority for this cel. Higher prorities
+ * are drawn on top of lower priorities.
+ */
+ int16 _priority;
+
+ CelObjPic(GuiResourceId pictureId, int16 celNo);
+ virtual ~CelObjPic() override {};
+
+ using CelObj::draw;
+ virtual void draw(Buffer &target, const Common::Rect &targetRect, const Common::Point &scaledPosition, const bool mirrorX) override;
+
+ virtual CelObjPic *duplicate() const override;
+ virtual byte *getResPointer() const override;
+};
+
+#pragma mark -
+#pragma mark CelObjMem
+
+/**
+ * A CelObjMem is the drawing primitive for arbitrary
+ * bitmaps generated in memory. Generated bitmaps in SCI32
+ * include text & vector drawings and per-pixel screen
+ * transitions like dissolves.
+ */
+class CelObjMem : public CelObj {
+public:
+ CelObjMem(reg_t bitmap);
+ virtual ~CelObjMem() override {};
+
+ virtual CelObjMem *duplicate() const override;
+ virtual byte *getResPointer() const override;
+};
+
+#pragma mark -
+#pragma mark CelObjColor
+
+/**
+ * A CelObjColor is the drawing primitive for fast,
+ * low-memory, flat color fills.
+ */
+class CelObjColor : public CelObj {
+public:
+ CelObjColor(uint8 color, int16 width, int16 height);
+ virtual ~CelObjColor() override {};
+
+ using CelObj::draw;
+ /**
+ * Block fills the target buffer with the cel color.
+ */
+ void draw(Buffer &target, const Common::Rect &targetRect) const;
+ virtual void draw(Buffer &target, const ScreenItem &screenItem, const Common::Rect &targetRect, const bool mirrorX) override;
+ virtual void draw(Buffer &target, const Common::Rect &targetRect, const Common::Point &scaledPosition, const bool mirrorX) override;
+
+ virtual CelObjColor *duplicate() const override;
+ virtual byte *getResPointer() const override;
+};
+} // End of namespace Sci
+
+#endif
diff --git a/engines/sci/graphics/compare.cpp b/engines/sci/graphics/compare.cpp
index 716a366b7c..729eeeaf81 100644
--- a/engines/sci/graphics/compare.cpp
+++ b/engines/sci/graphics/compare.cpp
@@ -67,7 +67,7 @@ uint16 GfxCompare::isOnControl(uint16 screenMask, const Common::Rect &rect) {
return result;
}
-reg_t GfxCompare::canBeHereCheckRectList(reg_t checkObject, const Common::Rect &checkRect, List *list) {
+reg_t GfxCompare::canBeHereCheckRectList(const reg_t checkObject, const Common::Rect &checkRect, const List *list, const uint16 signalFlags) const {
reg_t curAddress = list->first;
Node *curNode = _segMan->lookupNode(curAddress);
reg_t curObject;
@@ -78,7 +78,7 @@ reg_t GfxCompare::canBeHereCheckRectList(reg_t checkObject, const Common::Rect &
curObject = curNode->value;
if (curObject != checkObject) {
signal = readSelectorValue(_segMan, curObject, SELECTOR(signal));
- if (!(signal & (kSignalIgnoreActor | kSignalRemoveView | kSignalNoUpdate))) {
+ if (!(signal & signalFlags)) {
curRect.left = readSelectorValue(_segMan, curObject, SELECTOR(brLeft));
curRect.top = readSelectorValue(_segMan, curObject, SELECTOR(brTop));
curRect.right = readSelectorValue(_segMan, curObject, SELECTOR(brRight));
@@ -112,11 +112,6 @@ void GfxCompare::kernelSetNowSeen(reg_t objectReference) {
GfxView *view = NULL;
Common::Rect celRect(0, 0);
GuiResourceId viewId = (GuiResourceId)readSelectorValue(_segMan, objectReference, SELECTOR(view));
-
- // HACK: Ignore invalid views for now (perhaps unimplemented text views?)
- if (viewId == 0xFFFF) // invalid view
- return;
-
int16 loopNo = readSelectorValue(_segMan, objectReference, SELECTOR(loop));
int16 celNo = readSelectorValue(_segMan, objectReference, SELECTOR(cel));
int16 x = (int16)readSelectorValue(_segMan, objectReference, SELECTOR(x));
@@ -126,26 +121,8 @@ void GfxCompare::kernelSetNowSeen(reg_t objectReference) {
z = (int16)readSelectorValue(_segMan, objectReference, SELECTOR(z));
view = _cache->getView(viewId);
-
-#ifdef ENABLE_SCI32
- if (view->isSci2Hires())
- view->adjustToUpscaledCoordinates(y, x);
- else if ((getSciVersion() >= SCI_VERSION_2_1_EARLY) && (getSciVersion() <= SCI_VERSION_2_1_LATE))
- _coordAdjuster->fromScriptToDisplay(y, x);
-#endif
-
view->getCelRect(loopNo, celNo, x, y, z, celRect);
-#ifdef ENABLE_SCI32
- if (view->isSci2Hires()) {
- view->adjustBackUpscaledCoordinates(celRect.top, celRect.left);
- view->adjustBackUpscaledCoordinates(celRect.bottom, celRect.right);
- } else if ((getSciVersion() >= SCI_VERSION_2_1_EARLY) && (getSciVersion() <= SCI_VERSION_2_1_LATE)) {
- _coordAdjuster->fromDisplayToScript(celRect.top, celRect.left);
- _coordAdjuster->fromDisplayToScript(celRect.bottom, celRect.right);
- }
-#endif
-
if (lookupSelector(_segMan, objectReference, SELECTOR(nsTop), NULL, NULL) == kSelectorVariable) {
setNSRect(objectReference, celRect);
}
@@ -153,32 +130,57 @@ void GfxCompare::kernelSetNowSeen(reg_t objectReference) {
reg_t GfxCompare::kernelCanBeHere(reg_t curObject, reg_t listReference) {
Common::Rect checkRect;
- Common::Rect adjustedRect;
- uint16 signal, controlMask;
uint16 result;
checkRect.left = readSelectorValue(_segMan, curObject, SELECTOR(brLeft));
checkRect.top = readSelectorValue(_segMan, curObject, SELECTOR(brTop));
checkRect.right = readSelectorValue(_segMan, curObject, SELECTOR(brRight));
checkRect.bottom = readSelectorValue(_segMan, curObject, SELECTOR(brBottom));
+ uint16 signal = readSelectorValue(_segMan, curObject, SELECTOR(signal));
if (!checkRect.isValidRect()) { // can occur in Iceman and Mother Goose - HACK? TODO: is this really occuring in sierra sci? check this
warning("kCan(t)BeHere - invalid rect %d, %d -> %d, %d", checkRect.left, checkRect.top, checkRect.right, checkRect.bottom);
return NULL_REG; // this means "can be here"
}
- adjustedRect = _coordAdjuster->onControl(checkRect);
-
- signal = readSelectorValue(_segMan, curObject, SELECTOR(signal));
- controlMask = readSelectorValue(_segMan, curObject, SELECTOR(illegalBits));
+ Common::Rect adjustedRect = _coordAdjuster->onControl(checkRect);
+ uint16 controlMask = readSelectorValue(_segMan, curObject, SELECTOR(illegalBits));
result = isOnControl(GFX_SCREEN_MASK_CONTROL, adjustedRect) & controlMask;
if ((!result) && (signal & (kSignalIgnoreActor | kSignalRemoveView)) == 0) {
List *list = _segMan->lookupList(listReference);
if (!list)
error("kCanBeHere called with non-list as parameter");
- return canBeHereCheckRectList(curObject, checkRect, list);
+ return canBeHereCheckRectList(curObject, checkRect, list, kSignalIgnoreActor | kSignalRemoveView | kSignalNoUpdate);
}
+
+ return make_reg(0, result);
+}
+
+reg_t GfxCompare::kernelCantBeHere32(const reg_t curObject, const reg_t listReference) const {
+ // Most of SCI32 graphics code converts rects from the VM to exclusive
+ // rects before operating on them, but this call leverages SCI16 engine
+ // code that operates on inclusive rects, so the rect's bottom-right
+ // point is not modified like in other SCI32 kernel calls
+ Common::Rect checkRect(
+ readSelectorValue(_segMan, curObject, SELECTOR(brLeft)),
+ readSelectorValue(_segMan, curObject, SELECTOR(brTop)),
+ readSelectorValue(_segMan, curObject, SELECTOR(brRight)),
+ readSelectorValue(_segMan, curObject, SELECTOR(brBottom))
+ );
+
+ uint16 result = 0;
+ uint16 signal = readSelectorValue(_segMan, curObject, SELECTOR(signal));
+ const uint16 signalFlags = kSignalIgnoreActor | kSignalHidden;
+
+ if ((signal & signalFlags) == 0) {
+ List *list = _segMan->lookupList(listReference);
+ if (!list) {
+ error("kCantBeHere called with non-list as parameter");
+ }
+ result = !canBeHereCheckRectList(curObject, checkRect, list, signalFlags).isNull();
+ }
+
return make_reg(0, result);
}
@@ -201,15 +203,9 @@ void GfxCompare::kernelBaseSetter(reg_t object) {
GuiResourceId viewId = readSelectorValue(_segMan, object, SELECTOR(view));
int16 loopNo = readSelectorValue(_segMan, object, SELECTOR(loop));
int16 celNo = readSelectorValue(_segMan, object, SELECTOR(cel));
-
- // HACK: Ignore invalid views for now (perhaps unimplemented text views?)
- if (viewId == 0xFFFF) // invalid view
- return;
-
uint16 scaleSignal = 0;
- if (getSciVersion() >= SCI_VERSION_1_1) {
+ if (getSciVersion() >= SCI_VERSION_1_1)
scaleSignal = readSelectorValue(_segMan, object, SELECTOR(scaleSignal));
- }
Common::Rect celRect;
diff --git a/engines/sci/graphics/compare.h b/engines/sci/graphics/compare.h
index 88b44aeeb1..c7005980d0 100644
--- a/engines/sci/graphics/compare.h
+++ b/engines/sci/graphics/compare.h
@@ -40,6 +40,7 @@ public:
uint16 kernelOnControl(byte screenMask, const Common::Rect &rect);
void kernelSetNowSeen(reg_t objectReference);
reg_t kernelCanBeHere(reg_t curObject, reg_t listReference);
+ reg_t kernelCantBeHere32(const reg_t curObject, const reg_t listReference) const;
bool kernelIsItSkip(GuiResourceId viewId, int16 loopNo, int16 celNo, Common::Point position);
void kernelBaseSetter(reg_t object);
Common::Rect getNSRect(reg_t object);
@@ -58,7 +59,7 @@ private:
* *different* from checkObject, has a brRect which is contained inside
* checkRect.
*/
- reg_t canBeHereCheckRectList(reg_t checkObject, const Common::Rect &checkRect, List *list);
+ reg_t canBeHereCheckRectList(const reg_t checkObject, const Common::Rect &checkRect, const List *list, const uint16 signalFlags) const;
};
} // End of namespace Sci
diff --git a/engines/sci/graphics/controls16.cpp b/engines/sci/graphics/controls16.cpp
index e2e250cf9d..b4bd92699a 100644
--- a/engines/sci/graphics/controls16.cpp
+++ b/engines/sci/graphics/controls16.cpp
@@ -151,7 +151,7 @@ void GfxControls16::kernelTexteditChange(reg_t controlObject, reg_t eventObject)
Common::Rect rect;
if (textReference.isNull())
- error("kEditControl called on object that doesnt have a text reference");
+ error("kEditControl called on object that doesn't have a text reference");
text = _segMan->getString(textReference);
uint16 oldCursorPos = cursorPos;
diff --git a/engines/sci/graphics/controls32.cpp b/engines/sci/graphics/controls32.cpp
index 1bd497ce98..a877d8c276 100644
--- a/engines/sci/graphics/controls32.cpp
+++ b/engines/sci/graphics/controls32.cpp
@@ -23,9 +23,11 @@
#include "common/system.h"
#include "sci/sci.h"
+#include "sci/console.h"
#include "sci/event.h"
#include "sci/engine/kernel.h"
#include "sci/engine/seg_manager.h"
+#include "sci/engine/state.h"
#include "sci/graphics/cache.h"
#include "sci/graphics/compare.h"
#include "sci/graphics/controls32.h"
@@ -34,171 +36,321 @@
#include "sci/graphics/text32.h"
namespace Sci {
+GfxControls32::GfxControls32(SegManager *segMan, GfxCache *cache, GfxText32 *text) :
+ _segMan(segMan),
+ _gfxCache(cache),
+ _gfxText32(text),
+ _overwriteMode(false),
+ _nextCursorFlashTick(0) {}
-GfxControls32::GfxControls32(SegManager *segMan, GfxCache *cache, GfxText32 *text)
- : _segMan(segMan), _cache(cache), _text(text) {
-}
+reg_t GfxControls32::kernelEditText(const reg_t controlObject) {
+ SegManager *segMan = _segMan;
-GfxControls32::~GfxControls32() {
-}
+ TextEditor editor;
+ reg_t textObject = readSelector(_segMan, controlObject, SELECTOR(text));
+ editor.text = _segMan->getString(textObject);
+ editor.foreColor = readSelectorValue(_segMan, controlObject, SELECTOR(fore));
+ editor.backColor = readSelectorValue(_segMan, controlObject, SELECTOR(back));
+ editor.skipColor = readSelectorValue(_segMan, controlObject, SELECTOR(skip));
+ editor.fontId = readSelectorValue(_segMan, controlObject, SELECTOR(font));
+ editor.maxLength = readSelectorValue(_segMan, controlObject, SELECTOR(width));
+ editor.bitmap = readSelector(_segMan, controlObject, SELECTOR(bitmap));
+ editor.cursorCharPosition = 0;
+ editor.cursorIsDrawn = false;
+ editor.borderColor = readSelectorValue(_segMan, controlObject, SELECTOR(borderColor));
-void GfxControls32::kernelTexteditChange(reg_t controlObject) {
- SciEvent curEvent;
- uint16 maxChars = 40; //readSelectorValue(_segMan, controlObject, SELECTOR(max)); // TODO
- reg_t textReference = readSelector(_segMan, controlObject, SELECTOR(text));
- GfxFont *font = _cache->getFont(readSelectorValue(_segMan, controlObject, SELECTOR(font)));
- Common::String text;
- uint16 textSize;
- bool textChanged = false;
- bool textAddChar = false;
- Common::Rect rect;
+ reg_t titleObject = readSelector(_segMan, controlObject, SELECTOR(title));
+
+ int16 titleHeight = 0;
+ GuiResourceId titleFontId = readSelectorValue(_segMan, controlObject, SELECTOR(titleFont));
+ if (!titleObject.isNull()) {
+ GfxFont *titleFont = _gfxCache->getFont(titleFontId);
+ titleHeight += _gfxText32->scaleUpHeight(titleFont->getHeight()) + 1;
+ if (editor.borderColor != -1) {
+ titleHeight += 2;
+ }
+ }
- if (textReference.isNull())
- error("kEditControl called on object that doesnt have a text reference");
- text = _segMan->getString(textReference);
+ int16 width = 0;
+ int16 height = titleHeight;
- // TODO: Finish this
- warning("kEditText ('%s')", text.c_str());
- return;
+ GfxFont *editorFont = _gfxCache->getFont(editor.fontId);
+ height += _gfxText32->scaleUpHeight(editorFont->getHeight()) + 1;
+ _gfxText32->setFont(editor.fontId);
+ int16 emSize = _gfxText32->getCharWidth('M', true);
+ width += editor.maxLength * emSize + 1;
+ if (editor.borderColor != -1) {
+ width += 4;
+ height += 2;
+ }
- uint16 cursorPos = 0;
- //uint16 oldCursorPos = cursorPos;
- bool captureEvents = true;
- EventManager* eventMan = g_sci->getEventManager();
+ Common::Rect editorPlaneRect(width, height);
+ editorPlaneRect.translate(readSelectorValue(_segMan, controlObject, SELECTOR(x)), readSelectorValue(_segMan, controlObject, SELECTOR(y)));
- while (captureEvents) {
- curEvent = g_sci->getEventManager()->getSciEvent(SCI_EVENT_KEYBOARD | SCI_EVENT_PEEK);
+ reg_t planeObj = readSelector(_segMan, controlObject, SELECTOR(plane));
+ Plane *sourcePlane = g_sci->_gfxFrameout->getVisiblePlanes().findByObject(planeObj);
+ if (sourcePlane == nullptr) {
+ error("Could not find plane %04x:%04x", PRINT_REG(planeObj));
+ }
+ editorPlaneRect.translate(sourcePlane->_gameRect.left, sourcePlane->_gameRect.top);
- if (curEvent.type == SCI_EVENT_NONE) {
- eventMan->getSciEvent(SCI_EVENT_KEYBOARD); // consume the event
+ editor.textRect = Common::Rect(2, titleHeight + 2, width - 1, height - 1);
+ editor.width = width;
+
+ if (editor.bitmap.isNull()) {
+ TextAlign alignment = (TextAlign)readSelectorValue(_segMan, controlObject, SELECTOR(mode));
+
+ if (titleObject.isNull()) {
+ bool dimmed = readSelectorValue(_segMan, controlObject, SELECTOR(dimmed));
+ editor.bitmap = _gfxText32->createFontBitmap(width, height, editor.textRect, editor.text, editor.foreColor, editor.backColor, editor.skipColor, editor.fontId, alignment, editor.borderColor, dimmed, true);
} else {
- textSize = text.size();
+ Common::String title = _segMan->getString(titleObject);
+ int16 titleBackColor = readSelectorValue(_segMan, controlObject, SELECTOR(titleBack));
+ int16 titleForeColor = readSelectorValue(_segMan, controlObject, SELECTOR(titleFore));
+ editor.bitmap = _gfxText32->createTitledBitmap(width, height, editor.textRect, editor.text, editor.foreColor, editor.backColor, editor.skipColor, editor.fontId, alignment, editor.borderColor, title, titleForeColor, titleBackColor, titleFontId, true);
+ }
+ }
+
+ drawCursor(editor);
- switch (curEvent.type) {
- case SCI_EVENT_MOUSE_PRESS:
- // TODO: Implement mouse support for cursor change
+ Plane *plane = new Plane(editorPlaneRect, kPlanePicTransparent);
+ plane->changePic();
+ g_sci->_gfxFrameout->addPlane(*plane);
+
+ CelInfo32 celInfo;
+ celInfo.type = kCelTypeMem;
+ celInfo.bitmap = editor.bitmap;
+
+ ScreenItem *screenItem = new ScreenItem(plane->_object, celInfo, Common::Point(), ScaleInfo());
+ plane->_screenItemList.add(screenItem);
+
+ // frameOut must be called after the screen item is
+ // created, and before it is updated at the end of the
+ // event loop, otherwise it has both created and updated
+ // flags set which crashes the engine (it runs updates
+ // before creations)
+ g_sci->_gfxFrameout->frameOut(true);
+
+ EventManager *eventManager = g_sci->getEventManager();
+ bool clearTextOnInput = true;
+ bool textChanged = false;
+ for (;;) {
+ // We peek here because the last event needs to be allowed to
+ // dispatch a second time to the normal event handling system.
+ // In the actual engine, the event is always consumed and then
+ // the last event just gets posted back to the event manager for
+ // reprocessing, but instead, we only remove the event from the
+ // queue *after* we have determined it is not a defocusing event
+ const SciEvent event = eventManager->getSciEvent(SCI_EVENT_ANY | SCI_EVENT_PEEK);
+
+ bool focused = true;
+ // Original engine did not have a QUIT event but we have to handle it
+ if (event.type == SCI_EVENT_QUIT) {
+ focused = false;
+ break;
+ } else if (event.type == SCI_EVENT_MOUSE_PRESS && !editorPlaneRect.contains(event.mousePosSci)) {
+ focused = false;
+ } else if (event.type == SCI_EVENT_KEYBOARD) {
+ switch (event.character) {
+ case SCI_KEY_ESC:
+ case SCI_KEY_UP:
+ case SCI_KEY_DOWN:
+ case SCI_KEY_TAB:
+ case SCI_KEY_SHIFT_TAB:
+ case SCI_KEY_ENTER:
+ focused = false;
break;
- case SCI_EVENT_KEYBOARD:
- switch (curEvent.character) {
- case SCI_KEY_BACKSPACE:
- if (cursorPos > 0) {
- cursorPos--; text.deleteChar(cursorPos);
- textChanged = true;
- }
- eventMan->getSciEvent(SCI_EVENT_KEYBOARD); // consume the event
- break;
- case SCI_KEY_DELETE:
- if (cursorPos < textSize) {
- text.deleteChar(cursorPos);
- textChanged = true;
- }
- eventMan->getSciEvent(SCI_EVENT_KEYBOARD); // consume the event
- break;
- case SCI_KEY_HOME: // HOME
- cursorPos = 0; textChanged = true;
- eventMan->getSciEvent(SCI_EVENT_KEYBOARD); // consume the event
- break;
- case SCI_KEY_END: // END
- cursorPos = textSize; textChanged = true;
- eventMan->getSciEvent(SCI_EVENT_KEYBOARD); // consume the event
- break;
- case SCI_KEY_LEFT: // LEFT
- if (cursorPos > 0) {
- cursorPos--; textChanged = true;
- }
- eventMan->getSciEvent(SCI_EVENT_KEYBOARD); // consume the event
- break;
- case SCI_KEY_RIGHT: // RIGHT
- if (cursorPos + 1 <= textSize) {
- cursorPos++; textChanged = true;
- }
- eventMan->getSciEvent(SCI_EVENT_KEYBOARD); // consume the event
- break;
- case 3: // returned in SCI1 late and newer when Control - C is pressed
- if (curEvent.modifiers & SCI_KEYMOD_CTRL) {
- // Control-C erases the whole line
- cursorPos = 0; text.clear();
- textChanged = true;
+ }
+ }
+
+ if (!focused) {
+ break;
+ }
+
+ // Consume the event now that we know it is not one of the
+ // defocusing events above
+ eventManager->getSciEvent(SCI_EVENT_ANY);
+
+ // NOTE: In the original engine, the font and bitmap were
+ // reset here on each iteration through the loop, but it
+ // doesn't seem like this should be necessary since
+ // control is not yielded back to the VM until input is
+ // received, which means there is nothing that could modify
+ // the GfxText32's state with a different font in the
+ // meantime
+
+ bool shouldDeleteChar = false;
+ bool shouldRedrawText = false;
+ uint16 lastCursorPosition = editor.cursorCharPosition;
+ if (event.type == SCI_EVENT_KEYBOARD) {
+ switch (event.character) {
+ case SCI_KEY_LEFT:
+ clearTextOnInput = false;
+ if (editor.cursorCharPosition > 0) {
+ --editor.cursorCharPosition;
+ }
+ break;
+
+ case SCI_KEY_RIGHT:
+ clearTextOnInput = false;
+ if (editor.cursorCharPosition < editor.text.size()) {
+ ++editor.cursorCharPosition;
+ }
+ break;
+
+ case SCI_KEY_HOME:
+ clearTextOnInput = false;
+ editor.cursorCharPosition = 0;
+ break;
+
+ case SCI_KEY_END:
+ clearTextOnInput = false;
+ editor.cursorCharPosition = editor.text.size();
+ break;
+
+ case SCI_KEY_INSERT:
+ clearTextOnInput = false;
+ // Redrawing also changes the cursor rect to
+ // reflect the new insertion mode
+ shouldRedrawText = true;
+ _overwriteMode = !_overwriteMode;
+ break;
+
+ case SCI_KEY_DELETE:
+ clearTextOnInput = false;
+ if (editor.cursorCharPosition < editor.text.size()) {
+ shouldDeleteChar = true;
+ }
+ break;
+
+ case SCI_KEY_BACKSPACE:
+ clearTextOnInput = false;
+ shouldDeleteChar = true;
+ if (editor.cursorCharPosition > 0) {
+ --editor.cursorCharPosition;
+ }
+ break;
+
+ case SCI_KEY_ETX:
+ editor.text.clear();
+ editor.cursorCharPosition = 0;
+ shouldRedrawText = true;
+ break;
+
+ default: {
+ if (event.character >= 20 && event.character < 257) {
+ if (clearTextOnInput) {
+ clearTextOnInput = false;
+ editor.text.clear();
}
- eventMan->getSciEvent(SCI_EVENT_KEYBOARD); // consume the event
- break;
- case SCI_KEY_UP:
- case SCI_KEY_DOWN:
- case SCI_KEY_ENTER:
- case SCI_KEY_ESC:
- case SCI_KEY_TAB:
- case SCI_KEY_SHIFT_TAB:
- captureEvents = false;
- break;
- default:
- if ((curEvent.modifiers & SCI_KEYMOD_CTRL) && curEvent.character == 'c') {
- // Control-C in earlier SCI games (SCI0 - SCI1 middle)
- // Control-C erases the whole line
- cursorPos = 0; text.clear();
- textChanged = true;
- } else if (curEvent.character > 31 && curEvent.character < 256 && textSize < maxChars) {
- // insert pressed character
- textAddChar = true;
- textChanged = true;
+
+ if (
+ (_overwriteMode && editor.cursorCharPosition < editor.maxLength) ||
+ (editor.text.size() < editor.maxLength && _gfxText32->getCharWidth(event.character, true) + _gfxText32->getStringWidth(editor.text) < editor.textRect.width())
+ ) {
+ if (_overwriteMode && editor.cursorCharPosition < editor.text.size()) {
+ editor.text.setChar(event.character, editor.cursorCharPosition);
+ } else {
+ editor.text.insertChar(event.character, editor.cursorCharPosition);
+ }
+
+ ++editor.cursorCharPosition;
+ shouldRedrawText = true;
}
- eventMan->getSciEvent(SCI_EVENT_KEYBOARD); // consume the event
- break;
}
- break;
+ }
}
}
- if (textChanged) {
- rect = g_sci->_gfxCompare->getNSRect(controlObject);
+ if (shouldDeleteChar) {
+ shouldRedrawText = true;
+ if (editor.cursorCharPosition < editor.text.size()) {
+ editor.text.deleteChar(editor.cursorCharPosition);
+ }
+ }
- if (textAddChar) {
- const char *textPtr = text.c_str();
+ if (shouldRedrawText) {
+ eraseCursor(editor);
+ _gfxText32->erase(editor.textRect, true);
+ _gfxText32->drawTextBox(editor.text);
+ drawCursor(editor);
+ textChanged = true;
+ screenItem->_updated = g_sci->_gfxFrameout->getScreenCount();
+ } else if (editor.cursorCharPosition != lastCursorPosition) {
+ eraseCursor(editor);
+ drawCursor(editor);
+ screenItem->_updated = g_sci->_gfxFrameout->getScreenCount();
+ } else {
+ flashCursor(editor);
+ screenItem->_updated = g_sci->_gfxFrameout->getScreenCount();
+ }
- // We check if we are really able to add the new char
- uint16 textWidth = 0;
- while (*textPtr)
- textWidth += font->getCharWidth((byte)*textPtr++);
- textWidth += font->getCharWidth(curEvent.character);
+ g_sci->_gfxFrameout->frameOut(true);
+ g_sci->getSciDebugger()->onFrame();
+ g_sci->getEngineState()->speedThrottler(16);
+ g_sci->getEngineState()->_throttleTrigger = true;
+ }
- // Does it fit?
- if (textWidth >= rect.width()) {
- return;
- }
+ g_sci->_gfxFrameout->deletePlane(*plane);
+ if (readSelectorValue(segMan, controlObject, SELECTOR(frameOut))) {
+ g_sci->_gfxFrameout->frameOut(true);
+ }
- text.insertChar(curEvent.character, cursorPos++);
+ _segMan->freeHunkEntry(editor.bitmap);
- // Note: the following checkAltInput call might make the text
- // too wide to fit, but SSCI fails to check that too.
- }
+ if (textChanged) {
+ editor.text.trim();
+ SciString *string = _segMan->lookupString(textObject);
+ string->fromString(editor.text);
+ }
+
+ return make_reg(0, textChanged);
+}
- reg_t hunkId = readSelector(_segMan, controlObject, SELECTOR(bitmap));
- Common::Rect nsRect = g_sci->_gfxCompare->getNSRect(controlObject);
- //texteditCursorErase(); // TODO: Cursor
+void GfxControls32::drawCursor(TextEditor &editor) {
+ if (!editor.cursorIsDrawn) {
+ editor.cursorRect.left = editor.textRect.left + _gfxText32->getTextWidth(editor.text, 0, editor.cursorCharPosition);
- // Write back string
- _segMan->strcpy(textReference, text.c_str());
- // Modify the buffer and show it
- _text->createTextBitmap(controlObject, 0, 0, hunkId);
+ const int16 scaledFontHeight = _gfxText32->scaleUpHeight(_gfxText32->_font->getHeight());
- _text->drawTextBitmap(0, 0, nsRect, controlObject);
- //texteditCursorDraw(rect, text.c_str(), cursorPos); // TODO: Cursor
- g_system->updateScreen();
+ // NOTE: The original code branched on borderColor here but
+ // the two branches appeared to be identical, differing only
+ // because the compiler decided to be differently clever
+ // when optimising multiplication in each branch
+ if (_overwriteMode) {
+ editor.cursorRect.top = editor.textRect.top;
+ editor.cursorRect.setHeight(scaledFontHeight);
} else {
- // TODO: Cursor
- /*
- if (g_system->getMillis() >= _texteditBlinkTime) {
- _paint16->invertRect(_texteditCursorRect);
- _paint16->bitsShow(_texteditCursorRect);
- _texteditCursorVisible = !_texteditCursorVisible;
- texteditSetBlinkTime();
- }
- */
+ editor.cursorRect.top = editor.textRect.top + scaledFontHeight - 1;
+ editor.cursorRect.setHeight(1);
}
- textAddChar = false;
- textChanged = false;
- g_sci->sleep(10);
- } // while
+ const char currentChar = editor.cursorCharPosition < editor.text.size() ? editor.text[editor.cursorCharPosition] : ' ';
+ editor.cursorRect.setWidth(_gfxText32->getCharWidth(currentChar, true));
+
+ _gfxText32->invertRect(editor.bitmap, editor.width, editor.cursorRect, editor.foreColor, editor.backColor, true);
+
+ editor.cursorIsDrawn = true;
+ }
+
+ _nextCursorFlashTick = g_sci->getTickCount() + 30;
+}
+
+void GfxControls32::eraseCursor(TextEditor &editor) {
+ if (editor.cursorIsDrawn) {
+ _gfxText32->invertRect(editor.bitmap, editor.width, editor.cursorRect, editor.foreColor, editor.backColor, true);
+ editor.cursorIsDrawn = false;
+ }
+
+ _nextCursorFlashTick = g_sci->getTickCount() + 30;
}
+void GfxControls32::flashCursor(TextEditor &editor) {
+ if (g_sci->getTickCount() > _nextCursorFlashTick) {
+ _gfxText32->invertRect(editor.bitmap, editor.width, editor.cursorRect, editor.foreColor, editor.backColor, true);
+
+ editor.cursorIsDrawn = !editor.cursorIsDrawn;
+ _nextCursorFlashTick = g_sci->getTickCount() + 30;
+ }
+}
} // End of namespace Sci
diff --git a/engines/sci/graphics/controls32.h b/engines/sci/graphics/controls32.h
index 5af7c20f16..1bb7679ddd 100644
--- a/engines/sci/graphics/controls32.h
+++ b/engines/sci/graphics/controls32.h
@@ -29,20 +29,95 @@ class GfxCache;
class GfxScreen;
class GfxText32;
+struct TextEditor {
+ /**
+ * The bitmap where the editor is rendered.
+ */
+ reg_t bitmap;
+
+ /**
+ * The width of the editor, in bitmap pixels.
+ */
+ int16 width;
+
+ /**
+ * The text in the editor.
+ */
+ Common::String text;
+
+ /**
+ * The rect where text should be drawn into the editor,
+ * in bitmap pixels.
+ */
+ Common::Rect textRect;
+
+ /**
+ * The color of the border. -1 indicates no border.
+ */
+ int16 borderColor;
+
+ /**
+ * The text color.
+ */
+ uint8 foreColor;
+
+ /**
+ * The background color.
+ */
+ uint8 backColor;
+
+ /**
+ * The transparent color.
+ */
+ uint8 skipColor;
+
+ /**
+ * The font used to render the text in the editor.
+ */
+ GuiResourceId fontId;
+
+ /**
+ * The current position of the cursor within the editor.
+ */
+ uint16 cursorCharPosition;
+
+ /**
+ * Whether or not the cursor is currently drawn to the
+ * screen.
+ */
+ bool cursorIsDrawn;
+
+ /**
+ * The rectangle for drawing the input cursor, in bitmap
+ * pixels.
+ */
+ Common::Rect cursorRect;
+
+ /**
+ * The maximum allowed text length, in characters.
+ */
+ uint16 maxLength;
+};
+
/**
* Controls class, handles drawing of controls in SCI32 (SCI2, SCI2.1, SCI3) games
*/
class GfxControls32 {
public:
GfxControls32(SegManager *segMan, GfxCache *cache, GfxText32 *text);
- ~GfxControls32();
- void kernelTexteditChange(reg_t controlObject);
+ reg_t kernelEditText(const reg_t controlObject);
private:
SegManager *_segMan;
- GfxCache *_cache;
- GfxText32 *_text;
+ GfxCache *_gfxCache;
+ GfxText32 *_gfxText32;
+
+ bool _overwriteMode;
+ uint32 _nextCursorFlashTick;
+ void drawCursor(TextEditor &editor);
+ void eraseCursor(TextEditor &editor);
+ void flashCursor(TextEditor &editor);
};
} // End of namespace Sci
diff --git a/engines/sci/graphics/cursor.cpp b/engines/sci/graphics/cursor.cpp
index 1a58de073c..f5dd473959 100644
--- a/engines/sci/graphics/cursor.cpp
+++ b/engines/sci/graphics/cursor.cpp
@@ -336,6 +336,9 @@ void GfxCursor::setPosition(Common::Point pos) {
&& ((workaround->newPositionX == pos.x) && (workaround->newPositionY == pos.y))) {
EngineState *s = g_sci->getEngineState();
s->_cursorWorkaroundActive = true;
+ // At least on OpenPandora it seems that the cursor is actually set, but a bit afterwards
+ // touch screen controls will overwrite the position. More information see kGetEvent in kevent.cpp.
+ s->_cursorWorkaroundPosCount = 5; // should be enough for OpenPandora
s->_cursorWorkaroundPoint = pos;
s->_cursorWorkaroundRect = Common::Rect(workaround->rectLeft, workaround->rectTop, workaround->rectRight, workaround->rectBottom);
return;
@@ -453,6 +456,15 @@ void GfxCursor::kernelClearZoomZone() {
void GfxCursor::kernelSetZoomZone(byte multiplier, Common::Rect zone, GuiResourceId viewNum, int loopNum, int celNum, GuiResourceId picNum, byte zoomColor) {
kernelClearZoomZone();
+ // This function is a stub in the Mac version of Freddy Pharkas.
+ // This function was only used in two games (LB2 and Pharkas), but there
+ // was no version of LB2 for the Macintosh platform.
+ // CHECKME: This wasn't verified against disassembly, one might want
+ // to check against it, in case there's some leftover code in the stubbed
+ // function (although it does seem that this was completely removed).
+ if (g_sci->getPlatform() == Common::kPlatformMacintosh)
+ return;
+
_zoomMultiplier = multiplier;
if (_zoomMultiplier != 1 && _zoomMultiplier != 2 && _zoomMultiplier != 4)
diff --git a/engines/sci/graphics/cursor.h b/engines/sci/graphics/cursor.h
index c2d7998eb3..8d125c45b3 100644
--- a/engines/sci/graphics/cursor.h
+++ b/engines/sci/graphics/cursor.h
@@ -77,8 +77,18 @@ public:
*/
void kernelSetMoveZone(Common::Rect zone);
- void kernelClearZoomZone();
+ /**
+ * Creates a dynamic zoom cursor, that is used to zoom on specific parts of the screen,
+ * using a separate larger picture. This was only used by two SCI1.1 games, Laura Bow 2
+ * (for examining the glyphs), and Freddy Pharkas (for examining the prescription with
+ * the whisky glass).
+ *
+ * In the Mac version of Freddy Pharkas, this was removed completely, and the scene has
+ * been redesigned to work without this functionality. There was no version of LB2 for
+ * the Macintosh platform.
+ */
void kernelSetZoomZone(byte multiplier, Common::Rect zone, GuiResourceId viewNum, int loopNum, int celNum, GuiResourceId picNum, byte zoomColor);
+ void kernelClearZoomZone();
void kernelSetPos(Common::Point pos);
void kernelMoveCursor(Common::Point pos);
diff --git a/engines/sci/graphics/frameout.cpp b/engines/sci/graphics/frameout.cpp
index cb81fe8d61..6454a1eb32 100644
--- a/engines/sci/graphics/frameout.cpp
+++ b/engines/sci/graphics/frameout.cpp
@@ -21,6 +21,7 @@
*/
#include "common/algorithm.h"
+#include "common/config-manager.h"
#include "common/events.h"
#include "common/keyboard.h"
#include "common/list.h"
@@ -46,964 +47,1484 @@
#include "sci/graphics/paint32.h"
#include "sci/graphics/palette32.h"
#include "sci/graphics/picture.h"
+#include "sci/graphics/remap.h"
#include "sci/graphics/text32.h"
+#include "sci/graphics/plane32.h"
+#include "sci/graphics/screen_item32.h"
#include "sci/graphics/frameout.h"
#include "sci/video/robot_decoder.h"
namespace Sci {
-// TODO/FIXME: This is all guesswork
-
-enum SciSpeciaPlanelPictureCodes {
- kPlaneTranslucent = 0xfffe, // -2
- kPlanePlainColored = 0xffff // -1
+static int dissolveSequences[2][20] = {
+ /* SCI2.1early- */ { 3, 6, 12, 20, 48, 96, 184, 272, 576, 1280, 3232, 6912, 13568, 24576, 46080 },
+ /* SCI2.1mid+ */ { 0, 0, 3, 6, 12, 20, 48, 96, 184, 272, 576, 1280, 3232, 6912, 13568, 24576, 46080, 73728, 132096, 466944 }
+};
+static int16 divisionsDefaults[2][16] = {
+ /* SCI2.1early- */ { 1, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 40, 40, 101, 101 },
+ /* SCI2.1mid+ */ { 1, 20, 20, 20, 20, 10, 10, 10, 10, 20, 20, 6, 10, 101, 101, 2 }
+};
+static int16 unknownCDefaults[2][16] = {
+ /* SCI2.1early- */ { 0, 1, 1, 2, 2, 3, 3, 4, 4, 5, 5, 0, 0, 0, 0 },
+ /* SCI2.1mid+ */ { 0, 2, 2, 3, 3, 4, 4, 5, 5, 6, 6, 0, 0, 7, 7, 0 }
};
-GfxFrameout::GfxFrameout(SegManager *segMan, ResourceManager *resMan, GfxCoordAdjuster *coordAdjuster, GfxCache *cache, GfxScreen *screen, GfxPalette32 *palette, GfxPaint32 *paint32)
- : _segMan(segMan), _resMan(resMan), _cache(cache), _screen(screen), _palette(palette), _paint32(paint32), _isHiRes(false) {
-
- _coordAdjuster = (GfxCoordAdjuster32 *)coordAdjuster;
- _curScrollText = -1;
- _showScrollText = false;
- _maxScrollTexts = 0;
+GfxFrameout::GfxFrameout(SegManager *segMan, ResourceManager *resMan, GfxCoordAdjuster *coordAdjuster, GfxCache *cache, GfxScreen *screen, GfxPalette32 *palette, GfxPaint32 *paint32) :
+ _isHiRes(false),
+ _cache(cache),
+ _palette(palette),
+ _resMan(resMan),
+ _screen(screen),
+ _segMan(segMan),
+ _paint32(paint32),
+ _showStyles(nullptr),
+ // TODO: Stop using _gfxScreen
+ _currentBuffer(screen->getDisplayWidth(), screen->getDisplayHeight(), nullptr),
+ _remapOccurred(false),
+ _frameNowVisible(false),
+ _screenRect(screen->getDisplayWidth(), screen->getDisplayHeight()),
+ _overdrawThreshold(0),
+ _palMorphIsOn(false) {
+
+ _currentBuffer.setPixels(calloc(1, screen->getDisplayWidth() * screen->getDisplayHeight()));
+
+ for (int i = 0; i < 236; i += 2) {
+ _styleRanges[i] = 0;
+ _styleRanges[i + 1] = -1;
+ }
+ for (int i = 236; i < ARRAYSIZE(_styleRanges); ++i) {
+ _styleRanges[i] = 0;
+ }
// TODO: Make hires detection work uniformly across all SCI engine
// versions (this flag is normally passed by SCI::MakeGraphicsMgr
- // to the GraphicsMgr constructor depending upon video configuration)
+ // to the GraphicsMgr constructor depending upon video configuration,
+ // so should be handled upstream based on game configuration instead
+ // of here)
if (getSciVersion() >= SCI_VERSION_2_1_EARLY && _resMan->detectHires()) {
_isHiRes = true;
}
+
+ if (getSciVersion() < SCI_VERSION_2_1_MIDDLE) {
+ _dissolveSequenceSeeds = dissolveSequences[0];
+ _defaultDivisions = divisionsDefaults[0];
+ _defaultUnknownC = unknownCDefaults[0];
+ } else {
+ _dissolveSequenceSeeds = dissolveSequences[1];
+ _defaultDivisions = divisionsDefaults[1];
+ _defaultUnknownC = unknownCDefaults[1];
+ }
+
+ switch (g_sci->getGameId()) {
+ case GID_GK2:
+ case GID_LIGHTHOUSE:
+ case GID_LSL7:
+ case GID_PHANTASMAGORIA2:
+ case GID_PQSWAT:
+ case GID_TORIN:
+ case GID_RAMA:
+ _currentBuffer.scriptWidth = 640;
+ _currentBuffer.scriptHeight = 480;
+ break;
+ default:
+ // default script width for other games is 320x200
+ break;
+ }
+
+ // TODO: Nothing in the renderer really uses this. Currently,
+ // the cursor renderer does, and kLocalToGlobal/kGlobalToLocal
+ // do, but in the real engine (1) the cursor is handled in
+ // frameOut, and (2) functions do a very simple lookup of the
+ // plane and arithmetic with the plane's gameRect. In
+ // principle, CoordAdjuster could be reused for
+ // convertGameRectToPlaneRect, but it is not super clear yet
+ // what the benefit would be to do that.
+ _coordAdjuster = (GfxCoordAdjuster32 *)coordAdjuster;
+
+ // TODO: Script resolution is hard-coded per game;
+ // also this must be set or else the engine will crash
+ _coordAdjuster->setScriptsResolution(_currentBuffer.scriptWidth, _currentBuffer.scriptHeight);
}
GfxFrameout::~GfxFrameout() {
clear();
+ CelObj::deinit();
+ free(_currentBuffer.getPixels());
}
+void GfxFrameout::run() {
+ CelObj::init();
+ Plane::init();
+ ScreenItem::init();
+
+ // NOTE: This happens in SCI::InitPlane in the actual engine,
+ // and is a background fill plane to ensure hidden planes
+ // (planes with a priority of -1) are never drawn
+ Plane *initPlane = new Plane(Common::Rect(_currentBuffer.scriptWidth, _currentBuffer.scriptHeight));
+ initPlane->_priority = 0;
+ _planes.add(initPlane);
+}
+
+// SCI32 actually did not clear anything at all it seems on restore. The scripts actually cleared up
+// planes + screen items right before restoring. And after restoring they sync'd its internal planes list
+// as well.
void GfxFrameout::clear() {
- deletePlaneItems(NULL_REG);
_planes.clear();
- deletePlanePictures(NULL_REG);
- clearScrollTexts();
-}
-
-void GfxFrameout::clearScrollTexts() {
- _scrollTexts.clear();
- _curScrollText = -1;
-}
-
-void GfxFrameout::addScrollTextEntry(Common::String &text, reg_t kWindow, uint16 x, uint16 y, bool replace) {
- //reg_t bitmapHandle = g_sci->_gfxText32->createScrollTextBitmap(text, kWindow);
- // HACK: We set the container dimensions manually
- reg_t bitmapHandle = g_sci->_gfxText32->createScrollTextBitmap(text, kWindow, 480, 70);
- ScrollTextEntry textEntry;
- textEntry.bitmapHandle = bitmapHandle;
- textEntry.kWindow = kWindow;
- textEntry.x = x;
- textEntry.y = y;
- if (!replace || _scrollTexts.size() == 0) {
- if (_scrollTexts.size() > _maxScrollTexts) {
- _scrollTexts.remove_at(0);
- _curScrollText--;
- }
- _scrollTexts.push_back(textEntry);
- _curScrollText++;
- } else {
- _scrollTexts.pop_back();
- _scrollTexts.push_back(textEntry);
- }
+ _visiblePlanes.clear();
+ _showList.clear();
}
-void GfxFrameout::showCurrentScrollText() {
- if (!_showScrollText || _curScrollText < 0)
+// This is what Game::restore does, only needed when our ScummVM dialogs are patched in
+// It actually does one pass before actual restore deleting screen items + planes
+// And after restore it does another pass adding screen items + planes.
+// Attention: at least Space Quest 6's option plane seems to stay in memory right from the start and is not re-created.
+void GfxFrameout::syncWithScripts(bool addElements) {
+ EngineState *engineState = g_sci->getEngineState();
+ SegManager *segMan = engineState->_segMan;
+
+ // In case original save/restore dialogs are active, don't do anything
+ if (ConfMan.getBool("originalsaveload"))
return;
- uint16 size = (uint16)_scrollTexts.size();
- if (size > 0) {
- assert(_curScrollText < size);
- ScrollTextEntry textEntry = _scrollTexts[_curScrollText];
- g_sci->_gfxText32->drawScrollTextBitmap(textEntry.kWindow, textEntry.bitmapHandle, textEntry.x, textEntry.y);
- }
-}
-
-extern void showScummVMDialog(const Common::String &message);
-
-void GfxFrameout::kernelAddPlane(reg_t object) {
- PlaneEntry newPlane;
-
- 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 scriptWidth = readSelectorValue(_segMan, object, SELECTOR(resX));
- uint16 scriptHeight = readSelectorValue(_segMan, object, SELECTOR(resY));
-
- // Phantasmagoria 2 doesn't specify a script width/height
- if (g_sci->getGameId() == GID_PHANTASMAGORIA2) {
- scriptWidth = 640;
- scriptHeight = 480;
- }
-
- assert(scriptWidth > 0 && scriptHeight > 0);
- _coordAdjuster->setScriptsResolution(scriptWidth, scriptHeight);
- }
-
- // Import of QfG character files dialog is shown in QFG4.
- // Display additional popup information before letting user use it.
- // For the SCI0-SCI1.1 version of this, check kDrawControl().
- if (g_sci->inQfGImportRoom() && !strcmp(_segMan->getObjectName(object), "DSPlane")) {
- showScummVMDialog("Characters saved inside ScummVM are shown "
- "automatically. Character files saved in the original "
- "interpreter need to be put inside ScummVM's saved games "
- "directory and a prefix needs to be added depending on which "
- "game it was saved in: 'qfg1-' for Quest for Glory 1, 'qfg2-' "
- "for Quest for Glory 2, 'qfg3-' for Quest for Glory 3. "
- "Example: 'qfg2-thief.sav'.");
- }
-
- newPlane.object = object;
- newPlane.priority = readSelectorValue(_segMan, object, SELECTOR(priority));
- newPlane.lastPriority = -1; // hidden
- newPlane.planeOffsetX = 0;
- newPlane.planeOffsetY = 0;
- newPlane.pictureId = kPlanePlainColored;
- newPlane.planePictureMirrored = false;
- newPlane.planeBack = 0;
- _planes.push_back(newPlane);
-
- kernelUpdatePlane(object);
-}
-
-void GfxFrameout::kernelUpdatePlane(reg_t object) {
- for (PlaneList::iterator it = _planes.begin(); it != _planes.end(); ++it) {
- if (it->object == object) {
- // Read some information
- it->priority = readSelectorValue(_segMan, object, SELECTOR(priority));
- GuiResourceId lastPictureId = it->pictureId;
- it->pictureId = readSelectorValue(_segMan, object, SELECTOR(picture));
- if (lastPictureId != it->pictureId) {
- // picture got changed, load new picture
- deletePlanePictures(object);
- // Draw the plane's picture if it's not a translucent/plane colored frame
- if ((it->pictureId != kPlanePlainColored) && (it->pictureId != kPlaneTranslucent)) {
- // SQ6 gives us a bad picture number for the control menu
- if (_resMan->testResource(ResourceId(kResourceTypePic, it->pictureId)))
- addPlanePicture(object, it->pictureId, 0);
- }
- }
- it->planeRect.top = readSelectorValue(_segMan, object, SELECTOR(top));
- it->planeRect.left = readSelectorValue(_segMan, object, SELECTOR(left));
- it->planeRect.bottom = readSelectorValue(_segMan, object, SELECTOR(bottom));
- it->planeRect.right = readSelectorValue(_segMan, object, SELECTOR(right));
-
- _coordAdjuster->fromScriptToDisplay(it->planeRect.top, it->planeRect.left);
- _coordAdjuster->fromScriptToDisplay(it->planeRect.bottom, it->planeRect.right);
-
- // We get negative left in kq7 in scrolling rooms
- if (it->planeRect.left < 0) {
- it->planeOffsetX = -it->planeRect.left;
- it->planeRect.left = 0;
- } else {
- it->planeOffsetX = 0;
- }
+ // Get planes list object
+ reg_t planesListObject = engineState->variables[VAR_GLOBAL][10];
+ reg_t planesListElements = readSelector(segMan, planesListObject, SELECTOR(elements));
- if (it->planeRect.top < 0) {
- it->planeOffsetY = -it->planeRect.top;
- it->planeRect.top = 0;
- } else {
- it->planeOffsetY = 0;
- }
+ List *planesList = segMan->lookupList(planesListElements);
+ reg_t planesNodeObject = planesList->first;
- // We get bad plane-bottom in sq6
- if (it->planeRect.right > _screen->getWidth())
- it->planeRect.right = _screen->getWidth();
- if (it->planeRect.bottom > _screen->getHeight())
- it->planeRect.bottom = _screen->getHeight();
-
- it->planeClipRect = Common::Rect(it->planeRect.width(), it->planeRect.height());
- it->upscaledPlaneRect = it->planeRect;
- it->upscaledPlaneClipRect = it->planeClipRect;
- if (_screen->getUpscaledHires()) {
- _screen->adjustToUpscaledCoordinates(it->upscaledPlaneRect.top, it->upscaledPlaneRect.left);
- _screen->adjustToUpscaledCoordinates(it->upscaledPlaneRect.bottom, it->upscaledPlaneRect.right);
- _screen->adjustToUpscaledCoordinates(it->upscaledPlaneClipRect.top, it->upscaledPlaneClipRect.left);
- _screen->adjustToUpscaledCoordinates(it->upscaledPlaneClipRect.bottom, it->upscaledPlaneClipRect.right);
- }
+ // Go through all elements of planes::elements
+ while (!planesNodeObject.isNull()) {
+ Node *planesNode = segMan->lookupNode(planesNodeObject);
+ reg_t planeObject = planesNode->value;
+
+ if (addElements) {
+ // Add this plane object
+ kernelAddPlane(planeObject);
+ }
+
+ reg_t planeCastsObject = readSelector(segMan, planeObject, SELECTOR(casts));
+ reg_t setListElements = readSelector(segMan, planeCastsObject, SELECTOR(elements));
+
+ // Now go through all elements of plane::casts::elements
+ List *planeCastsList = segMan->lookupList(setListElements);
+ reg_t planeCastsNodeObject = planeCastsList->first;
+
+ while (!planeCastsNodeObject.isNull()) {
+ Node *castsNode = segMan->lookupNode(planeCastsNodeObject);
+ reg_t castsObject = castsNode->value;
+
+ reg_t castsListElements = readSelector(segMan, castsObject, SELECTOR(elements));
+
+ List *castsList = segMan->lookupList(castsListElements);
+ reg_t castNodeObject = castsList->first;
- it->planePictureMirrored = readSelectorValue(_segMan, object, SELECTOR(mirrored));
- it->planeBack = readSelectorValue(_segMan, object, SELECTOR(back));
+ while (!castNodeObject.isNull()) {
+ Node *castNode = segMan->lookupNode(castNodeObject);
+ reg_t castObject = castNode->value;
- sortPlanes();
+ // read selector "-info-" of this object
+ // TODO: Seems to have been changed for SCI3
+ // Do NOT use getInfoSelector in here. SCI3 games did not use infoToa, but an actual selector.
+ // Maybe that selector is just a straight copy, but it needs to get verified/checked.
+ uint16 castInfoSelector = readSelectorValue(segMan, castObject, SELECTOR(_info_));
- // Update the items in the plane
- for (FrameoutList::iterator listIterator = _screenItems.begin(); listIterator != _screenItems.end(); listIterator++) {
- reg_t itemPlane = readSelector(_segMan, (*listIterator)->object, SELECTOR(plane));
- if (object == itemPlane) {
- kernelUpdateScreenItem((*listIterator)->object);
+ if (castInfoSelector & kInfoFlagViewInserted) {
+ if (addElements) {
+ // Flag set, so add this screen item
+ kernelAddScreenItem(castObject);
+ } else {
+ // Flag set, so delete this screen item
+ kernelDeleteScreenItem(castObject);
+ }
}
+
+ castNodeObject = castNode->succ;
}
- return;
+ planeCastsNodeObject = castsNode->succ;
+ }
+
+ if (!addElements) {
+ // Delete this plane object
+ kernelDeletePlane(planeObject);
}
+
+ planesNodeObject = planesNode->succ;
}
- error("kUpdatePlane called on plane that wasn't added before");
}
-void GfxFrameout::kernelDeletePlane(reg_t object) {
- deletePlaneItems(object);
- deletePlanePictures(object);
+#pragma mark -
+#pragma mark Screen items
+
+void GfxFrameout::kernelAddScreenItem(const reg_t object) {
+ const reg_t planeObject = readSelector(_segMan, object, SELECTOR(plane));
- for (PlaneList::iterator it = _planes.begin(); it != _planes.end(); ++it) {
- if (it->object == object) {
- _planes.erase(it);
- Common::Rect planeRect;
- planeRect.top = readSelectorValue(_segMan, object, SELECTOR(top));
- planeRect.left = readSelectorValue(_segMan, object, SELECTOR(left));
- planeRect.bottom = readSelectorValue(_segMan, object, SELECTOR(bottom));
- planeRect.right = readSelectorValue(_segMan, object, SELECTOR(right));
+ _segMan->getObject(object)->setInfoSelectorFlag(kInfoFlagViewInserted);
+
+ Plane *plane = _planes.findByObject(planeObject);
+ if (plane == nullptr) {
+ error("kAddScreenItem: Plane %04x:%04x not found for screen item %04x:%04x", PRINT_REG(planeObject), PRINT_REG(object));
+ }
+
+ ScreenItem *screenItem = plane->_screenItemList.findByObject(object);
+ if (screenItem != nullptr) {
+ screenItem->update(object);
+ } else {
+ screenItem = new ScreenItem(object);
+ plane->_screenItemList.add(screenItem);
+ }
+}
- _coordAdjuster->fromScriptToDisplay(planeRect.top, planeRect.left);
- _coordAdjuster->fromScriptToDisplay(planeRect.bottom, planeRect.right);
+void GfxFrameout::kernelUpdateScreenItem(const reg_t object) {
+ const reg_t magnifierObject = readSelector(_segMan, object, SELECTOR(magnifier));
+ if (magnifierObject.isNull()) {
+ const reg_t planeObject = readSelector(_segMan, object, SELECTOR(plane));
+ Plane *plane = _planes.findByObject(planeObject);
+ if (plane == nullptr) {
+ error("kUpdateScreenItem: Plane %04x:%04x not found for screen item %04x:%04x", PRINT_REG(planeObject), PRINT_REG(object));
+ }
- // Blackout removed plane rect
- _paint32->fillRect(planeRect, 0);
- return;
+ ScreenItem *screenItem = plane->_screenItemList.findByObject(object);
+ if (screenItem == nullptr) {
+ error("kUpdateScreenItem: Screen item %04x:%04x not found in plane %04x:%04x", PRINT_REG(object), PRINT_REG(planeObject));
}
+
+ screenItem->update(object);
+ } else {
+ error("Magnifier view is not known to be used by any game. Please submit a bug report with details about the game you were playing and what you were doing that triggered this error. Thanks!");
}
}
-void GfxFrameout::addPlanePicture(reg_t object, GuiResourceId pictureId, uint16 startX, uint16 startY) {
- if (pictureId == kPlanePlainColored || pictureId == kPlaneTranslucent) // sanity check
+void GfxFrameout::kernelDeleteScreenItem(const reg_t object) {
+ _segMan->getObject(object)->clearInfoSelectorFlag(kInfoFlagViewInserted);
+
+ const reg_t planeObject = readSelector(_segMan, object, SELECTOR(plane));
+ Plane *plane = _planes.findByObject(planeObject);
+ if (plane == nullptr) {
+ return;
+ }
+
+ ScreenItem *screenItem = plane->_screenItemList.findByObject(object);
+ if (screenItem == nullptr) {
return;
+ }
- PlanePictureEntry newPicture;
- newPicture.object = object;
- newPicture.pictureId = pictureId;
- newPicture.picture = new GfxPicture(_resMan, _coordAdjuster, 0, _screen, _palette, pictureId, false);
- newPicture.startX = startX;
- newPicture.startY = startY;
- newPicture.pictureCels = 0;
- _planePictures.push_back(newPicture);
+ if (screenItem->_created == 0) {
+ screenItem->_created = 0;
+ screenItem->_updated = 0;
+ screenItem->_deleted = getScreenCount();
+ } else {
+ plane->_screenItemList.erase(screenItem);
+ plane->_screenItemList.pack();
+ }
}
-void GfxFrameout::deletePlanePictures(reg_t object) {
- PlanePictureList::iterator it = _planePictures.begin();
+#pragma mark -
+#pragma mark Planes
- while (it != _planePictures.end()) {
- if (it->object == object || object.isNull()) {
- delete it->pictureCels;
- delete it->picture;
- it = _planePictures.erase(it);
- } else {
- ++it;
- }
+void GfxFrameout::kernelAddPlane(const reg_t object) {
+ Plane *plane = _planes.findByObject(object);
+ if (plane != nullptr) {
+ plane->update(object);
+ updatePlane(*plane);
+ } else {
+ plane = new Plane(object);
+ addPlane(*plane);
}
}
-// Provides the same functionality as kGraph(DrawLine)
-reg_t GfxFrameout::addPlaneLine(reg_t object, Common::Point startPoint, Common::Point endPoint, byte color, byte priority, byte control) {
- for (PlaneList::iterator it = _planes.begin(); it != _planes.end(); ++it) {
- if (it->object == object) {
- PlaneLineEntry line;
- line.hunkId = _segMan->allocateHunkEntry("PlaneLine()", 1); // we basically use this for a unique ID
- line.startPoint = startPoint;
- line.endPoint = endPoint;
- line.color = color;
- line.priority = priority;
- line.control = control;
- it->lines.push_back(line);
- return line.hunkId;
- }
+void GfxFrameout::kernelUpdatePlane(const reg_t object) {
+ Plane *plane = _planes.findByObject(object);
+ if (plane == nullptr) {
+ error("kUpdatePlane: Plane %04x:%04x not found", PRINT_REG(object));
}
- return NULL_REG;
+ plane->update(object);
+ updatePlane(*plane);
}
-void GfxFrameout::updatePlaneLine(reg_t object, reg_t hunkId, Common::Point startPoint, Common::Point endPoint, byte color, byte priority, byte control) {
- // Check if we're asked to update a line that was never added
- if (hunkId.isNull())
- return;
+void GfxFrameout::kernelDeletePlane(const reg_t object) {
+ Plane *plane = _planes.findByObject(object);
+ if (plane == nullptr) {
+ error("kDeletePlane: Plane %04x:%04x not found", PRINT_REG(object));
+ }
- for (PlaneList::iterator it = _planes.begin(); it != _planes.end(); ++it) {
- if (it->object == object) {
- for (PlaneLineList::iterator it2 = it->lines.begin(); it2 != it->lines.end(); ++it2) {
- if (it2->hunkId == hunkId) {
- it2->startPoint = startPoint;
- it2->endPoint = endPoint;
- it2->color = color;
- it2->priority = priority;
- it2->control = control;
- return;
- }
- }
- }
+ if (plane->_created) {
+ // NOTE: The original engine calls some `AbortPlane` function that
+ // just ends up doing this anyway so we skip the extra indirection
+ _planes.erase(plane);
+ } else {
+ plane->_created = 0;
+ plane->_deleted = g_sci->_gfxFrameout->getScreenCount();
}
}
-void GfxFrameout::deletePlaneLine(reg_t object, reg_t hunkId) {
- // Check if we're asked to delete a line that was never added (happens during the intro of LSL6)
- if (hunkId.isNull())
- return;
+void GfxFrameout::deletePlane(Plane &planeToFind) {
+ Plane *plane = _planes.findByObject(planeToFind._object);
+ if (plane == nullptr) {
+ error("deletePlane: Plane %04x:%04x not found", PRINT_REG(planeToFind._object));
+ }
- for (PlaneList::iterator it = _planes.begin(); it != _planes.end(); ++it) {
- if (it->object == object) {
- for (PlaneLineList::iterator it2 = it->lines.begin(); it2 != it->lines.end(); ++it2) {
- if (it2->hunkId == hunkId) {
- _segMan->freeHunkEntry(hunkId);
- it2 = it->lines.erase(it2);
- return;
- }
- }
- }
+ if (plane->_created) {
+ _planes.erase(plane);
+ } else {
+ plane->_created = 0;
+ plane->_moved = 0;
+ plane->_deleted = getScreenCount();
}
}
-// Adapted from GfxAnimate::applyGlobalScaling()
-void GfxFrameout::applyGlobalScaling(FrameoutEntry *itemEntry, Common::Rect planeRect, int16 celHeight) {
- // Global scaling uses global var 2 and some other stuff to calculate scaleX/scaleY
- int16 maxScale = readSelectorValue(_segMan, itemEntry->object, SELECTOR(maxScale));
- int16 maxCelHeight = (maxScale * celHeight) >> 7;
- reg_t globalVar2 = g_sci->getEngineState()->variables[VAR_GLOBAL][2]; // current room object
- int16 vanishingY = readSelectorValue(_segMan, globalVar2, SELECTOR(vanishingY));
+void GfxFrameout::kernelMovePlaneItems(const reg_t object, const int16 deltaX, const int16 deltaY, const bool scrollPics) {
+ Plane *plane = _planes.findByObject(object);
+ if (plane == nullptr) {
+ error("kMovePlaneItems: Plane %04x:%04x not found", PRINT_REG(object));
+ }
- int16 fixedPortY = planeRect.bottom - vanishingY;
- int16 fixedEntryY = itemEntry->y - vanishingY;
- if (!fixedEntryY)
- fixedEntryY = 1;
+ plane->scrollScreenItems(deltaX, deltaY, scrollPics);
- if ((celHeight == 0) || (fixedPortY == 0))
- error("global scaling panic");
+ for (ScreenItemList::iterator it = plane->_screenItemList.begin(); it != plane->_screenItemList.end(); ++it) {
+ ScreenItem &screenItem = **it;
- itemEntry->scaleY = (maxCelHeight * fixedEntryY) / fixedPortY;
- itemEntry->scaleY = (itemEntry->scaleY * maxScale) / celHeight;
+ // If object is a number, the screen item from the
+ // engine, not a script, and should be ignored
+ if (screenItem._object.isNumber()) {
+ continue;
+ }
- // Make sure that the calculated value is sane
- if (itemEntry->scaleY < 1 /*|| itemEntry->scaleY > 128*/)
- itemEntry->scaleY = 128;
+ if (deltaX != 0) {
+ writeSelectorValue(_segMan, screenItem._object, SELECTOR(x), readSelectorValue(_segMan, screenItem._object, SELECTOR(x)) + deltaX);
+ }
- itemEntry->scaleX = itemEntry->scaleY;
+ if (deltaY != 0) {
+ writeSelectorValue(_segMan, screenItem._object, SELECTOR(y), readSelectorValue(_segMan, screenItem._object, SELECTOR(y)) + deltaY);
+ }
+ }
+}
- // and set objects scale selectors
- //writeSelectorValue(_segMan, itemEntry->object, SELECTOR(scaleX), itemEntry->scaleX);
- //writeSelectorValue(_segMan, itemEntry->object, SELECTOR(scaleY), itemEntry->scaleY);
+int16 GfxFrameout::kernelGetHighPlanePri() {
+ return _planes.getTopSciPlanePriority();
}
-void GfxFrameout::kernelAddScreenItem(reg_t object) {
- // Ignore invalid items
- if (!_segMan->isObject(object)) {
- warning("kernelAddScreenItem: Attempt to add an invalid object (%04x:%04x)", PRINT_REG(object));
- return;
+void GfxFrameout::addPlane(Plane &plane) {
+ if (_planes.findByObject(plane._object) == nullptr) {
+ plane.clipScreenRect(_screenRect);
+ _planes.add(&plane);
+ } else {
+ plane._deleted = 0;
+ if (plane._created == 0) {
+ plane._moved = g_sci->_gfxFrameout->getScreenCount();
+ }
+ _planes.sort();
}
+}
+
+void GfxFrameout::updatePlane(Plane &plane) {
+ // NOTE: This assertion comes from SCI engine code.
+ assert(_planes.findByObject(plane._object) == &plane);
- FrameoutEntry *itemEntry = new FrameoutEntry();
- memset(itemEntry, 0, sizeof(FrameoutEntry));
- itemEntry->object = object;
- itemEntry->givenOrderNr = _screenItems.size();
- itemEntry->visible = true;
- _screenItems.push_back(itemEntry);
+ Plane *visiblePlane = _visiblePlanes.findByObject(plane._object);
+ plane.sync(visiblePlane, _screenRect);
+ // NOTE: updateScreenRect was originally called a second time here,
+ // but it is already called at the end of the Plane::Update call
+ // in the original engine anyway.
- kernelUpdateScreenItem(object);
+ _planes.sort();
}
-void GfxFrameout::kernelUpdateScreenItem(reg_t object) {
- // Ignore invalid items
- if (!_segMan->isObject(object)) {
- warning("kernelUpdateScreenItem: Attempt to update an invalid object (%04x:%04x)", PRINT_REG(object));
- return;
- }
+#pragma mark -
+#pragma mark Pics
- FrameoutEntry *itemEntry = findScreenItem(object);
- if (!itemEntry) {
- warning("kernelUpdateScreenItem: invalid object %04x:%04x", PRINT_REG(object));
- return;
+void GfxFrameout::kernelAddPicAt(const reg_t planeObject, const GuiResourceId pictureId, const int16 x, const int16 y, const bool mirrorX) {
+ Plane *plane = _planes.findByObject(planeObject);
+ if (plane == nullptr) {
+ error("kAddPicAt: Plane %04x:%04x not found", PRINT_REG(planeObject));
}
+ plane->addPic(pictureId, Common::Point(x, y), mirrorX);
+}
- itemEntry->viewId = readSelectorValue(_segMan, object, SELECTOR(view));
- itemEntry->loopNo = readSelectorValue(_segMan, object, SELECTOR(loop));
- itemEntry->celNo = readSelectorValue(_segMan, object, SELECTOR(cel));
- itemEntry->x = readSelectorValue(_segMan, object, SELECTOR(x));
- itemEntry->y = readSelectorValue(_segMan, object, SELECTOR(y));
- itemEntry->z = readSelectorValue(_segMan, object, SELECTOR(z));
- itemEntry->priority = readSelectorValue(_segMan, object, SELECTOR(priority));
- if (readSelectorValue(_segMan, object, SELECTOR(fixPriority)) == 0)
- itemEntry->priority = itemEntry->y;
+#pragma mark -
+#pragma mark Rendering
- itemEntry->signal = readSelectorValue(_segMan, object, SELECTOR(signal));
- itemEntry->scaleSignal = readSelectorValue(_segMan, object, SELECTOR(scaleSignal));
+void GfxFrameout::frameOut(const bool shouldShowBits, const Common::Rect &rect) {
+// TODO: Robot
+// if (_robot != nullptr) {
+// _robot.doRobot();
+// }
- if (itemEntry->scaleSignal & kScaleSignalDoScaling32) {
- itemEntry->scaleX = readSelectorValue(_segMan, object, SELECTOR(scaleX));
- itemEntry->scaleY = readSelectorValue(_segMan, object, SELECTOR(scaleY));
- } else {
- itemEntry->scaleX = 128;
- itemEntry->scaleY = 128;
- }
- itemEntry->visible = true;
+ // NOTE: The original engine allocated these as static arrays of 100
+ // pointers to ScreenItemList / RectList
+ ScreenItemListList screenItemLists;
+ EraseListList eraseLists;
- // Check if the entry can be hidden
- if (lookupSelector(_segMan, object, SELECTOR(visible), NULL, NULL) != kSelectorNone)
- itemEntry->visible = readSelectorValue(_segMan, object, SELECTOR(visible));
-}
+ screenItemLists.resize(_planes.size());
+ eraseLists.resize(_planes.size());
-void GfxFrameout::kernelDeleteScreenItem(reg_t object) {
- FrameoutEntry *itemEntry = findScreenItem(object);
- // If the item could not be found, it may already have been deleted
- if (!itemEntry)
- return;
+ if (g_sci->_gfxRemap32->getRemapCount() > 0 && _remapOccurred) {
+ remapMarkRedraw();
+ }
- _screenItems.remove(itemEntry);
- delete itemEntry;
-}
+ calcLists(screenItemLists, eraseLists, rect);
-void GfxFrameout::deletePlaneItems(reg_t planeObject) {
- FrameoutList::iterator listIterator = _screenItems.begin();
+ for (ScreenItemListList::iterator list = screenItemLists.begin(); list != screenItemLists.end(); ++list) {
+ list->sort();
+ }
- while (listIterator != _screenItems.end()) {
- bool objectMatches = false;
- if (!planeObject.isNull()) {
- reg_t itemPlane = readSelector(_segMan, (*listIterator)->object, SELECTOR(plane));
- objectMatches = (planeObject == itemPlane);
- } else {
- objectMatches = true;
+ for (ScreenItemListList::iterator list = screenItemLists.begin(); list != screenItemLists.end(); ++list) {
+ for (DrawList::iterator drawItem = list->begin(); drawItem != list->end(); ++drawItem) {
+ (*drawItem)->screenItem->getCelObj().submitPalette();
}
+ }
- if (objectMatches) {
- FrameoutEntry *itemEntry = *listIterator;
- listIterator = _screenItems.erase(listIterator);
- delete itemEntry;
- } else {
- ++listIterator;
- }
+ _remapOccurred = _palette->updateForFrame();
+
+ // NOTE: SCI engine set this to false on each loop through the
+ // planelist iterator below. Since that is a waste, we only set
+ // it once.
+ _frameNowVisible = false;
+
+ for (PlaneList::size_type i = 0; i < _planes.size(); ++i) {
+ drawEraseList(eraseLists[i], *_planes[i]);
+ drawScreenItemList(screenItemLists[i]);
}
-}
-FrameoutEntry *GfxFrameout::findScreenItem(reg_t object) {
- for (FrameoutList::iterator listIterator = _screenItems.begin(); listIterator != _screenItems.end(); listIterator++) {
- FrameoutEntry *itemEntry = *listIterator;
- if (itemEntry->object == object)
- return itemEntry;
+// TODO: Robot
+// if (_robot != nullptr) {
+// _robot->frameAlmostVisible();
+// }
+
+ _palette->updateHardware();
+
+ if (shouldShowBits) {
+ showBits();
}
- return NULL;
-}
+ _frameNowVisible = true;
-int16 GfxFrameout::kernelGetHighPlanePri() {
- sortPlanes();
- return readSelectorValue(g_sci->getEngineState()->_segMan, _planes.back().object, SELECTOR(priority));
+// TODO: Robot
+// if (_robot != nullptr) {
+// robot->frameNowVisible();
+// }
}
-void GfxFrameout::kernelAddPicAt(reg_t planeObj, GuiResourceId pictureId, int16 pictureX, int16 pictureY) {
- addPlanePicture(planeObj, pictureId, pictureX, pictureY);
-}
+// Determine the parts of 'r' that aren't overlapped by 'other'.
+// Returns -1 if r and other have no intersection.
+// Returns number of returned parts (in outRects) otherwise.
+// (In particular, this returns 0 if r is contained in other.)
+int splitRects(Common::Rect r, const Common::Rect &other, Common::Rect(&outRects)[4]) {
+ if (!r.intersects(other)) {
+ return -1;
+ }
-bool sortHelper(const FrameoutEntry* entry1, const FrameoutEntry* entry2) {
- if (entry1->priority == entry2->priority) {
- if (entry1->y == entry2->y)
- return (entry1->givenOrderNr < entry2->givenOrderNr);
- return (entry1->y < entry2->y);
+ int count = 0;
+ if (r.top < other.top) {
+ Common::Rect &t = outRects[count++];
+ t = r;
+ t.bottom = other.top;
+ r.top = other.top;
}
- return (entry1->priority < entry2->priority);
-}
-bool planeSortHelper(const PlaneEntry &entry1, const PlaneEntry &entry2) {
- if (entry1.priority < 0)
- return true;
+ if (r.bottom > other.bottom) {
+ Common::Rect &t = outRects[count++];
+ t = r;
+ t.top = other.bottom;
+ r.bottom = other.bottom;
+ }
- if (entry2.priority < 0)
- return false;
+ if (r.left < other.left) {
+ Common::Rect &t = outRects[count++];
+ t = r;
+ t.right = other.left;
+ r.left = other.left;
+ }
- return entry1.priority < entry2.priority;
+ if (r.right > other.right) {
+ Common::Rect &t = outRects[count++];
+ t = r;
+ t.left = other.right;
+ }
+
+ return count;
}
-void GfxFrameout::sortPlanes() {
- // First, remove any invalid planes
- for (PlaneList::iterator it = _planes.begin(); it != _planes.end();) {
- if (!_segMan->isObject(it->object))
- it = _planes.erase(it);
- else
- it++;
+void GfxFrameout::calcLists(ScreenItemListList &drawLists, EraseListList &eraseLists, const Common::Rect &calcRect) {
+ RectList rectlist;
+ Common::Rect outRects[4];
+
+ int deletedPlaneCount = 0;
+ bool addedToRectList = false;
+ int planeCount = _planes.size();
+ bool foundTransparentPlane = false;
+
+ if (!calcRect.isEmpty()) {
+ addedToRectList = true;
+ rectlist.add(calcRect);
}
- // Sort the rest of them
- Common::sort(_planes.begin(), _planes.end(), planeSortHelper);
-}
+ for (int outerPlaneIndex = 0; outerPlaneIndex < planeCount; ++outerPlaneIndex) {
+ Plane *outerPlane = _planes[outerPlaneIndex];
-void GfxFrameout::showVideo() {
- bool skipVideo = false;
- RobotDecoder *videoDecoder = g_sci->_robotDecoder;
- uint16 x = videoDecoder->getPos().x;
- uint16 y = videoDecoder->getPos().y;
- uint16 screenWidth = _screen->getWidth();
- uint16 screenHeight = _screen->getHeight();
- uint16 outputWidth;
- uint16 outputHeight;
+ if (outerPlane->_type == kPlaneTypeTransparent) {
+ foundTransparentPlane = true;
+ }
- if (videoDecoder->hasDirtyPalette())
- g_system->getPaletteManager()->setPalette(videoDecoder->getPalette(), 0, 256);
+ Plane *visiblePlane = _visiblePlanes.findByObject(outerPlane->_object);
- while (!g_engine->shouldQuit() && !videoDecoder->endOfVideo() && !skipVideo) {
- if (videoDecoder->needsUpdate()) {
- const Graphics::Surface *frame = videoDecoder->decodeNextFrame();
- if (frame) {
- // We need to clip here
- // At least Phantasmagoria shows a 640x390 video on a 630x450 screen during the intro
- outputWidth = frame->w > screenWidth ? screenWidth : frame->w;
- outputHeight = frame->h > screenHeight ? screenHeight : frame->h;
- g_system->copyRectToScreen(frame->getPixels(), frame->pitch, x, y, outputWidth, outputHeight);
+ if (outerPlane->_deleted) {
+ if (visiblePlane != nullptr) {
+ if (!visiblePlane->_screenRect.isEmpty()) {
+ addedToRectList = true;
+ rectlist.add(visiblePlane->_screenRect);
+ }
+ }
+ ++deletedPlaneCount;
+ } else if (visiblePlane != nullptr) {
+ if (outerPlane->_updated) {
+ --outerPlane->_updated;
+
+ int splitcount = splitRects(visiblePlane->_screenRect, outerPlane->_screenRect, outRects);
+ if (splitcount) {
+ if (splitcount == -1) {
+ if (!visiblePlane->_screenRect.isEmpty()) {
+ rectlist.add(visiblePlane->_screenRect);
+ }
+ } else {
+ for (int i = 0; i < splitcount; ++i) {
+ rectlist.add(outRects[i]);
+ }
+ }
- if (videoDecoder->hasDirtyPalette())
- g_system->getPaletteManager()->setPalette(videoDecoder->getPalette(), 0, 256);
+ addedToRectList = true;
+ }
- g_system->updateScreen();
+ if (!outerPlane->_redrawAllCount) {
+ int splitCount = splitRects(outerPlane->_screenRect, visiblePlane->_screenRect, outRects);
+ if (splitCount) {
+ for (int i = 0; i < splitCount; ++i) {
+ rectlist.add(outRects[i]);
+ }
+ addedToRectList = true;
+ }
+ }
}
}
- Common::Event event;
- while (g_system->getEventManager()->pollEvent(event)) {
- if ((event.type == Common::EVENT_KEYDOWN && event.kbd.keycode == Common::KEYCODE_ESCAPE) || event.type == Common::EVENT_LBUTTONUP)
- skipVideo = true;
- }
+ if (addedToRectList) {
+ for (RectList::iterator rect = rectlist.begin(); rect != rectlist.end(); ++rect) {
+ for (int innerPlaneIndex = _planes.size() - 1; innerPlaneIndex >= 0; --innerPlaneIndex) {
+ Plane *innerPlane = _planes[innerPlaneIndex];
+
+ if (!innerPlane->_deleted && innerPlane->_type != kPlaneTypeTransparent && innerPlane->_screenRect.intersects(**rect)) {
+ if (innerPlane->_redrawAllCount == 0) {
+ eraseLists[innerPlaneIndex].add(innerPlane->_screenRect.findIntersectingRect(**rect));
+ }
+
+ int splitCount = splitRects(**rect, innerPlane->_screenRect, outRects);
+ for (int i = 0; i < splitCount; ++i) {
+ rectlist.add(outRects[i]);
+ }
+
+ rectlist.erase(rect);
+ break;
+ }
+ }
+ }
- g_system->delayMillis(10);
+ rectlist.pack();
+ }
}
-}
-void GfxFrameout::createPlaneItemList(reg_t planeObject, FrameoutList &itemList) {
- // Copy screen items of the current frame to the list of items to be drawn
- for (FrameoutList::iterator listIterator = _screenItems.begin(); listIterator != _screenItems.end(); listIterator++) {
- reg_t itemPlane = readSelector(_segMan, (*listIterator)->object, SELECTOR(plane));
- if (planeObject == itemPlane) {
- kernelUpdateScreenItem((*listIterator)->object); // TODO: Why is this necessary?
- itemList.push_back(*listIterator);
+ // clean up deleted planes
+ if (deletedPlaneCount) {
+ for (int planeIndex = planeCount - 1; planeIndex >= 0; --planeIndex) {
+ Plane *plane = _planes[planeIndex];
+
+ if (plane->_deleted) {
+ --plane->_deleted;
+ if (plane->_deleted <= 0) {
+ PlaneList::iterator visiblePlaneIt = Common::find_if(_visiblePlanes.begin(), _visiblePlanes.end(), FindByObject<Plane *>(plane->_object));
+ if (visiblePlaneIt != _visiblePlanes.end()) {
+ _visiblePlanes.erase(visiblePlaneIt);
+ }
+
+ _planes.remove_at(planeIndex);
+ eraseLists.remove_at(planeIndex);
+ drawLists.remove_at(planeIndex);
+ }
+
+ if (--deletedPlaneCount <= 0) {
+ break;
+ }
+ }
}
}
- for (PlanePictureList::iterator pictureIt = _planePictures.begin(); pictureIt != _planePictures.end(); pictureIt++) {
- if (pictureIt->object == planeObject) {
- GfxPicture *planePicture = pictureIt->picture;
- // Allocate memory for picture cels
- pictureIt->pictureCels = new FrameoutEntry[planePicture->getSci32celCount()];
+ planeCount = _planes.size();
+ for (int outerIndex = 0; outerIndex < planeCount; ++outerIndex) {
+ // "outer" just refers to the outer loop
+ Plane *outerPlane = _planes[outerIndex];
+ if (outerPlane->_priorityChanged) {
+ --outerPlane->_priorityChanged;
+
+ Plane *visibleOuterPlane = _visiblePlanes.findByObject(outerPlane->_object);
+
+ rectlist.add(outerPlane->_screenRect.findIntersectingRect(visibleOuterPlane->_screenRect));
+
+ for (int innerIndex = planeCount - 1; innerIndex >= 0; --innerIndex) {
+ // "inner" just refers to the inner loop
+ Plane *innerPlane = _planes[innerIndex];
+ Plane *visibleInnerPlane = _visiblePlanes.findByObject(innerPlane->_object);
+
+ int rectCount = rectlist.size();
+ for (int rectIndex = 0; rectIndex < rectCount; ++rectIndex) {
+ int splitCount = splitRects(*rectlist[rectIndex], _planes[innerIndex]->_screenRect, outRects);
+
+ if (splitCount == 0) {
+ if (visibleInnerPlane != nullptr) {
+ // same priority, or relative priority between inner/outer changed
+ if ((visibleOuterPlane->_priority - visibleInnerPlane->_priority) * (outerPlane->_priority - innerPlane->_priority) <= 0) {
+ if (outerPlane->_priority <= innerPlane->_priority) {
+ eraseLists[innerIndex].add(*rectlist[rectIndex]);
+ } else {
+ eraseLists[outerIndex].add(*rectlist[rectIndex]);
+ }
+ }
+ }
+
+ rectlist.erase_at(rectIndex);
+ } else if (splitCount != -1) {
+ for (int i = 0; i < splitCount; ++i) {
+ rectlist.add(outRects[i]);
+ }
+
+ if (visibleInnerPlane != nullptr) {
+ // same priority, or relative priority between inner/outer changed
+ if ((visibleOuterPlane->_priority - visibleInnerPlane->_priority) * (outerPlane->_priority - innerPlane->_priority) <= 0) {
+ *rectlist[rectIndex] = outerPlane->_screenRect.findIntersectingRect(innerPlane->_screenRect);
+ if (outerPlane->_priority <= innerPlane->_priority) {
+ eraseLists[innerIndex].add(*rectlist[rectIndex]);
+ }
+ else {
+ eraseLists[outerIndex].add(*rectlist[rectIndex]);
+ }
+ }
+ }
+ rectlist.erase_at(rectIndex);
+ }
+ }
+ rectlist.pack();
+ }
+ }
+ }
- // Add following cels to the itemlist
- FrameoutEntry *picEntry = pictureIt->pictureCels;
- int planePictureCels = planePicture->getSci32celCount();
- for (int pictureCelNr = 0; pictureCelNr < planePictureCels; pictureCelNr++) {
- picEntry->celNo = pictureCelNr;
- picEntry->object = NULL_REG;
- picEntry->picture = planePicture;
- picEntry->y = planePicture->getSci32celY(pictureCelNr);
- picEntry->x = planePicture->getSci32celX(pictureCelNr);
- picEntry->picStartX = pictureIt->startX;
- picEntry->picStartY = pictureIt->startY;
- picEntry->visible = true;
+ for (int planeIndex = 0; planeIndex < planeCount; ++planeIndex) {
+ Plane *plane = _planes[planeIndex];
+ Plane *visiblePlane = nullptr;
- picEntry->priority = planePicture->getSci32celPriority(pictureCelNr);
+ PlaneList::iterator visiblePlaneIt = Common::find_if(_visiblePlanes.begin(), _visiblePlanes.end(), FindByObject<Plane *>(plane->_object));
+ if (visiblePlaneIt != _visiblePlanes.end()) {
+ visiblePlane = *visiblePlaneIt;
+ }
- itemList.push_back(picEntry);
- picEntry++;
+ if (plane->_redrawAllCount) {
+ plane->redrawAll(visiblePlane, _planes, drawLists[planeIndex], eraseLists[planeIndex]);
+ } else {
+ if (visiblePlane == nullptr) {
+ error("Missing visible plane for source plane %04x:%04x", PRINT_REG(plane->_object));
}
+
+ plane->calcLists(*visiblePlane, _planes, drawLists[planeIndex], eraseLists[planeIndex]);
+ }
+
+ if (plane->_created) {
+ _visiblePlanes.add(new Plane(*plane));
+ --plane->_created;
+ } else if (plane->_moved) {
+ assert(visiblePlaneIt != _visiblePlanes.end());
+ **visiblePlaneIt = *plane;
+ --plane->_moved;
}
}
- // Now sort our itemlist
- Common::sort(itemList.begin(), itemList.end(), sortHelper);
-}
+ if (foundTransparentPlane) {
+ for (int planeIndex = 0; planeIndex < planeCount; ++planeIndex) {
+ for (int i = planeIndex + 1; i < planeCount; ++i) {
+ if (_planes[i]->_type == kPlaneTypeTransparent) {
+ _planes[i]->filterUpEraseRects(drawLists[i], eraseLists[planeIndex]);
+ }
+ }
-bool GfxFrameout::isPictureOutOfView(FrameoutEntry *itemEntry, Common::Rect planeRect, int16 planeOffsetX, int16 planeOffsetY) {
- // Out of view horizontally (sanity checks)
- int16 pictureCelStartX = itemEntry->picStartX + itemEntry->x;
- int16 pictureCelEndX = pictureCelStartX + itemEntry->picture->getSci32celWidth(itemEntry->celNo);
- int16 planeStartX = planeOffsetX;
- int16 planeEndX = planeStartX + planeRect.width();
- if (pictureCelEndX < planeStartX)
- return true;
- if (pictureCelStartX > planeEndX)
- return true;
+ if (_planes[planeIndex]->_type == kPlaneTypeTransparent) {
+ for (int i = planeIndex - 1; i >= 0; --i) {
+ _planes[i]->filterDownEraseRects(drawLists[i], eraseLists[i], eraseLists[planeIndex]);
+ }
- // Out of view vertically (sanity checks)
- int16 pictureCelStartY = itemEntry->picStartY + itemEntry->y;
- int16 pictureCelEndY = pictureCelStartY + itemEntry->picture->getSci32celHeight(itemEntry->celNo);
- int16 planeStartY = planeOffsetY;
- int16 planeEndY = planeStartY + planeRect.height();
- if (pictureCelEndY < planeStartY)
- return true;
- if (pictureCelStartY > planeEndY)
- return true;
+ if (eraseLists[planeIndex].size() > 0) {
+ error("Transparent plane's erase list not absorbed");
+ }
+ }
- return false;
+ for (int i = planeIndex + 1; i < planeCount; ++i) {
+ if (_planes[i]->_type == kPlaneTypeTransparent) {
+ _planes[i]->filterUpDrawRects(drawLists[i], drawLists[planeIndex]);
+ }
+ }
+ }
+ }
}
-void GfxFrameout::drawPicture(FrameoutEntry *itemEntry, int16 planeOffsetX, int16 planeOffsetY, bool planePictureMirrored) {
- int16 pictureOffsetX = planeOffsetX;
- int16 pictureX = itemEntry->x;
- if ((planeOffsetX) || (itemEntry->picStartX)) {
- if (planeOffsetX <= itemEntry->picStartX) {
- pictureX += itemEntry->picStartX - planeOffsetX;
- pictureOffsetX = 0;
- } else {
- pictureOffsetX = planeOffsetX - itemEntry->picStartX;
- }
+void GfxFrameout::drawEraseList(const RectList &eraseList, const Plane &plane) {
+ if (plane._type != kPlaneTypeColored) {
+ return;
}
- int16 pictureOffsetY = planeOffsetY;
- int16 pictureY = itemEntry->y;
- if ((planeOffsetY) || (itemEntry->picStartY)) {
- if (planeOffsetY <= itemEntry->picStartY) {
- pictureY += itemEntry->picStartY - planeOffsetY;
- pictureOffsetY = 0;
- } else {
- pictureOffsetY = planeOffsetY - itemEntry->picStartY;
+ for (RectList::const_iterator it = eraseList.begin(); it != eraseList.end(); ++it) {
+ mergeToShowList(**it, _showList, _overdrawThreshold);
+ _currentBuffer.fillRect(**it, plane._back);
+ }
+}
+
+void GfxFrameout::drawScreenItemList(const DrawList &screenItemList) {
+ for (DrawList::const_iterator it = screenItemList.begin(); it != screenItemList.end(); ++it) {
+ DrawItem &drawItem = **it;
+ mergeToShowList(drawItem.rect, _showList, _overdrawThreshold);
+ ScreenItem &screenItem = *drawItem.screenItem;
+ // TODO: Remove
+// debug("Drawing item %04x:%04x to %d %d %d %d", PRINT_REG(screenItem._object), PRINT_RECT(drawItem.rect));
+ CelObj &celObj = *screenItem._celObj;
+ celObj.draw(_currentBuffer, screenItem, drawItem.rect, screenItem._mirrorX ^ celObj._mirrorX);
+ }
+}
+
+void GfxFrameout::mergeToShowList(const Common::Rect &drawRect, RectList &showList, const int overdrawThreshold) {
+ Common::Rect merged(drawRect);
+
+ bool didDelete = true;
+ RectList::size_type count = showList.size();
+ while (didDelete && count) {
+ didDelete = false;
+
+ for (RectList::size_type i = 0; i < count; ++i) {
+ Common::Rect existing = *showList[i];
+ Common::Rect candidate;
+ candidate.left = MIN(merged.left, existing.left);
+ candidate.top = MIN(merged.top, existing.top);
+ candidate.right = MAX(merged.right, existing.right);
+ candidate.bottom = MAX(merged.bottom, existing.bottom);
+
+ if (candidate.height() * candidate.width() - merged.width() * merged.height() - existing.width() * existing.height() <= overdrawThreshold) {
+ merged = candidate;
+ showList.erase_at(i);
+ didDelete = true;
+ }
}
+
+ count = showList.pack();
}
- itemEntry->picture->drawSci32Vga(itemEntry->celNo, pictureX, itemEntry->y, pictureOffsetX, pictureOffsetY, planePictureMirrored);
- // warning("picture cel %d %d", itemEntry->celNo, itemEntry->priority);
+ showList.add(merged);
}
-/* TODO: This is the proper implementation of GraphicsMgr::FrameOut transcribed from SQ6 SCI engine disassembly.
-static DrawList* g_drawLists[100];
-static RectList* g_rectLists[100];
-void GfxFrameout::FrameOut(bool shouldShowBits, SOL_Rect *rect) {
- if (robot) {
- robot.doRobot();
+void GfxFrameout::palMorphFrameOut(const int8 *styleRanges, const ShowStyleEntry *showStyle) {
+ Palette sourcePalette(*_palette->getNextPalette());
+ alterVmap(sourcePalette, sourcePalette, -1, styleRanges);
+
+ int16 prevRoom = g_sci->getEngineState()->variables[VAR_GLOBAL][12].toSint16();
+
+ Common::Rect rect(_screen->getDisplayWidth(), _screen->getDisplayHeight());
+ _showList.add(rect);
+ showBits();
+
+ Common::Rect calcRect(0, 0);
+
+ // NOTE: The original engine allocated these as static arrays of 100
+ // pointers to ScreenItemList / RectList
+ ScreenItemListList screenItemLists;
+ EraseListList eraseLists;
+
+ screenItemLists.resize(_planes.size());
+ eraseLists.resize(_planes.size());
+
+ if (g_sci->_gfxRemap32->getRemapCount() > 0 && _remapOccurred) {
+ remapMarkRedraw();
}
- auto planeCount = screen.planeList.planeCount;
- if (planeCount > 0) {
- for (int planeIndex = 0; planeIndex < planeCount; ++planeIndex) {
- Plane plane = *screen.planeList[planeIndex];
+ calcLists(screenItemLists, eraseLists, calcRect);
+ for (ScreenItemListList::iterator list = screenItemLists.begin(); list != screenItemLists.end(); ++list) {
+ list->sort();
+ }
- DrawList* drawList = new DrawList();
- g_drawLists[planeIndex] = drawList;
- RectList* rectList = new RectList();
- g_rectLists[planeIndex] = rectList;
+ for (ScreenItemListList::iterator list = screenItemLists.begin(); list != screenItemLists.end(); ++list) {
+ for (DrawList::iterator drawItem = list->begin(); drawItem != list->end(); ++drawItem) {
+ (*drawItem)->screenItem->getCelObj().submitPalette();
}
}
-
- if (g_Remap_numActiveRemaps > 0 && remapNeeded) {
- screen.RemapMarkRedraw();
+
+ _remapOccurred = _palette->updateForFrame();
+ _frameNowVisible = false;
+
+ for (PlaneList::size_type i = 0; i < _planes.size(); ++i) {
+ drawEraseList(eraseLists[i], *_planes[i]);
+ drawScreenItemList(screenItemLists[i]);
}
-
- CalcLists(&g_drawLists, &g_rectLists, rect);
- // SCI engine stores reference *after* CalcLists
- planeCount = screen.planeList.planeCount;
- if (planeCount > 0) {
- for (int drawListIndex = 0; drawListIndex < planeCount; ++i) {
- DrawList* drawList = g_drawLists[drawListIndex];
- drawList->Sort();
- }
+ Palette nextPalette(*_palette->getNextPalette());
- for (int drawListIndex = 0; drawListIndex < planeCount; ++i) {
- DrawList* drawList = g_drawLists[drawListIndex];
- if (drawList == nullptr || drawList->count == 0) {
- continue;
+ if (prevRoom < 1000) {
+ for (int i = 0; i < ARRAYSIZE(sourcePalette.colors); ++i) {
+ if (styleRanges[i] == -1 || styleRanges[i] == 0) {
+ sourcePalette.colors[i] = nextPalette.colors[i];
+ sourcePalette.colors[i].used = true;
}
-
- for (int screenItemIndex = 0, screenItemCount = drawList->count; screenItemIndex < screenItemCount; ++screenItemIndex) {
- ScreenItem* screenItem = drawList->items[screenItemIndex];
- screenItem->GetCelObj()->SubmitPalette();
+ }
+ } else {
+ for (int i = 0; i < ARRAYSIZE(sourcePalette.colors); ++i) {
+ if (styleRanges[i] == -1 || (styleRanges[i] == 0 && i > 71 && i < 104)) {
+ sourcePalette.colors[i] = nextPalette.colors[i];
+ sourcePalette.colors[i].used = true;
}
}
}
- // UpdateForFrame is where all palette mutations occur (cycles, varies, etc.)
- bool remapNeeded = GPalette().UpdateForFrame();
- if (planeCount > 0) {
- frameNowVisible = false;
+ _palette->submit(sourcePalette);
+ _palette->updateFFrame();
+ _palette->updateHardware();
+ alterVmap(nextPalette, sourcePalette, 1, _styleRanges);
- for (int planeIndex = 0; planeIndex < planeCount; ++planeIndex) {
- Plane* plane = screen.planeList[planeIndex];
+ if (showStyle && showStyle->type != kShowStyleUnknown) {
+// TODO: SCI2.1mid transition effects
+// processEffects();
+ warning("Transition %d not implemented!", showStyle->type);
+ } else {
+ showBits();
+ }
+
+ _frameNowVisible = true;
- DrawEraseList(g_rectLists[planeIndex], plane);
- DrawScreenItemsList(g_drawLists[planeIndex]);
+ for (PlaneList::iterator plane = _planes.begin(); plane != _planes.end(); ++plane) {
+ (*plane)->_redrawAllCount = getScreenCount();
+ }
+
+ if (g_sci->_gfxRemap32->getRemapCount() > 0 && _remapOccurred) {
+ remapMarkRedraw();
+ }
+
+ calcLists(screenItemLists, eraseLists, calcRect);
+ for (ScreenItemListList::iterator list = screenItemLists.begin(); list != screenItemLists.end(); ++list) {
+ list->sort();
+ }
+
+ for (ScreenItemListList::iterator list = screenItemLists.begin(); list != screenItemLists.end(); ++list) {
+ for (DrawList::iterator drawItem = list->begin(); drawItem != list->end(); ++drawItem) {
+ (*drawItem)->screenItem->getCelObj().submitPalette();
}
}
- if (robot) {
- robot.FrameAlmostVisible();
+ _remapOccurred = _palette->updateForFrame();
+ // NOTE: During this second loop, `_frameNowVisible = false` is
+ // inside the next loop in SCI2.1mid
+ _frameNowVisible = false;
+
+ for (PlaneList::size_type i = 0; i < _planes.size(); ++i) {
+ drawEraseList(eraseLists[i], *_planes[i]);
+ drawScreenItemList(screenItemLists[i]);
}
- GPalette().UpdateHardware();
+ _palette->submit(nextPalette);
+ _palette->updateFFrame();
+ _palette->updateHardware();
+ showBits();
- if (shouldShowBits) {
- ShowBits();
+ _frameNowVisible = true;
+}
+
+// TODO: What does the bit masking for the show rects do,
+// and does it cause an off-by-one error in rect calculations
+// since SOL_Rect is BR inclusive and Common::Rect is BR
+// exclusive?
+void GfxFrameout::showBits() {
+ for (RectList::const_iterator rect = _showList.begin(); rect != _showList.end(); ++rect) {
+ Common::Rect rounded(**rect);
+ // NOTE: SCI engine used BR-inclusive rects so used slightly
+ // different masking here to ensure that the width of rects
+ // was always even.
+ rounded.left &= ~1;
+ rounded.right = (rounded.right + 1) & ~1;
+
+ // TODO:
+ // _cursor->GonnaPaint(rounded);
}
- frameNowVisible = true;
+ // TODO:
+ // _cursor->PaintStarting();
+
+ for (RectList::const_iterator rect = _showList.begin(); rect != _showList.end(); ++rect) {
+ Common::Rect rounded(**rect);
+ // NOTE: SCI engine used BR-inclusive rects so used slightly
+ // different masking here to ensure that the width of rects
+ // was always even.
+ rounded.left &= ~1;
+ rounded.right = (rounded.right + 1) & ~1;
- if (robot) {
- robot.FrameNowVisible();
+ byte *sourceBuffer = (byte *)_currentBuffer.getPixels() + rounded.top * _currentBuffer.screenWidth + rounded.left;
+
+ g_system->copyRectToScreen(sourceBuffer, _currentBuffer.screenWidth, rounded.left, rounded.top, rounded.width(), rounded.height());
}
- if (planeCount > 0) {
- for (int planeIndex = 0; planeIndex < planeCount; ++planeIndex) {
- if (g_rectLists[planeIndex] != nullptr) {
- delete g_rectLists[planeIndex];
+ // TODO:
+ // _cursor->DonePainting();
+
+ _showList.clear();
+}
+
+void GfxFrameout::alterVmap(const Palette &palette1, const Palette &palette2, const int8 style, const int8 *const styleRanges) {
+ uint8 clut[256];
+
+ for (int paletteIndex = 0; paletteIndex < ARRAYSIZE(palette1.colors); ++paletteIndex) {
+ int outerR = palette1.colors[paletteIndex].r;
+ int outerG = palette1.colors[paletteIndex].g;
+ int outerB = palette1.colors[paletteIndex].b;
+
+ if (styleRanges[paletteIndex] == style) {
+ int minDiff = 262140;
+ int minDiffIndex;
+
+ for (int i = 0; i < 236; ++i) {
+ if (styleRanges[i] != style) {
+ int r = palette1.colors[i].r;
+ int g = palette1.colors[i].g;
+ int b = palette1.colors[i].b;
+ int diffSquared = (outerR - r) * (outerR - r) + (outerG - g) * (outerG - g) + (outerB - b) * (outerB - b);
+ if (diffSquared < minDiff) {
+ minDiff = diffSquared;
+ minDiffIndex = i;
+ }
+ }
}
- if (g_drawLists[planeIndex] != nullptr) {
- delete g_drawLists[planeIndex];
+
+ clut[paletteIndex] = minDiffIndex;
+ }
+
+ if (style == 1 && styleRanges[paletteIndex] == 0) {
+ int minDiff = 262140;
+ int minDiffIndex;
+
+ for (int i = 0; i < 236; ++i) {
+ int r = palette2.colors[i].r;
+ int g = palette2.colors[i].g;
+ int b = palette2.colors[i].b;
+
+ int diffSquared = (outerR - r) * (outerR - r) + (outerG - g) * (outerG - g) + (outerB - b) * (outerB - b);
+ if (diffSquared < minDiff) {
+ minDiff = diffSquared;
+ minDiffIndex = i;
+ }
}
+
+ clut[paletteIndex] = minDiffIndex;
+ }
+ }
+
+ // NOTE: This is currBuffer->ptr in SCI engine
+ byte *pixels = (byte *)_currentBuffer.getPixels();
+
+ for (int pixelIndex = 0, numPixels = _currentBuffer.screenWidth * _currentBuffer.screenHeight; pixelIndex < numPixels; ++pixelIndex) {
+ byte currentValue = pixels[pixelIndex];
+ int8 styleRangeValue = styleRanges[currentValue];
+ if (styleRangeValue == -1 && styleRangeValue == style) {
+ currentValue = pixels[pixelIndex] = clut[currentValue];
+ // NOTE: In original engine this assignment happens outside of the
+ // condition, but if the branch is not followed the value is just
+ // going to be the same as it was before
+ styleRangeValue = styleRanges[currentValue];
+ }
+
+ if (
+ (styleRangeValue == 1 && styleRangeValue == style) ||
+ (styleRangeValue == 0 && style == 1)
+ ) {
+ pixels[pixelIndex] = clut[currentValue];
}
}
}
-void GfxFrameout::CalcLists(DrawList **drawLists, RectList **rectLists, SOL_Rect *rect) {
- screen.CalcLists(&visibleScreen, drawLists, rectLists, rect);
-}
-*/
-void GfxFrameout::kernelFrameout() {
- if (g_sci->_robotDecoder->isVideoLoaded()) {
- showVideo();
+
+void GfxFrameout::kernelSetPalStyleRange(const uint8 fromColor, const uint8 toColor) {
+ if (toColor > fromColor) {
return;
}
- _palette->updateForFrame();
+ for (int i = fromColor; i < toColor; ++i) {
+ _styleRanges[i] = 0;
+ }
+}
- // TODO: Tons of drawing stuff should be here, see commented out implementation above
+inline ShowStyleEntry * GfxFrameout::findShowStyleForPlane(const reg_t planeObj) const {
+ ShowStyleEntry *entry = _showStyles;
+ while (entry != nullptr) {
+ if (entry->plane == planeObj) {
+ break;
+ }
+ entry = entry->next;
+ }
- _palette->updateHardware();
+ return entry;
+}
- for (PlaneList::iterator it = _planes.begin(); it != _planes.end(); it++) {
- reg_t planeObject = it->object;
+inline ShowStyleEntry *GfxFrameout::deleteShowStyleInternal(ShowStyleEntry *const showStyle) {
+ ShowStyleEntry *lastEntry = nullptr;
- // Draw any plane lines, if they exist
- // These are drawn on invisible planes as well. (e.g. "invisiblePlane" in LSL6 hires)
- // FIXME: Lines aren't always drawn (e.g. when the narrator speaks in LSL6 hires).
- // Perhaps something is painted over them?
- for (PlaneLineList::iterator it2 = it->lines.begin(); it2 != it->lines.end(); ++it2) {
- Common::Point startPoint = it2->startPoint;
- Common::Point endPoint = it2->endPoint;
- _coordAdjuster->kernelLocalToGlobal(startPoint.x, startPoint.y, it->object);
- _coordAdjuster->kernelLocalToGlobal(endPoint.x, endPoint.y, it->object);
- _screen->drawLine(startPoint, endPoint, it2->color, it2->priority, it2->control);
+ for (ShowStyleEntry *testEntry = _showStyles; testEntry != nullptr; testEntry = testEntry->next) {
+ if (testEntry == showStyle) {
+ break;
}
+ lastEntry = testEntry;
+ }
- int16 planeLastPriority = it->lastPriority;
+ if (lastEntry == nullptr) {
+ _showStyles = showStyle->next;
+ lastEntry = _showStyles;
+ } else {
+ lastEntry->next = showStyle->next;
+ }
- // Update priority here, sq6 sets it w/o UpdatePlane
- int16 planePriority = it->priority = readSelectorValue(_segMan, planeObject, SELECTOR(priority));
+ delete[] showStyle->fadeColorRanges;
+ delete showStyle;
- it->lastPriority = planePriority;
- 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);
- continue;
- }
+ // TODO: Verify that this is the correct entry to return
+ // for the loop in processShowStyles to work correctly
+ return lastEntry;
+}
- // There is a race condition lurking in SQ6, which causes the game to hang in the intro, when teleporting to Polysorbate LX.
- // Since I first wrote the patch, the race has stopped occurring for me though.
- // I'll leave this for investigation later, when someone can reproduce.
- //if (it->pictureId == kPlanePlainColored) // FIXME: This is what SSCI does, and fixes the intro of LSL7, but breaks the dialogs in GK1 (adds black boxes)
- if (it->pictureId == kPlanePlainColored && (it->planeBack || g_sci->getGameId() != GID_GK1))
- _paint32->fillRect(it->planeRect, it->planeBack);
+// TODO: 10-argument version is only in SCI3; argc checks are currently wrong for this version
+// and need to be fixed in future
+// TODO: SQ6 does not use 'priority' (exists since SCI2) or 'blackScreen' (exists since SCI3);
+// check to see if other versions use or if they are just always ignored
+void GfxFrameout::kernelSetShowStyle(const uint16 argc, const reg_t planeObj, const ShowStyleType type, const int16 seconds, const int16 back, const int16 priority, const int16 animate, const int16 frameOutNow, reg_t pFadeArray, int16 divisions, const int16 blackScreen) {
+
+ bool hasDivisions = false;
+ bool hasFadeArray = false;
+
+ // KQ7 2.0b uses a mismatched version of the Styler script (SCI2.1early script
+ // for SCI2.1mid engine), so the calls it makes to kSetShowStyle are wrong and
+ // put `divisions` where `pFadeArray` is supposed to be
+ if (getSciVersion() == SCI_VERSION_2_1_MIDDLE && g_sci->getGameId() == GID_KQ7) {
+ hasDivisions = argc > 7;
+ hasFadeArray = false;
+ divisions = argc > 7 ? pFadeArray.toSint16() : -1;
+ pFadeArray = NULL_REG;
+ } else if (getSciVersion() < SCI_VERSION_2_1_MIDDLE) {
+ hasDivisions = argc > 7;
+ hasFadeArray = false;
+ } else if (getSciVersion() < SCI_VERSION_3) {
+ hasDivisions = argc > 8;
+ hasFadeArray = argc > 7;
+ } else {
+ hasDivisions = argc > 9;
+ hasFadeArray = argc > 8;
+ }
- _coordAdjuster->pictureSetDisplayArea(it->planeRect);
- // Invoking drewPicture() with an invalid picture ID in SCI32 results in
- // invalidating the palVary palette when a palVary effect is active. This
- // is quite obvious in QFG4, where the day time palette is incorrectly
- // shown when exiting the caves, and the correct night time palette
- // flashes briefly each time that kPalVaryInit is called.
- if (it->pictureId != 0xFFFF)
- _palette->drewPicture(it->pictureId);
+ bool isFadeUp;
+ int16 color;
+ if (back != -1) {
+ isFadeUp = false;
+ color = back;
+ } else {
+ isFadeUp = true;
+ color = 0;
+ }
- FrameoutList itemList;
+ if ((getSciVersion() < SCI_VERSION_2_1_MIDDLE && type == 15) || type > 15) {
+ error("Illegal show style %d for plane %04x:%04x", type, PRINT_REG(planeObj));
+ }
- createPlaneItemList(planeObject, itemList);
+ Plane *plane = _planes.findByObject(planeObj);
+ if (plane == nullptr) {
+ error("Plane %04x:%04x is not present in active planes list", PRINT_REG(planeObj));
+ }
- for (FrameoutList::iterator listIterator = itemList.begin(); listIterator != itemList.end(); listIterator++) {
- FrameoutEntry *itemEntry = *listIterator;
+ bool createNewEntry = true;
+ ShowStyleEntry *entry = findShowStyleForPlane(planeObj);
+ if (entry != nullptr) {
+ // TODO: SCI2.1early has different criteria for show style reuse
+ bool useExisting = true;
- if (!itemEntry->visible)
- continue;
+ if (useExisting) {
+ useExisting = entry->divisions == (hasDivisions ? divisions : _defaultDivisions[type]) && entry->unknownC == _defaultUnknownC[type];
+ }
- if (itemEntry->object.isNull()) {
- // Picture cel data
- _coordAdjuster->fromScriptToDisplay(itemEntry->y, itemEntry->x);
- _coordAdjuster->fromScriptToDisplay(itemEntry->picStartY, itemEntry->picStartX);
+ if (useExisting) {
+ createNewEntry = false;
+ isFadeUp = true;
+ entry->currentStep = 0;
+ } else {
+ isFadeUp = true;
+ color = entry->color;
+ deleteShowStyleInternal(entry/*, true*/);
+ entry = nullptr;
+ }
+ }
- if (!isPictureOutOfView(itemEntry, it->planeRect, it->planeOffsetX, it->planeOffsetY))
- drawPicture(itemEntry, it->planeOffsetX, it->planeOffsetY, it->planePictureMirrored);
- } else {
- GfxView *view = (itemEntry->viewId != 0xFFFF) ? _cache->getView(itemEntry->viewId) : NULL;
- int16 dummyX = 0;
-
- if (view && view->isSci2Hires()) {
- view->adjustToUpscaledCoordinates(itemEntry->y, itemEntry->x);
- view->adjustToUpscaledCoordinates(itemEntry->z, dummyX);
- } else if (getSciVersion() >= SCI_VERSION_2_1_EARLY) {
- _coordAdjuster->fromScriptToDisplay(itemEntry->y, itemEntry->x);
- _coordAdjuster->fromScriptToDisplay(itemEntry->z, dummyX);
+ if (type > 0) {
+ if (createNewEntry) {
+ entry = new ShowStyleEntry;
+ // NOTE: SCI2.1 engine tests if allocation returned a null pointer
+ // but then only avoids setting currentStep if this is so. Since
+ // this is a nonsensical approach, we do not do that here
+ entry->currentStep = 0;
+ entry->unknownC = _defaultUnknownC[type];
+ entry->processed = false;
+ entry->divisions = hasDivisions ? divisions : _defaultDivisions[type];
+ entry->plane = planeObj;
+
+ entry->fadeColorRanges = nullptr;
+ if (hasFadeArray) {
+ // NOTE: SCI2.1mid engine does no check to verify that an array is
+ // successfully retrieved, and SegMan will cause a fatal error
+ // if we try to use a memory segment that is not an array
+ SciArray<reg_t> *table = _segMan->lookupArray(pFadeArray);
+
+ uint32 rangeCount = table->getSize();
+ entry->fadeColorRangesCount = rangeCount;
+
+ // NOTE: SCI engine code always allocates memory even if the range
+ // table has no entries, but this does not really make sense, so
+ // we avoid the allocation call in this case
+ if (rangeCount > 0) {
+ entry->fadeColorRanges = new uint16[rangeCount];
+ for (size_t i = 0; i < rangeCount; ++i) {
+ entry->fadeColorRanges[i] = table->getValue(i).toUint16();
+ }
}
+ } else {
+ entry->fadeColorRangesCount = 0;
+ }
+ }
- // Adjust according to current scroll position
- itemEntry->x -= it->planeOffsetX;
- itemEntry->y -= it->planeOffsetY;
-
- uint16 useInsetRect = readSelectorValue(_segMan, itemEntry->object, SELECTOR(useInsetRect));
- if (useInsetRect) {
- itemEntry->celRect.top = readSelectorValue(_segMan, itemEntry->object, SELECTOR(inTop));
- itemEntry->celRect.left = readSelectorValue(_segMan, itemEntry->object, SELECTOR(inLeft));
- itemEntry->celRect.bottom = readSelectorValue(_segMan, itemEntry->object, SELECTOR(inBottom));
- itemEntry->celRect.right = readSelectorValue(_segMan, itemEntry->object, SELECTOR(inRight));
- if (view && view->isSci2Hires()) {
- view->adjustToUpscaledCoordinates(itemEntry->celRect.top, itemEntry->celRect.left);
- view->adjustToUpscaledCoordinates(itemEntry->celRect.bottom, itemEntry->celRect.right);
- }
- itemEntry->celRect.translate(itemEntry->x, itemEntry->y);
- // TODO: maybe we should clip the cels rect with this, i'm not sure
- // the only currently known usage is game menu of gk1
- } else if (view) {
- // Process global scaling, if needed.
- // TODO: Seems like SCI32 always processes global scaling for scaled objects
- // TODO: We can only process symmetrical scaling for now (i.e. same value for scaleX/scaleY)
- if ((itemEntry->scaleSignal & kScaleSignalDoScaling32) &&
- !(itemEntry->scaleSignal & kScaleSignalDisableGlobalScaling32) &&
- (itemEntry->scaleX == itemEntry->scaleY) &&
- itemEntry->scaleX != 128)
- applyGlobalScaling(itemEntry, it->planeRect, view->getHeight(itemEntry->loopNo, itemEntry->celNo));
-
- if ((itemEntry->scaleX == 128) && (itemEntry->scaleY == 128))
- view->getCelRect(itemEntry->loopNo, itemEntry->celNo,
- itemEntry->x, itemEntry->y, itemEntry->z, itemEntry->celRect);
- 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
- nsRect.translate(it->planeOffsetX, it->planeOffsetY);
-
- if (g_sci->getGameId() == GID_PHANTASMAGORIA2) {
- // HACK: Some (?) objects in Phantasmagoria 2 have no NS rect. Skip them for now.
- // TODO: Remove once we figure out how Phantasmagoria 2 draws objects on screen.
- if (lookupSelector(_segMan, itemEntry->object, SELECTOR(nsLeft), NULL, NULL) != kSelectorVariable)
- continue;
- }
+ // NOTE: The original engine had no nullptr check and would just crash
+ // if it got to here
+ if (entry == nullptr) {
+ error("Cannot edit non-existing ShowStyle entry");
+ }
- if (view && view->isSci2Hires()) {
- view->adjustBackUpscaledCoordinates(nsRect.top, nsRect.left);
- view->adjustBackUpscaledCoordinates(nsRect.bottom, nsRect.right);
- g_sci->_gfxCompare->setNSRect(itemEntry->object, nsRect);
- } else if (getSciVersion() >= SCI_VERSION_2_1_EARLY && _isHiRes) {
- _coordAdjuster->fromDisplayToScript(nsRect.top, nsRect.left);
- _coordAdjuster->fromDisplayToScript(nsRect.bottom, nsRect.right);
- g_sci->_gfxCompare->setNSRect(itemEntry->object, nsRect);
- }
+ entry->fadeUp = isFadeUp;
+ entry->color = color;
+ entry->nextTick = g_sci->getTickCount();
+ entry->type = type;
+ entry->animate = animate;
+ entry->delay = (seconds * 60 + entry->divisions - 1) / entry->divisions;
- // TODO: For some reason, the top left nsRect coordinates get
- // swapped in the GK1 inventory screen, investigate why.
- // This is also needed for GK1 rooms 710 and 720 (catacombs, inner and
- // outer circle), for handling the tiles and talking to Wolfgang.
- // HACK: Fix the coordinates by explicitly setting them here for GK1.
- // Also check bug #6729, for another case where this is needed.
- if (g_sci->getGameId() == GID_GK1)
- g_sci->_gfxCompare->setNSRect(itemEntry->object, nsRect);
- }
+ if (entry->delay == 0) {
+ if (entry->fadeColorRanges != nullptr) {
+ delete[] entry->fadeColorRanges;
+ }
+ delete entry;
+ error("ShowStyle has no duration");
+ }
- // 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;
-
- Common::Rect clipRect, translatedClipRect;
- clipRect = itemEntry->celRect;
-
- if (view && view->isSci2Hires()) {
- clipRect.clip(it->upscaledPlaneClipRect);
- translatedClipRect = clipRect;
- translatedClipRect.translate(it->upscaledPlaneRect.left, it->upscaledPlaneRect.top);
- } else {
- // QFG4 passes invalid rectangles when a battle is starting
- if (!clipRect.isValidRect())
- continue;
- clipRect.clip(it->planeClipRect);
- translatedClipRect = clipRect;
- translatedClipRect.translate(it->planeRect.left, it->planeRect.top);
- }
+ if (frameOutNow) {
+ Common::Rect frameOutRect(0, 0);
+ frameOut(false, frameOutRect);
+ }
+
+ if (createNewEntry) {
+ // TODO: Implement SCI2.1early and SCI3
+ entry->next = _showStyles;
+ _showStyles = entry;
+ }
+ }
+}
- if (view) {
- if (!clipRect.isEmpty()) {
- if ((itemEntry->scaleX == 128) && (itemEntry->scaleY == 128))
- view->draw(itemEntry->celRect, clipRect, translatedClipRect,
- itemEntry->loopNo, itemEntry->celNo, 255, 0, view->isSci2Hires());
- else
- view->drawScaled(itemEntry->celRect, clipRect, translatedClipRect,
- itemEntry->loopNo, itemEntry->celNo, 255, itemEntry->scaleX, itemEntry->scaleY);
+// NOTE: Different version of SCI engine support different show styles
+// SCI2 implements 0, 1/3/5/7/9, 2/4/6/8/10, 11, 12, 13, 14
+// SCI2.1 implements 0, 1/2/3/4/5/6/7/8/9/10/11/12/15, 13, 14
+// SCI3 implements 0, 1/3/5/7/9, 2/4/6/8/10, 11, 12/15, 13, 14
+// TODO: Sierra code needs to be replaced with code that uses the
+// computed entry->delay property instead of just counting divisors,
+// as the latter is machine-speed-dependent and leads to wrong
+// transition speeds
+void GfxFrameout::processShowStyles() {
+ uint32 now = g_sci->getTickCount();
+
+ bool continueProcessing;
+
+ // TODO: Change to bool? Engine uses inc to set the value to true,
+ // but there does not seem to be any reason to actually count how
+ // many times it was set
+ int doFrameOut;
+ do {
+ continueProcessing = false;
+ doFrameOut = 0;
+ ShowStyleEntry *showStyle = _showStyles;
+ while (showStyle != nullptr) {
+ bool retval = false;
+
+ if (!showStyle->animate) {
+ ++doFrameOut;
+ }
+
+ if (showStyle->nextTick < now || !showStyle->animate) {
+ // TODO: Different versions of SCI use different processors!
+ // This is the SQ6/KQ7/SCI2.1mid table.
+ switch (showStyle->type) {
+ case kShowStyleNone: {
+ retval = processShowStyleNone(showStyle);
+ break;
+ }
+ case kShowStyleHShutterOut:
+ case kShowStyleVShutterOut:
+ case kShowStyleWipeLeft:
+ case kShowStyleWipeUp:
+ case kShowStyleIrisOut:
+ case kShowStyleHShutterIn:
+ case kShowStyleVShutterIn:
+ case kShowStyleWipeRight:
+ case kShowStyleWipeDown:
+ case kShowStyleIrisIn:
+ case kShowStyle11:
+ case kShowStyle12:
+ case kShowStyleUnknown: {
+ retval = processShowStyleMorph(showStyle);
+ break;
+ }
+ case kShowStyleFadeOut: {
+ retval = processShowStyleFade(-1, showStyle);
+ break;
+ }
+ case kShowStyleFadeIn: {
+ retval = processShowStyleFade(1, showStyle);
+ break;
}
}
+ }
- // Draw text, if it exists
- if (lookupSelector(_segMan, itemEntry->object, SELECTOR(text), NULL, NULL) == kSelectorVariable) {
- g_sci->_gfxText32->drawTextBitmap(itemEntry->x, itemEntry->y, it->planeRect, itemEntry->object);
- }
+ if (!retval) {
+ continueProcessing = true;
}
- }
- for (PlanePictureList::iterator pictureIt = _planePictures.begin(); pictureIt != _planePictures.end(); pictureIt++) {
- if (pictureIt->object == planeObject) {
- delete[] pictureIt->pictureCels;
- pictureIt->pictureCels = 0;
+ if (retval && showStyle->processed) {
+ showStyle = deleteShowStyleInternal(showStyle);
+ } else {
+ showStyle = showStyle->next;
}
}
- }
- showCurrentScrollText();
+ if (doFrameOut) {
+ frameOut(true);
+
+ // TODO: Transitions without the “animate” flag are too
+ // fast, but the throttle value is arbitrary. Someone on
+ // real hardware probably needs to test what the actual
+ // speed of these transitions should be
+ EngineState *state = g_sci->getEngineState();
+ state->speedThrottler(33);
+ state->_throttleTrigger = true;
+ }
+ } while(continueProcessing && doFrameOut);
+}
- _screen->copyToScreen();
+bool GfxFrameout::processShowStyleNone(ShowStyleEntry *const showStyle) {
+ if (showStyle->fadeUp) {
+ _palette->setFade(100, 0, 255);
+ } else {
+ _palette->setFade(0, 0, 255);
+ }
- g_sci->getEngineState()->_throttleTrigger = true;
+ showStyle->processed = true;
+ return true;
}
-void GfxFrameout::printPlaneList(Console *con) {
- for (PlaneList::const_iterator it = _planes.begin(); it != _planes.end(); ++it) {
- PlaneEntry p = *it;
- Common::String curPlaneName = _segMan->getObjectName(p.object);
- Common::Rect r = p.upscaledPlaneRect;
- Common::Rect cr = p.upscaledPlaneClipRect;
-
- con->debugPrintf("%04x:%04x (%s): prio %d, lastprio %d, offsetX %d, offsetY %d, pic %d, mirror %d, back %d\n",
- PRINT_REG(p.object), curPlaneName.c_str(),
- (int16)p.priority, (int16)p.lastPriority,
- p.planeOffsetX, p.planeOffsetY, p.pictureId,
- p.planePictureMirrored, p.planeBack);
- con->debugPrintf(" rect: (%d, %d, %d, %d), clip rect: (%d, %d, %d, %d)\n",
- r.left, r.top, r.right, r.bottom,
- cr.left, cr.top, cr.right, cr.bottom);
-
- if (p.pictureId != 0xffff && p.pictureId != 0xfffe) {
- con->debugPrintf("Pictures:\n");
-
- for (PlanePictureList::iterator pictureIt = _planePictures.begin(); pictureIt != _planePictures.end(); pictureIt++) {
- if (pictureIt->object == p.object) {
- con->debugPrintf(" Picture %d: x %d, y %d\n", pictureIt->pictureId, pictureIt->startX, pictureIt->startY);
- }
+bool GfxFrameout::processShowStyleMorph(ShowStyleEntry *const showStyle) {
+ palMorphFrameOut(_styleRanges, showStyle);
+ showStyle->processed = true;
+ return true;
+}
+
+// TODO: Normalise use of 'entry' vs 'showStyle'
+bool GfxFrameout::processShowStyleFade(const int direction, ShowStyleEntry *const showStyle) {
+ bool unchanged = true;
+ if (showStyle->currentStep < showStyle->divisions) {
+ int percent;
+ if (direction <= 0) {
+ percent = showStyle->divisions - showStyle->currentStep - 1;
+ } else {
+ percent = showStyle->currentStep;
+ }
+
+ percent *= 100;
+ percent /= showStyle->divisions - 1;
+
+ if (showStyle->fadeColorRangesCount > 0) {
+ for (int i = 0, len = showStyle->fadeColorRangesCount; i < len; i += 2) {
+ _palette->setFade(percent, showStyle->fadeColorRanges[i], showStyle->fadeColorRanges[i + 1]);
}
+ } else {
+ _palette->setFade(percent, 0, 255);
}
+
+ ++showStyle->currentStep;
+ showStyle->nextTick += showStyle->delay;
+ unchanged = false;
+ }
+
+ if (showStyle->currentStep >= showStyle->divisions && unchanged) {
+ if (direction > 0) {
+ showStyle->processed = true;
+ }
+
+ return true;
+ }
+
+ return false;
+}
+
+void GfxFrameout::kernelFrameOut(const bool shouldShowBits) {
+ if (_showStyles != nullptr) {
+ processShowStyles();
+ } else if (_palMorphIsOn) {
+ palMorphFrameOut(_styleRanges, nullptr);
+ _palMorphIsOn = false;
+ } else {
+// TODO: Window scroll
+// if (g_PlaneScroll) {
+// processScrolls();
+// }
+
+ frameOut(shouldShowBits);
+ }
+}
+
+#pragma mark -
+#pragma mark Mouse cursor
+
+reg_t GfxFrameout::kernelIsOnMe(const reg_t object, const Common::Point &position, bool checkPixel) const {
+ const reg_t planeObject = readSelector(_segMan, object, SELECTOR(plane));
+ Plane *plane = _visiblePlanes.findByObject(planeObject);
+ if (plane == nullptr) {
+ return make_reg(0, 0);
+ }
+
+ ScreenItem *screenItem = plane->_screenItemList.findByObject(object);
+ if (screenItem == nullptr) {
+ return make_reg(0, 0);
}
+
+ // NOTE: The original engine passed a copy of the ScreenItem into isOnMe
+ // as a hack around the fact that the screen items in `_visiblePlanes`
+ // did not have their `_celObj` pointers cleared when their CelInfo was
+ // updated by `Plane::decrementScreenItemArrayCounts`. We handle this
+ // this more intelligently by clearing `_celObj` in the copy assignment
+ // operator, which is only ever called by `decrementScreenItemArrayCounts`
+ // anyway.
+ return make_reg(0, isOnMe(*screenItem, *plane, position, checkPixel));
}
-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));
+bool GfxFrameout::isOnMe(const ScreenItem &screenItem, const Plane &plane, const Common::Point &position, const bool checkPixel) const {
+
+ Common::Point scaledPosition(position);
+ mulru(scaledPosition, Ratio(_currentBuffer.screenWidth, _currentBuffer.scriptWidth), Ratio(_currentBuffer.screenHeight, _currentBuffer.scriptHeight));
+ scaledPosition.x += plane._planeRect.left;
+ scaledPosition.y += plane._planeRect.top;
+
+ if (!screenItem._screenRect.contains(scaledPosition)) {
+ return false;
+ }
+
+ if (checkPixel) {
+ CelObj &celObj = screenItem.getCelObj();
+
+ bool mirrorX = screenItem._mirrorX ^ celObj._mirrorX;
+
+ scaledPosition.x -= screenItem._scaledPosition.x;
+ scaledPosition.y -= screenItem._scaledPosition.y;
- if (planeObject == itemPlane) {
- Common::String curItemName = _segMan->getObjectName(e->object);
- Common::Rect icr = e->celRect;
- GuiResourceId picId = e->picture ? e->picture->getResourceId() : 0;
+ mulru(scaledPosition, Ratio(celObj._scaledWidth, _currentBuffer.screenWidth), Ratio(celObj._scaledHeight, _currentBuffer.screenHeight));
- con->debugPrintf("%d: %04x:%04x (%s), view %d, loop %d, cel %d, x %d, y %d, z %d, "
- "signal %d, scale signal %d, scaleX %d, scaleY %d, rect (%d, %d, %d, %d), "
- "pic %d, picX %d, picY %d, visible %d\n",
- e->givenOrderNr, PRINT_REG(e->object), curItemName.c_str(),
- e->viewId, e->loopNo, e->celNo, e->x, e->y, e->z,
- e->signal, e->scaleSignal, e->scaleX, e->scaleY,
- icr.left, icr.top, icr.right, icr.bottom,
- picId, e->picStartX, e->picStartY, e->visible);
+ if (screenItem._scale.signal != kScaleSignalNone && screenItem._scale.x && screenItem._scale.y) {
+ scaledPosition.x = scaledPosition.x * 128 / screenItem._scale.x;
+ scaledPosition.y = scaledPosition.y * 128 / screenItem._scale.y;
}
+
+ uint8 pixel = celObj.readPixel(scaledPosition.x, scaledPosition.y, mirrorX);
+ return pixel != celObj._transparentColor;
}
+
+ return true;
+}
+
+void GfxFrameout::kernelSetNowSeen(const reg_t screenItemObject) const {
+ const reg_t planeObject = readSelector(_segMan, screenItemObject, SELECTOR(plane));
+
+ Plane *plane = _planes.findByObject(planeObject);
+ if (plane == nullptr) {
+ error("kSetNowSeen: Plane %04x:%04x not found for screen item %04x:%04x", PRINT_REG(planeObject), PRINT_REG(screenItemObject));
+ }
+
+ ScreenItem *screenItem = plane->_screenItemList.findByObject(screenItemObject);
+ if (screenItem == nullptr) {
+ error("kSetNowSeen: Screen item %04x:%04x not found in plane %04x:%04x", PRINT_REG(screenItemObject), PRINT_REG(planeObject));
+ }
+
+ Common::Rect result = screenItem->getNowSeenRect(*plane);
+ writeSelectorValue(_segMan, screenItemObject, SELECTOR(nsLeft), result.left);
+ writeSelectorValue(_segMan, screenItemObject, SELECTOR(nsTop), result.top);
+ writeSelectorValue(_segMan, screenItemObject, SELECTOR(nsRight), result.right - 1);
+ writeSelectorValue(_segMan, screenItemObject, SELECTOR(nsBottom), result.bottom - 1);
+}
+
+void GfxFrameout::remapMarkRedraw() {
+ for (PlaneList::const_iterator it = _planes.begin(); it != _planes.end(); ++it) {
+ Plane *p = *it;
+ p->remapMarkRedraw();
+ }
+}
+
+#pragma mark -
+#pragma mark Debugging
+
+void GfxFrameout::printPlaneListInternal(Console *con, const PlaneList &planeList) const {
+ for (PlaneList::const_iterator it = planeList.begin(); it != planeList.end(); ++it) {
+ Plane *p = *it;
+ p->printDebugInfo(con);
+ }
+}
+
+void GfxFrameout::printPlaneList(Console *con) const {
+ printPlaneListInternal(con, _planes);
+}
+
+void GfxFrameout::printVisiblePlaneList(Console *con) const {
+ printPlaneListInternal(con, _visiblePlanes);
+}
+
+void GfxFrameout::printPlaneItemListInternal(Console *con, const ScreenItemList &screenItemList) const {
+ ScreenItemList::size_type i = 0;
+ for (ScreenItemList::const_iterator sit = screenItemList.begin(); sit != screenItemList.end(); sit++) {
+ ScreenItem *screenItem = *sit;
+ con->debugPrintf("%2d: ", i++);
+ screenItem->printDebugInfo(con);
+ }
+}
+
+void GfxFrameout::printPlaneItemList(Console *con, const reg_t planeObject) const {
+ Plane *p = _planes.findByObject(planeObject);
+
+ if (p == nullptr) {
+ con->debugPrintf("Plane does not exist");
+ return;
+ }
+
+ printPlaneItemListInternal(con, p->_screenItemList);
+}
+
+void GfxFrameout::printVisiblePlaneItemList(Console *con, const reg_t planeObject) const {
+ Plane *p = _visiblePlanes.findByObject(planeObject);
+
+ if (p == nullptr) {
+ con->debugPrintf("Plane does not exist");
+ return;
+ }
+
+ printPlaneItemListInternal(con, p->_screenItemList);
}
} // End of namespace Sci
diff --git a/engines/sci/graphics/frameout.h b/engines/sci/graphics/frameout.h
index d1a706e8de..8ed95a00de 100644
--- a/engines/sci/graphics/frameout.h
+++ b/engines/sci/graphics/frameout.h
@@ -23,86 +23,132 @@
#ifndef SCI_GRAPHICS_FRAMEOUT_H
#define SCI_GRAPHICS_FRAMEOUT_H
-namespace Sci {
+#include "sci/graphics/plane32.h"
+#include "sci/graphics/screen_item32.h"
-class GfxPicture;
+namespace Sci {
+// TODO: Don't do this this way
+int splitRects(Common::Rect r, const Common::Rect &other, Common::Rect(&outRects)[4]);
-struct PlaneLineEntry {
- reg_t hunkId;
- Common::Point startPoint;
- Common::Point endPoint;
- byte color;
- byte priority;
- byte control;
+// TODO: Verify display styles and adjust names appropriately for
+// types 1 through 12 & 15 (others are correct)
+// Names should be:
+// * VShutterIn, VShutterOut
+// * HShutterIn, HShutterOut
+// * WipeLeft, WipeRight, WipeDown, WipeUp
+// * PixelDissolve
+// * ShutDown and Kill? (and Plain and Fade?)
+enum ShowStyleType /* : uint8 */ {
+ kShowStyleNone = 0,
+ kShowStyleHShutterOut = 1,
+ kShowStyleHShutterIn = 2,
+ kShowStyleVShutterOut = 3,
+ kShowStyleVShutterIn = 4,
+ kShowStyleWipeLeft = 5,
+ kShowStyleWipeRight = 6,
+ kShowStyleWipeUp = 7,
+ kShowStyleWipeDown = 8,
+ kShowStyleIrisOut = 9,
+ kShowStyleIrisIn = 10,
+ kShowStyle11 = 11,
+ kShowStyle12 = 12,
+ kShowStyleFadeOut = 13,
+ kShowStyleFadeIn = 14,
+ // TODO: Only in SCI3
+ kShowStyleUnknown = 15
};
-typedef Common::List<PlaneLineEntry> PlaneLineList;
-
-struct PlaneEntry {
- reg_t object;
- int16 priority;
- int16 lastPriority;
- int16 planeOffsetX;
- int16 planeOffsetY;
- GuiResourceId pictureId;
- Common::Rect planeRect;
- Common::Rect planeClipRect;
- Common::Rect upscaledPlaneRect;
- Common::Rect upscaledPlaneClipRect;
- bool planePictureMirrored;
- byte planeBack;
- PlaneLineList lines;
-};
+/**
+ * Show styles represent transitions applied to draw planes.
+ * One show style per plane can be active at a time.
+ */
+struct ShowStyleEntry {
+ /**
+ * The ID of the plane this show style belongs to.
+ * In SCI2.1mid (at least SQ6), per-plane transitions
+ * were removed and a single plane ID is used.
+ */
+ reg_t plane;
-typedef Common::List<PlaneEntry> PlaneList;
-
-struct FrameoutEntry {
- uint16 givenOrderNr;
- reg_t object;
- GuiResourceId viewId;
- int16 loopNo;
- int16 celNo;
- int16 x, y, z;
- int16 priority;
- uint16 signal;
- uint16 scaleSignal;
- int16 scaleX;
- int16 scaleY;
- Common::Rect celRect;
- GfxPicture *picture;
- int16 picStartX;
- int16 picStartY;
- bool visible;
-};
+ /**
+ * The type of the transition.
+ */
+ ShowStyleType type;
-typedef Common::List<FrameoutEntry *> FrameoutList;
+ // TODO: This name is probably incorrect
+ bool fadeUp;
-struct PlanePictureEntry {
- reg_t object;
- int16 startX;
- int16 startY;
- GuiResourceId pictureId;
- GfxPicture *picture;
- FrameoutEntry *pictureCels; // temporary
-};
+ /**
+ * The number of steps for the show style.
+ */
+ int16 divisions;
-typedef Common::List<PlanePictureEntry> PlanePictureList;
+ // NOTE: This property exists from SCI2 through at least
+ // SCI2.1mid but is never used in the actual processing
+ // of the styles?
+ int unknownC;
-struct ScrollTextEntry {
- reg_t bitmapHandle;
- reg_t kWindow;
- uint16 x;
- uint16 y;
-};
+ /**
+ * The color used by transitions that draw CelObjColor
+ * screen items. -1 for transitions that do not draw
+ * screen items.
+ */
+ int16 color;
+
+ // TODO: Probably uint32
+ // TODO: This field probably should be used in order to
+ // provide time-accurate processing of show styles. In the
+ // actual SCI engine (at least 2–2.1mid) it appears that
+ // style transitions are drawn “as fast as possible”, one
+ // step per loop, even though this delay field exists
+ int delay;
+
+ // TODO: Probably bool, but never seems to be true?
+ int animate;
-typedef Common::Array<ScrollTextEntry> ScrollTextList;
+ /**
+ * The wall time at which the next step of the animation
+ * should execute.
+ */
+ uint32 nextTick;
-enum ViewScaleSignals32 {
- kScaleSignalDoScaling32 = 0x0001, // enables scaling when drawing that cel (involves scaleX and scaleY)
- kScaleSignalUnk1 = 0x0002, // unknown
- kScaleSignalDisableGlobalScaling32 = 0x0004
+ /**
+ * During playback of the show style, the current step
+ * (out of divisions).
+ */
+ int currentStep;
+
+ /**
+ * The next show style.
+ */
+ ShowStyleEntry *next;
+
+ /**
+ * Whether or not this style has finished running and
+ * is ready for disposal.
+ */
+ bool processed;
+
+ //
+ // Engine specific properties for SCI2.1mid through SCI3
+ //
+
+ /**
+ * The number of entries in the fadeColorRanges array.
+ */
+ uint8 fadeColorRangesCount;
+
+ /**
+ * A pointer to an dynamically sized array of palette
+ * indexes, in the order [ fromColor, toColor, ... ].
+ * Only colors within this range are transitioned.
+ */
+ uint16 *fadeColorRanges;
};
+typedef Common::Array<DrawList> ScreenItemListList;
+typedef Common::Array<RectList> EraseListList;
+
class GfxCache;
class GfxCoordAdjuster32;
class GfxPaint32;
@@ -114,69 +160,293 @@ class GfxScreen;
* Roughly equivalent to GraphicsMgr in the actual SCI engine.
*/
class GfxFrameout {
+private:
+ bool _isHiRes;
+ GfxCache *_cache;
+ GfxCoordAdjuster32 *_coordAdjuster;
+ GfxPalette32 *_palette;
+ ResourceManager *_resMan;
+ GfxScreen *_screen;
+ SegManager *_segMan;
+ GfxPaint32 *_paint32;
+
public:
GfxFrameout(SegManager *segMan, ResourceManager *resMan, GfxCoordAdjuster *coordAdjuster, GfxCache *cache, GfxScreen *screen, GfxPalette32 *palette, GfxPaint32 *paint32);
~GfxFrameout();
- void kernelAddPlane(reg_t object);
- void kernelUpdatePlane(reg_t object);
- void kernelDeletePlane(reg_t object);
- void applyGlobalScaling(FrameoutEntry *itemEntry, Common::Rect planeRect, int16 celHeight);
- void kernelAddScreenItem(reg_t object);
- void kernelUpdateScreenItem(reg_t object);
- void kernelDeleteScreenItem(reg_t object);
- void deletePlaneItems(reg_t planeObject);
- FrameoutEntry *findScreenItem(reg_t object);
- int16 kernelGetHighPlanePri();
- void kernelAddPicAt(reg_t planeObj, GuiResourceId pictureId, int16 pictureX, int16 pictureY);
- void kernelFrameout();
-
- void addPlanePicture(reg_t object, GuiResourceId pictureId, uint16 startX, uint16 startY = 0);
- void deletePlanePictures(reg_t object);
- reg_t addPlaneLine(reg_t object, Common::Point startPoint, Common::Point endPoint, byte color, byte priority, byte control);
- void updatePlaneLine(reg_t object, reg_t hunkId, Common::Point startPoint, Common::Point endPoint, byte color, byte priority, byte control);
- void deletePlaneLine(reg_t object, reg_t hunkId);
void clear();
+ void syncWithScripts(bool addElements); // this is what Game::restore does, only needed when our ScummVM dialogs are patched in
+ void run();
- // Scroll text functions
- void addScrollTextEntry(Common::String &text, reg_t kWindow, uint16 x, uint16 y, bool replace);
- void showCurrentScrollText();
- void initScrollText(uint16 maxItems) { _maxScrollTexts = maxItems; }
- void clearScrollTexts();
- void firstScrollText() { if (_scrollTexts.size() > 0) _curScrollText = 0; }
- void lastScrollText() { if (_scrollTexts.size() > 0) _curScrollText = _scrollTexts.size() - 1; }
- void prevScrollText() { if (_curScrollText > 0) _curScrollText--; }
- void nextScrollText() { if (_curScrollText + 1 < (uint16)_scrollTexts.size()) _curScrollText++; }
- void toggleScrollText(bool show) { _showScrollText = show; }
+#pragma mark -
+#pragma mark Screen items
+private:
+ void deleteScreenItem(ScreenItem *screenItem, const reg_t plane);
+ void remapMarkRedraw();
- void printPlaneList(Console *con);
- void printPlaneItemList(Console *con, reg_t planeObject);
+public:
+ void kernelAddScreenItem(const reg_t object);
+ void kernelUpdateScreenItem(const reg_t object);
+ void kernelDeleteScreenItem(const reg_t object);
+ void kernelSetNowSeen(const reg_t screenItemObject) const;
+#pragma mark -
+#pragma mark Planes
private:
- bool _isHiRes;
+ /**
+ * The list of planes (i.e. layers) that have been added
+ * to the screen.
+ *
+ * @note This field is on `GraphicsMgr.screen` in SCI
+ * engine.
+ */
+ PlaneList _planes;
- void showVideo();
- void createPlaneItemList(reg_t planeObject, FrameoutList &itemList);
- bool isPictureOutOfView(FrameoutEntry *itemEntry, Common::Rect planeRect, int16 planeOffsetX, int16 planeOffsetY);
- void drawPicture(FrameoutEntry *itemEntry, int16 planeOffsetX, int16 planeOffsetY, bool planePictureMirrored);
+ /**
+ * Updates an existing plane with properties from the
+ * given VM object.
+ */
+ void updatePlane(Plane &plane);
- SegManager *_segMan;
- ResourceManager *_resMan;
- GfxCoordAdjuster32 *_coordAdjuster;
- GfxCache *_cache;
- GfxPalette32 *_palette;
- GfxScreen *_screen;
- GfxPaint32 *_paint32;
+public:
+ /**
+ * Creates and adds a new plane to the plane list, or
+ * cancels deletion and updates an already-existing
+ * plane if a plane matching the given plane VM object
+ * already exists within the current plane list.
+ *
+ * @note This method is on Screen in SCI engine, but it
+ * is only ever called on `GraphicsMgr.screen`.
+ */
+ void addPlane(Plane &plane);
- FrameoutList _screenItems;
- PlaneList _planes;
- PlanePictureList _planePictures;
- ScrollTextList _scrollTexts;
- int16 _curScrollText;
- bool _showScrollText;
- uint16 _maxScrollTexts;
+ /**
+ * Deletes a plane within the current plane list.
+ *
+ * @note This method is on Screen in SCI engine, but it
+ * is only ever called on `GraphicsMgr.screen`.
+ */
+ void deletePlane(Plane &plane);
- void sortPlanes();
+ const PlaneList &getPlanes() const {
+ return _planes;
+ }
+ const PlaneList &getVisiblePlanes() const {
+ return _visiblePlanes;
+ }
+ void kernelAddPlane(const reg_t object);
+ void kernelUpdatePlane(const reg_t object);
+ void kernelDeletePlane(const reg_t object);
+ void kernelMovePlaneItems(const reg_t object, const int16 deltaX, const int16 deltaY, const bool scrollPics);
+ int16 kernelGetHighPlanePri();
+
+#pragma mark -
+#pragma mark Pics
+public:
+ void kernelAddPicAt(const reg_t planeObject, const GuiResourceId pictureId, const int16 pictureX, const int16 pictureY, const bool mirrorX);
+
+#pragma mark -
+
+ // TODO: Remap-related?
+ void kernelSetPalStyleRange(const uint8 fromColor, const uint8 toColor);
+
+#pragma mark -
+#pragma mark Transitions
+private:
+ int *_dissolveSequenceSeeds;
+ int16 *_defaultDivisions;
+ int16 *_defaultUnknownC;
+
+ /**
+ * TODO: Documentation
+ */
+ ShowStyleEntry *_showStyles;
+
+ inline ShowStyleEntry *findShowStyleForPlane(const reg_t planeObj) const;
+ inline ShowStyleEntry *deleteShowStyleInternal(ShowStyleEntry *const showStyle);
+ void processShowStyles();
+ bool processShowStyleNone(ShowStyleEntry *showStyle);
+ bool processShowStyleMorph(ShowStyleEntry *showStyle);
+ bool processShowStyleFade(const int direction, ShowStyleEntry *showStyle);
+
+public:
+ // NOTE: This signature is taken from SCI3 Phantasmagoria 2
+ // and is valid for all implementations of SCI32
+ void kernelSetShowStyle(const uint16 argc, const reg_t planeObj, const ShowStyleType type, const int16 seconds, const int16 direction, const int16 priority, const int16 animate, const int16 frameOutNow, reg_t pFadeArray, int16 divisions, const int16 blackScreen);
+
+#pragma mark -
+#pragma mark Rendering
+private:
+ /**
+ * TODO: Documentation
+ */
+ int8 _styleRanges[256];
+
+ /**
+ * The internal display pixel buffer. During frameOut,
+ * this buffer is drawn into according to the draw and
+ * erase rects calculated by `calcLists`, then drawn out
+ * to the hardware surface according to the `_showList`
+ * rects (which are also calculated by `calcLists`).
+ */
+ Buffer _currentBuffer;
+
+ /**
+ * TODO: Documentation
+ */
+ bool _remapOccurred;
+
+ /**
+ * Whether or not the data in the current buffer is what
+ * is visible to the user. During rendering updates,
+ * this flag is set to false.
+ */
+ bool _frameNowVisible;
+
+ /**
+ * TODO: Document
+ * TODO: Depending upon if the engine ever modifies this
+ * rect, it may be stupid to store it separately instead
+ * of just getting width/height from GfxScreen.
+ *
+ * @note This field is on `GraphicsMgr.screen` in SCI
+ * engine.
+ */
+ Common::Rect _screenRect;
+
+ /**
+ * A list of rectangles, in display coordinates, that
+ * represent portions of the internal screen buffer that
+ * should be drawn to the hardware display surface.
+ *
+ * @note This field is on `GraphicsMgr.screen` in SCI
+ * engine.
+ */
+ RectList _showList;
+
+ /**
+ * The amount of extra overdraw that is acceptable when
+ * merging two show list rectangles together into a
+ * single larger rectangle.
+ *
+ * @note This field is on `GraphicsMgr.screen` in SCI
+ * engine.
+ */
+ int _overdrawThreshold;
+
+ /**
+ * A list of planes that are currently drawn to the
+ * hardware display surface. Used to calculate
+ * differences in plane properties between the last
+ * frame and current frame.
+ *
+ * @note This field is on `GraphicsMgr.visibleScreen` in
+ * SCI engine.
+ */
+ PlaneList _visiblePlanes;
+
+ /**
+ * Calculates the location and dimensions of dirty rects
+ * over the entire screen for rendering the next frame.
+ * The draw and erase lists in `drawLists` and
+ * `eraseLists` each represent one plane on the screen.
+ */
+ void calcLists(ScreenItemListList &drawLists, EraseListList &eraseLists, const Common::Rect &calcRect);
+
+ /**
+ * Erases the areas in the given erase list from the
+ * visible screen buffer by filling them with the color
+ * from the corresponding plane. This is an optimisation
+ * for colored-type planes only; other plane types have
+ * to be redrawn from pixel data.
+ */
+ void drawEraseList(const RectList &eraseList, const Plane &plane);
+
+ /**
+ * Draws all screen items from the given draw list to
+ * the visible screen buffer.
+ */
+ void drawScreenItemList(const DrawList &screenItemList);
+
+ /**
+ * Adds a new rectangle to the list of regions to write
+ * out to the hardware. The provided rect may be merged
+ * into an existing rectangle to reduce the number of
+ * blit operations.
+ */
+ void mergeToShowList(const Common::Rect &drawRect, RectList &showList, const int overdrawThreshold);
+
+ /**
+ * TODO: Documentation
+ */
+ void palMorphFrameOut(const int8 *styleRanges, const ShowStyleEntry *showStyle);
+
+ /**
+ * Writes the internal frame buffer out to hardware and
+ * clears the show list.
+ */
+ void showBits();
+
+public:
+ /**
+ * Whether palMorphFrameOut should be used instead of
+ * frameOut for rendering. Used by kMorphOn to
+ * explicitly enable palMorphFrameOut for one frame.
+ */
+ bool _palMorphIsOn;
+
+ inline Buffer &getCurrentBuffer() {
+ return _currentBuffer;
+ }
+
+ void kernelFrameOut(const bool showBits);
+
+ /**
+ * Updates the internal screen buffer for the next
+ * frame. If `shouldShowBits` is true, also sends the
+ * buffer to hardware.
+ */
+ void frameOut(const bool shouldShowBits, const Common::Rect &rect = Common::Rect());
+
+ /**
+ * Modifies the raw pixel data for the next frame with
+ * new palette indexes based on matched style ranges.
+ */
+ void alterVmap(const Palette &palette1, const Palette &palette2, const int8 style, const int8 *const styleRanges);
+
+ // NOTE: This function is used within ScreenItem subsystem and assigned
+ // to various booleanish fields that seem to represent the state of the
+ // screen item (created, updated, deleted). In GK1/DOS, Phant1/m68k,
+ // SQ6/DOS, SQ6/Win, and Phant2/Win, this function simply returns 1. If
+ // you know of any game/environment where this function returns some
+ // value other than 1, or if you used to work at Sierra and can explain
+ // why this is a thing (and if anyone needs to care about it), please
+ // open a ticket!!
+ inline int getScreenCount() const {
+ return 1;
+ };
+
+#pragma mark -
+#pragma mark Mouse cursor
+private:
+ /**
+ * Determines whether or not the point given by
+ * `position` is inside of the given screen item.
+ */
+ bool isOnMe(const ScreenItem &screenItem, const Plane &plane, const Common::Point &position, const bool checkPixel) const;
+
+public:
+ reg_t kernelIsOnMe(const reg_t object, const Common::Point &position, const bool checkPixel) const;
+
+#pragma mark -
+#pragma mark Debugging
+public:
+ void printPlaneList(Console *con) const;
+ void printVisiblePlaneList(Console *con) const;
+ void printPlaneListInternal(Console *con, const PlaneList &planeList) const;
+ void printPlaneItemList(Console *con, const reg_t planeObject) const;
+ void printVisiblePlaneItemList(Console *con, const reg_t planeObject) const;
+ void printPlaneItemListInternal(Console *con, const ScreenItemList &screenItemList) const;
};
} // End of namespace Sci
diff --git a/engines/sci/graphics/helpers.h b/engines/sci/graphics/helpers.h
index e5b9f2aaed..19dddd74b8 100644
--- a/engines/sci/graphics/helpers.h
+++ b/engines/sci/graphics/helpers.h
@@ -26,6 +26,11 @@
#include "common/endian.h" // for READ_LE_UINT16
#include "common/rect.h"
#include "common/serializer.h"
+#ifdef ENABLE_SCI32
+#include "common/rational.h"
+#include "graphics/pixelformat.h"
+#include "graphics/surface.h"
+#endif
#include "sci/engine/vm_types.h"
namespace Sci {
@@ -45,6 +50,9 @@ typedef int16 TextAlignment;
#define PORTS_FIRSTWINDOWID 2
#define PORTS_FIRSTSCRIPTWINDOWID 3
+#ifdef ENABLE_SCI32
+#define PRINT_RECT(x) (x).left,(x).top,(x).right,(x).bottom
+#endif
struct Port {
uint16 id;
@@ -118,6 +126,102 @@ struct Window : public Port, public Common::Serializable {
}
};
+#ifdef ENABLE_SCI32
+/**
+ * Multiplies a rectangle by two ratios with default
+ * rounding. Modifies the rect directly.
+ */
+inline void mul(Common::Rect &rect, const Common::Rational &ratioX, const Common::Rational &ratioY) {
+ rect.left = (rect.left * ratioX).toInt();
+ rect.top = (rect.top * ratioY).toInt();
+ rect.right = (rect.right * ratioX).toInt();
+ rect.bottom = (rect.bottom * ratioY).toInt();
+}
+
+/**
+ * Multiplies a rectangle by two ratios with default
+ * rounding. Modifies the rect directly. Uses inclusive
+ * rectangle rounding.
+ */
+inline void mulinc(Common::Rect &rect, const Common::Rational &ratioX, const Common::Rational &ratioY) {
+ rect.left = (rect.left * ratioX).toInt();
+ rect.top = (rect.top * ratioY).toInt();
+ rect.right = ((rect.right - 1) * ratioX).toInt() + 1;
+ rect.bottom = ((rect.bottom - 1) * ratioY).toInt() + 1;
+}
+
+/**
+ * Multiplies a number by a rational number, rounding up to
+ * the nearest whole number.
+ */
+inline int mulru(const int value, const Common::Rational &ratio, const int extra = 0) {
+ int num = (value + extra) * ratio.getNumerator();
+ int result = num / ratio.getDenominator();
+ if (num > ratio.getDenominator() && num % ratio.getDenominator()) {
+ ++result;
+ }
+ return result - extra;
+}
+
+/**
+ * Multiplies a point by two rational numbers for X and Y,
+ * rounding up to the nearest whole number. Modifies the
+ * point directly.
+ */
+inline void mulru(Common::Point &point, const Common::Rational &ratioX, const Common::Rational &ratioY) {
+ point.x = mulru(point.x, ratioX);
+ point.y = mulru(point.y, ratioY);
+}
+
+/**
+ * Multiplies a point by two rational numbers for X and Y,
+ * rounding up to the nearest whole number. Modifies the
+ * rect directly.
+ */
+inline void mulru(Common::Rect &rect, const Common::Rational &ratioX, const Common::Rational &ratioY, const int extra) {
+ rect.left = mulru(rect.left, ratioX);
+ rect.top = mulru(rect.top, ratioY);
+ rect.right = mulru(rect.right - 1, ratioX, extra) + 1;
+ rect.bottom = mulru(rect.bottom - 1, ratioY, extra) + 1;
+}
+
+struct Buffer : public Graphics::Surface {
+ uint16 screenWidth;
+ uint16 screenHeight;
+ uint16 scriptWidth;
+ uint16 scriptHeight;
+
+ Buffer(const uint16 width, const uint16 height, uint8 *const pix) :
+ screenWidth(width),
+ screenHeight(height),
+ // TODO: These values are not correct for all games. Script
+ // dimensions were hard-coded per game in the original
+ // interpreter. Search all games for their internal script
+ // dimensions and set appropriately. (This code does not
+ // appear to exist at all in SCI3, which uses 640x480.)
+ scriptWidth(320),
+ scriptHeight(200) {
+ init(width, height, width, pix, Graphics::PixelFormat::createFormatCLUT8());
+ }
+
+ void clear(const uint8 value) {
+ memset(pixels, value, w * h);
+ }
+
+ inline uint8 *getAddress(const uint16 x, const uint16 y) {
+ return (uint8 *)getBasePtr(x, y);
+ }
+
+ inline uint8 *getAddressSimRes(const uint16 x, const uint16 y) {
+ return (uint8*)pixels + (y * w * screenHeight / scriptHeight) + (x * screenWidth / scriptWidth);
+ }
+
+ bool isNull() {
+ return pixels == nullptr;
+ }
+};
+#endif
+
struct Color {
byte used;
byte r, g, b;
@@ -146,7 +250,7 @@ struct Palette {
}
}
- return false;
+ return true;
}
inline bool operator!=(const Palette &other) const {
return !(*this == other);
diff --git a/engines/sci/graphics/lists32.h b/engines/sci/graphics/lists32.h
new file mode 100644
index 0000000000..4f74c77325
--- /dev/null
+++ b/engines/sci/graphics/lists32.h
@@ -0,0 +1,192 @@
+/* ScummVM - Graphic Adventure Engine
+ *
+ * ScummVM is the legal property of its developers, whose names
+ * are too numerous to list here. Please refer to the COPYRIGHT
+ * file distributed with this source distribution.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ *
+ */
+
+#ifndef SCI_GRAPHICS_LISTS32_H
+#define SCI_GRAPHICS_LISTS32_H
+
+#include "common/array.h"
+
+namespace Sci {
+
+/**
+ * StablePointerArray holds pointers in a fixed-size array
+ * that maintains position of erased items until `pack` is
+ * called. It is used by DrawList, RectList, and
+ * ScreenItemList. StablePointerArray takes ownership of
+ * all pointers that are passed to it and deletes them when
+ * calling `erase` or when destroying the
+ * StablePointerArray.
+ */
+template<class T, uint N>
+class StablePointerArray {
+ uint _size;
+ T *_items[N];
+
+public:
+ typedef T **iterator;
+ typedef T *const *const_iterator;
+ typedef T *value_type;
+ typedef uint size_type;
+
+ StablePointerArray() : _size(0), _items() {}
+ StablePointerArray(const StablePointerArray &other) : _size(other._size) {
+ for (size_type i = 0; i < _size; ++i) {
+ if (other._items[i] == nullptr) {
+ _items[i] = nullptr;
+ } else {
+ _items[i] = new T(*other._items[i]);
+ }
+ }
+ }
+ ~StablePointerArray() {
+ for (size_type i = 0; i < _size; ++i) {
+ delete _items[i];
+ }
+ }
+
+ void operator=(const StablePointerArray &other) {
+ clear();
+ _size = other._size;
+ for (size_type i = 0; i < _size; ++i) {
+ if (other._items[i] == nullptr) {
+ _items[i] = nullptr;
+ } else {
+ _items[i] = new T(*other._items[i]);
+ }
+ }
+ }
+
+ T *const &operator[](size_type index) const {
+ assert(index < _size);
+ return _items[index];
+ }
+
+ T *&operator[](size_type index) {
+ assert(index < _size);
+ return _items[index];
+ }
+
+ /**
+ * Adds a new pointer to the array.
+ */
+ void add(T *item) {
+ assert(_size < N);
+ _items[_size++] = item;
+ }
+
+ iterator begin() {
+ return _items;
+ }
+
+ const_iterator begin() const {
+ return _items;
+ }
+
+ void clear() {
+ for (size_type i = 0; i < _size; ++i) {
+ delete _items[i];
+ _items[i] = nullptr;
+ }
+
+ _size = 0;
+ }
+
+ iterator end() {
+ return _items + _size;
+ }
+
+ const_iterator end() const {
+ return _items + _size;
+ }
+
+ /**
+ * Erases the object pointed to by the given iterator.
+ */
+ void erase(T *item) {
+ for (iterator it = begin(); it != end(); ++it) {
+ if (*it == item) {
+ delete *it;
+ *it = nullptr;
+ break;
+ }
+ }
+ }
+
+ /**
+ * Erases the object pointed to by the given iterator.
+ */
+ void erase(iterator &it) {
+ assert(it >= _items && it < _items + _size);
+ delete *it;
+ *it = nullptr;
+ }
+
+ /**
+ * Erases the object pointed to at the given index.
+ */
+ void erase_at(size_type index) {
+ assert(index < _size);
+
+ delete _items[index];
+ _items[index] = nullptr;
+ }
+
+ /**
+ * Removes freed pointers from the pointer list.
+ */
+ size_type pack() {
+ iterator freePtr = begin();
+ size_type newSize = 0;
+
+ for (iterator it = begin(), last = end(); it != last; ++it) {
+ if (*it != nullptr) {
+ *freePtr = *it;
+ ++freePtr;
+ ++newSize;
+ }
+ }
+
+ _size = newSize;
+ return newSize;
+ }
+
+ /**
+ * The number of populated slots in the array. The size
+ * of the array will only go down once `pack` is called.
+ */
+ size_type size() const {
+ return _size;
+ }
+};
+
+template<typename T>
+class FindByObject {
+ const reg_t &_object;
+public:
+ FindByObject(const reg_t &object) : _object(object) {}
+ bool operator()(const T entry) const {
+ return entry && entry->_object == _object;
+ }
+};
+
+} // End of namespace Sci
+#endif
diff --git a/engines/sci/graphics/palette.cpp b/engines/sci/graphics/palette.cpp
index 106924ecd3..1514ad838f 100644
--- a/engines/sci/graphics/palette.cpp
+++ b/engines/sci/graphics/palette.cpp
@@ -32,6 +32,7 @@
#include "sci/graphics/cache.h"
#include "sci/graphics/maciconbar.h"
#include "sci/graphics/palette.h"
+#include "sci/graphics/remap.h"
#include "sci/graphics/screen.h"
#include "sci/graphics/view.h"
@@ -103,9 +104,6 @@ GfxPalette::GfxPalette(ResourceManager *resMan, GfxScreen *screen)
default:
error("GfxPalette: Unknown view type");
}
-
- _remapOn = false;
- resetRemapping();
}
GfxPalette::~GfxPalette() {
@@ -310,7 +308,7 @@ void GfxPalette::set(Palette *newPalette, bool force, bool forceRealMerge) {
uint32 systime = _sysPalette.timestamp;
if (force || newPalette->timestamp != systime) {
- // SCI1.1+ doesnt do real merging anymore, but simply copying over the used colors from other palettes
+ // SCI1.1+ doesn't do real merging anymore, but simply copying over the used colors from other palettes
// There are some games with inbetween SCI1.1 interpreters, use real merging for them (e.g. laura bow 2 demo)
if ((forceRealMerge) || (_useMerging))
_sysPaletteChanged |= merge(newPalette, force, forceRealMerge);
@@ -336,79 +334,6 @@ 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 = (byte)(_sysPalette.colors[i].r * _remappingPercentToSet * 0.30 / 100);
- byte gComponent = (byte)(_sysPalette.colors[i].g * _remappingPercentToSet * 0.59 / 100);
- byte bComponent = (byte)(_sysPalette.colors[i].b * _remappingPercentToSet * 0.11 / 100);
- byte luminosity = rComponent + gComponent + bComponent;
- _remappingByPercent[i] = kernelFindColor(luminosity, luminosity, luminosity);
- }
-
- _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;
@@ -589,15 +514,8 @@ 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);
- }
- }
+ if (g_sci->_gfxRemap16)
+ g_sci->_gfxRemap16->updateRemapping();
g_system->getPaletteManager()->setPalette(bpal, 0, 256);
}
diff --git a/engines/sci/graphics/palette.h b/engines/sci/graphics/palette.h
index 61a8d36cb9..7335dc59d0 100644
--- a/engines/sci/graphics/palette.h
+++ b/engines/sci/graphics/palette.h
@@ -35,12 +35,6 @@ class GfxScreen;
#define SCI_PALETTE_MATCH_PERFECT 0x8000
#define SCI_PALETTE_MATCH_COLORMASK 0xFF
-enum ColorRemappingType {
- kRemappingNone = 0,
- kRemappingByRange = 1,
- kRemappingByPercent = 2
-};
-
/**
* Palette class, handles palette operations like changing intensity, setting up the palette, merging different palettes
*/
@@ -64,15 +58,6 @@ 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();
@@ -138,12 +123,6 @@ protected:
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/palette32.cpp b/engines/sci/graphics/palette32.cpp
index e330b5620b..6844011675 100644
--- a/engines/sci/graphics/palette32.cpp
+++ b/engines/sci/graphics/palette32.cpp
@@ -28,29 +28,41 @@
#include "sci/event.h"
#include "sci/resource.h"
#include "sci/graphics/palette32.h"
+#include "sci/graphics/remap.h"
#include "sci/graphics/screen.h"
namespace Sci {
-
+
GfxPalette32::GfxPalette32(ResourceManager *resMan, GfxScreen *screen)
: GfxPalette(resMan, screen),
+ // Palette versioning
+ _version(1),
+ _versionUpdated(false),
+ _sourcePalette(_sysPalette),
+ _nextPalette(_sysPalette),
+ // Clut
_clutTable(nullptr),
- // Palette cycling
- _cyclers(), _cycleMap(),
// Palette varying
- _sourcePalette(_sysPalette), _nextPalette(_sysPalette),
- _varyTime(0), _varyDirection(0), _varyTargetPercent(0),
- _varyTargetPalette(nullptr), _varyStartPalette(nullptr),
- _varyFromColor(0), _varyToColor(255), _varyNumTimesPaused(0),
- _varyPercent(_varyTargetPercent), _varyLastTick(0),
- // Palette versioning
- _version(1), _versionUpdated(false) {
- memset(_fadeTable, 100, sizeof(_fadeTable));
-
+ _varyStartPalette(nullptr),
+ _varyTargetPalette(nullptr),
+ _varyFromColor(0),
+ _varyToColor(255),
+ _varyLastTick(0),
+ _varyTime(0),
+ _varyDirection(0),
+ _varyTargetPercent(0),
+ _varyNumTimesPaused(0),
+ // Palette cycling
+ _cyclers(),
+ _cycleMap() {
+ _varyPercent = _varyTargetPercent;
+ for (int i = 0, len = ARRAYSIZE(_fadeTable); i < len; ++i) {
+ _fadeTable[i] = 100;
+ }
// NOTE: In SCI engine, the palette manager constructor loads
// the default palette, but in ScummVM this initialisation
// is performed by SciEngine::run; see r49523 for details
- }
+}
GfxPalette32::~GfxPalette32() {
unloadClut();
@@ -59,13 +71,17 @@ GfxPalette32::~GfxPalette32() {
}
inline void mergePaletteInternal(Palette *const to, const Palette *const from) {
- for (int i = 0; i < ARRAYSIZE(to->colors); ++i) {
+ for (int i = 0, len = ARRAYSIZE(to->colors); i < len; ++i) {
if (from->colors[i].used) {
to->colors[i] = from->colors[i];
}
}
}
+const Palette *GfxPalette32::getNextPalette() const {
+ return &_nextPalette;
+}
+
void GfxPalette32::submit(Palette &palette) {
// TODO: The resource manager in SCI32 retains raw data of palettes from
// the ResourceManager (ResourceMgr) through SegManager (MemoryMgr), and
@@ -205,17 +221,22 @@ int16 GfxPalette32::matchColor(const byte r, const byte g, const byte b, const i
return bestIndex;
}
-void GfxPalette32::updateForFrame() {
+bool GfxPalette32::updateForFrame() {
applyAll();
_versionUpdated = false;
- // TODO: Implement remapping
- // g_sci->_gfxFrameout->remapAllTables(_nextPalette != _sysPalette);
+ return g_sci->_gfxRemap32->remapAllTables(_nextPalette != _sysPalette);
+}
+
+void GfxPalette32::updateFFrame() {
+ for (int i = 0; i < ARRAYSIZE(_nextPalette.colors); ++i) {
+ _nextPalette.colors[i] = _sourcePalette.colors[i];
+ }
+ _versionUpdated = false;
+ g_sci->_gfxRemap32->remapAllTables(_nextPalette != _sysPalette);
}
void GfxPalette32::updateHardware() {
if (_sysPalette == _nextPalette) {
- // TODO: This condition has never been encountered yet
- debug("Skipping hardware update because palettes are identical");
return;
}
@@ -246,9 +267,8 @@ void GfxPalette32::applyAll() {
applyFade();
}
-//
-// Clut
-//
+#pragma mark -
+#pragma mark Colour look-up
bool GfxPalette32::loadClut(uint16 clutId) {
// loadClut() will load a color lookup table from a clu file and set
@@ -300,9 +320,8 @@ void GfxPalette32::unloadClut() {
_clutTable = nullptr;
}
-//
-// Palette vary
-//
+#pragma mark -
+#pragma mark Varying
inline bool GfxPalette32::createPaletteFromResourceInternal(const GuiResourceId paletteId, Palette *const out) const {
Resource *palResource = _resMan->findResource(ResourceId(kResourceTypePalette, paletteId), false);
@@ -409,7 +428,7 @@ void GfxPalette32::setVaryPercent(const int16 percent, const int time, const int
}
int16 GfxPalette32::getVaryPercent() const {
- return abs(_varyPercent);
+ return ABS(_varyPercent);
}
void GfxPalette32::varyOff() {
@@ -542,9 +561,8 @@ void GfxPalette32::applyVary() {
}
}
-//
-// Palette cycling
-//
+#pragma mark -
+#pragma mark Cycling
inline void GfxPalette32::clearCycleMap(const uint16 fromColor, const uint16 numColorsToClear) {
bool *mapEntry = _cycleMap + fromColor;
@@ -677,14 +695,8 @@ void GfxPalette32::cycleAllOn() {
}
void GfxPalette32::cycleAllPause() {
- // TODO: The SCI SQ6 cycleAllPause function does not seem to perform
- // nullptr checking?? This would definitely cause null pointer
- // dereference in SCI code. I have not seen anything actually call
- // this function yet, so it is possible it is just unused and broken
- // in SCI SQ6. This assert exists so that if this function is called,
- // it is noticed and can be rechecked in the actual engine.
- // Obviously this code *does* do nullptr checks instead of crashing. :)
- assert(0);
+ // NOTE: The original engine did not check for null pointers in the
+ // palette cyclers pointer array.
for (int i = 0, len = ARRAYSIZE(_cyclers); i < len; ++i) {
PalCycler *cycler = _cyclers[i];
if (cycler != nullptr) {
@@ -768,11 +780,16 @@ void GfxPalette32::applyCycles() {
}
}
-//
-// Palette fading
-//
+#pragma mark -
+#pragma mark Fading
-void GfxPalette32::setFade(uint8 percent, uint8 fromColor, uint16 numColorsToFade) {
+// NOTE: There are some game scripts (like SQ6 Sierra logo and main menu) that call
+// setFade with numColorsToFade set to 256, but other parts of the engine like
+// processShowStyleNone use 255 instead of 256. It is not clear if this is because
+// the last palette entry is intentionally left unmodified, or if this is a bug
+// in the engine. It certainly seems confused because all other places that accept
+// color ranges typically receive values in the range of 0–255.
+void GfxPalette32::setFade(uint16 percent, uint8 fromColor, uint16 numColorsToFade) {
if (fromColor > numColorsToFade) {
return;
}
@@ -794,9 +811,10 @@ void GfxPalette32::applyFade() {
Color &color = _nextPalette.colors[i];
- color.r = (int16)color.r * _fadeTable[i] / 100;
- color.g = (int16)color.g * _fadeTable[i] / 100;
- color.b = (int16)color.b * _fadeTable[i] / 100;
+ color.r = MIN(255, (uint16)color.r * _fadeTable[i] / 100);
+ color.g = MIN(255, (uint16)color.g * _fadeTable[i] / 100);
+ color.b = MIN(255, (uint16)color.b * _fadeTable[i] / 100);
}
}
-}
+
+} // End of namespace Sci
diff --git a/engines/sci/graphics/palette32.h b/engines/sci/graphics/palette32.h
index 8744687e4c..a5450776dc 100644
--- a/engines/sci/graphics/palette32.h
+++ b/engines/sci/graphics/palette32.h
@@ -25,6 +25,7 @@
#include "sci/graphics/palette.h"
+namespace Sci {
enum PalCyclerDirection {
PalCycleBackward = 0,
PalCycleForward = 1
@@ -71,188 +72,213 @@ struct PalCycler {
uint16 numTimesPaused;
};
-namespace Sci {
- class GfxPalette32 : public GfxPalette {
- public:
- GfxPalette32(ResourceManager *resMan, GfxScreen *screen);
- ~GfxPalette32();
-
- protected:
- /**
- * The palette revision version. Increments once per game
- * loop that changes the source palette. TODO: Possibly
- * other areas also change _version, double-check once it
- * is all implemented.
- */
- uint32 _version;
-
- /**
- * Whether or not the palette manager version was updated
- * during this loop.
- */
- bool _versionUpdated;
-
- /**
- * The unmodified source palette loaded by kPalette. Additional
- * palette entries may be mixed into the source palette by
- * CelObj objects, which contain their own palettes.
- */
- Palette _sourcePalette;
-
- /**
- * The palette to be used when the hardware is next updated.
- * On update, _nextPalette is transferred to _sysPalette.
- */
- Palette _nextPalette;
-
- // SQ6 defines 10 cyclers
- PalCycler *_cyclers[10];
-
- /**
- * The cycle map is used to detect overlapping cyclers.
- * According to SCI engine code, when two cyclers overlap,
- * a fatal error has occurred and the engine will display
- * an error and then exit.
- */
- bool _cycleMap[256];
- inline void clearCycleMap(uint16 fromColor, uint16 numColorsToClear);
- inline void setCycleMap(uint16 fromColor, uint16 numColorsToClear);
- inline PalCycler *getCycler(uint16 fromColor);
-
- /**
- * The fade table records the expected intensity level of each pixel
- * in the palette that will be displayed on the next frame.
- */
- byte _fadeTable[256];
-
- /**
- * An optional lookup table used to remap RGB565 colors to a palette
- * index. Used by Phantasmagoria 2 in 8-bit color environments.
- */
- byte *_clutTable;
-
- /**
- * An optional palette used to describe the source colors used
- * in a palette vary operation. If this palette is not specified,
- * sourcePalette is used instead.
- */
- Palette *_varyStartPalette;
-
- /**
- * An optional palette used to describe the target colors used
- * in a palette vary operation.
- */
- Palette *_varyTargetPalette;
-
- /**
- * The minimum palette index that has been varied from the
- * source palette. 0–255
- */
- uint8 _varyFromColor;
-
- /**
- * The maximum palette index that is has been varied from the
- * source palette. 0-255
- */
- uint8 _varyToColor;
-
- /**
- * The tick at the last time the palette vary was updated.
- */
- uint32 _varyLastTick;
-
- /**
- * TODO: Document
- * The velocity of change in percent?
- */
- int _varyTime;
-
- /**
- * TODO: Better documentation
- * The direction of change, -1, 0, or 1.
- */
- int16 _varyDirection;
-
- /**
- * The amount, in percent, that the vary color is currently
- * blended into the source color.
- */
- int16 _varyPercent;
-
- /**
- * The target amount that a vary color will be blended into
- * the source color.
- */
- int16 _varyTargetPercent;
-
- /**
- * The number of time palette varying has been paused.
- */
- uint16 _varyNumTimesPaused;
-
- /**
- * Submits a palette to display. Entries marked as “used” in the
- * submitted palette are merged into the existing entries of
- * _sourcePalette.
- */
- void submit(Palette &palette);
-
- public:
- virtual void saveLoadWithSerializer(Common::Serializer &s) override;
-
- bool kernelSetFromResource(GuiResourceId resourceId, bool force) override;
- int16 kernelFindColor(uint16 r, uint16 g, uint16 b) override;
- void set(Palette *newPalette, bool force, bool forceRealMerge = false) override;
- int16 matchColor(const byte matchRed, const byte matchGreen, const byte matchBlue, const int defaultDifference, int &lastCalculatedDifference, const bool *const matchTable);
-
- void updateForFrame();
- void updateHardware();
- void applyAll();
-
- bool loadClut(uint16 clutId);
- byte matchClutColor(uint16 color);
- void unloadClut();
-
- void kernelPalVarySet(const GuiResourceId paletteId, const int16 percent, const int time, const int16 fromColor, const int16 toColor);
- void kernelPalVaryMergeTarget(const GuiResourceId paletteId);
- void kernelPalVarySetTarget(const GuiResourceId paletteId);
- void kernelPalVarySetStart(const GuiResourceId paletteId);
- void kernelPalVaryMergeStart(const GuiResourceId paletteId);
- virtual void kernelPalVaryPause(bool pause) override;
-
- void setVary(const Palette *const targetPalette, const int16 percent, const int time, const int16 fromColor, const int16 toColor);
- void setVaryPercent(const int16 percent, const int time, const int16 fromColor, const int16 fromColorAlternate);
- int16 getVaryPercent() const;
- void varyOff();
- void mergeTarget(const Palette *const palette);
- void varyPause();
- void varyOn();
- void setVaryTime(const int time);
- void setTarget(const Palette *const palette);
- void setStart(const Palette *const palette);
- void mergeStart(const Palette *const palette);
- private:
- bool createPaletteFromResourceInternal(const GuiResourceId paletteId, Palette *const out) const;
- Palette getPaletteFromResourceInternal(const GuiResourceId paletteId) const;
- void setVaryTimeInternal(const int16 percent, const int time);
- public:
- void applyVary();
-
- void setCycle(const uint8 fromColor, const uint8 toColor, const int16 direction, const int16 delay);
- void doCycle(const uint8 fromColor, const int16 speed);
- void cycleOn(const uint8 fromColor);
- void cyclePause(const uint8 fromColor);
- void cycleAllOn();
- void cycleAllPause();
- void cycleOff(const uint8 fromColor);
- void cycleAllOff();
- void applyAllCycles();
- void applyCycles();
-
- void setFade(const uint8 percent, const uint8 fromColor, const uint16 toColor);
- void fadeOff();
- void applyFade();
- };
-}
+class GfxPalette32 : public GfxPalette {
+public:
+ GfxPalette32(ResourceManager *resMan, GfxScreen *screen);
+ ~GfxPalette32();
+
+private:
+ // NOTE: currentPalette in SCI engine is called _sysPalette
+ // here.
+
+ /**
+ * The palette revision version. Increments once per game
+ * loop that changes the source palette. TODO: Possibly
+ * other areas also change _version, double-check once it
+ * is all implemented.
+ */
+ uint32 _version;
+
+ /**
+ * Whether or not the palette manager version was updated
+ * during this loop.
+ */
+ bool _versionUpdated;
+
+ /**
+ * The unmodified source palette loaded by kPalette. Additional
+ * palette entries may be mixed into the source palette by
+ * CelObj objects, which contain their own palettes.
+ */
+ Palette _sourcePalette;
+
+ /**
+ * The palette to be used when the hardware is next updated.
+ * On update, _nextPalette is transferred to _sysPalette.
+ */
+ Palette _nextPalette;
+
+ bool createPaletteFromResourceInternal(const GuiResourceId paletteId, Palette *const out) const;
+ Palette getPaletteFromResourceInternal(const GuiResourceId paletteId) const;
+
+public:
+ virtual void saveLoadWithSerializer(Common::Serializer &s) override;
+ const Palette *getNextPalette() const;
+
+ bool kernelSetFromResource(GuiResourceId resourceId, bool force) override;
+ int16 kernelFindColor(uint16 r, uint16 g, uint16 b) override;
+ void set(Palette *newPalette, bool force, bool forceRealMerge = false) override;
+ int16 matchColor(const byte matchRed, const byte matchGreen, const byte matchBlue, const int defaultDifference, int &lastCalculatedDifference, const bool *const matchTable);
+
+ /**
+ * Submits a palette to display. Entries marked as “used” in the
+ * submitted palette are merged into the existing entries of
+ * _sourcePalette.
+ */
+ void submit(Palette &palette);
+
+ bool updateForFrame();
+ void updateFFrame();
+ void updateHardware();
+ void applyAll();
+
+#pragma mark -
+#pragma mark color look-up
+private:
+ /**
+ * An optional lookup table used to remap RGB565 colors to a palette
+ * index. Used by Phantasmagoria 2 in 8-bit color environments.
+ */
+ byte *_clutTable;
+
+public:
+ bool loadClut(uint16 clutId);
+ byte matchClutColor(uint16 color);
+ void unloadClut();
+
+#pragma mark -
+#pragma mark Varying
+private:
+ /**
+ * An optional palette used to describe the source colors used
+ * in a palette vary operation. If this palette is not specified,
+ * sourcePalette is used instead.
+ */
+ Palette *_varyStartPalette;
+
+ /**
+ * An optional palette used to describe the target colors used
+ * in a palette vary operation.
+ */
+ Palette *_varyTargetPalette;
+
+ /**
+ * The minimum palette index that has been varied from the
+ * source palette. 0–255
+ */
+ uint8 _varyFromColor;
+
+ /**
+ * The maximum palette index that is has been varied from the
+ * source palette. 0-255
+ */
+ uint8 _varyToColor;
+
+ /**
+ * The tick at the last time the palette vary was updated.
+ */
+ uint32 _varyLastTick;
+
+ /**
+ * The amount of time to elapse, in ticks, between each cycle
+ * of a palette vary animation.
+ */
+ int _varyTime;
+
+ /**
+ * The direction of change: -1, 0, or 1.
+ */
+ int16 _varyDirection;
+
+ /**
+ * The amount, in percent, that the vary color is currently
+ * blended into the source color.
+ */
+ int16 _varyPercent;
+
+ /**
+ * The target amount that a vary color will be blended into
+ * the source color.
+ */
+ int16 _varyTargetPercent;
+
+ /**
+ * The number of time palette varying has been paused.
+ */
+ uint16 _varyNumTimesPaused;
+
+public:
+ void kernelPalVarySet(const GuiResourceId paletteId, const int16 percent, const int time, const int16 fromColor, const int16 toColor);
+ void kernelPalVaryMergeTarget(const GuiResourceId paletteId);
+ void kernelPalVarySetTarget(const GuiResourceId paletteId);
+ void kernelPalVarySetStart(const GuiResourceId paletteId);
+ void kernelPalVaryMergeStart(const GuiResourceId paletteId);
+ virtual void kernelPalVaryPause(bool pause) override;
+
+ void setVary(const Palette *const targetPalette, const int16 percent, const int time, const int16 fromColor, const int16 toColor);
+ void setVaryPercent(const int16 percent, const int time, const int16 fromColor, const int16 fromColorAlternate);
+ int16 getVaryPercent() const;
+ void varyOff();
+ void mergeTarget(const Palette *const palette);
+ void varyPause();
+ void varyOn();
+ void setVaryTime(const int time);
+ void setTarget(const Palette *const palette);
+ void setStart(const Palette *const palette);
+ void mergeStart(const Palette *const palette);
+ void setVaryTimeInternal(const int16 percent, const int time);
+ void applyVary();
+
+#pragma mark -
+#pragma mark Cycling
+private:
+ // SQ6 defines 10 cyclers
+ PalCycler *_cyclers[10];
+
+ /**
+ * The cycle map is used to detect overlapping cyclers.
+ * According to SCI engine code, when two cyclers overlap,
+ * a fatal error has occurred and the engine will display
+ * an error and then exit.
+ */
+ bool _cycleMap[256];
+ inline void clearCycleMap(uint16 fromColor, uint16 numColorsToClear);
+ inline void setCycleMap(uint16 fromColor, uint16 numColorsToClear);
+ inline PalCycler *getCycler(uint16 fromColor);
+
+public:
+ void setCycle(const uint8 fromColor, const uint8 toColor, const int16 direction, const int16 delay);
+ void doCycle(const uint8 fromColor, const int16 speed);
+ void cycleOn(const uint8 fromColor);
+ void cyclePause(const uint8 fromColor);
+ void cycleAllOn();
+ void cycleAllPause();
+ void cycleOff(const uint8 fromColor);
+ void cycleAllOff();
+ void applyAllCycles();
+ void applyCycles();
+ const bool *getCyclemap() { return _cycleMap; }
+
+#pragma mark -
+#pragma mark Fading
+private:
+ /**
+ * The fade table records the expected intensity level of each pixel
+ * in the palette that will be displayed on the next frame.
+ */
+ uint16 _fadeTable[256];
+
+public:
+ /**
+ * Sets the intensity level for a range of palette
+ * entries. An intensity of zero indicates total
+ * darkness. Intensity may be set to over 100 percent.
+ */
+ void setFade(const uint16 percent, const uint8 fromColor, const uint16 toColor);
+ void fadeOff();
+ void applyFade();
+};
+
+} // End of namespace Sci
#endif
diff --git a/engines/sci/graphics/picture.cpp b/engines/sci/graphics/picture.cpp
index d7ef84dc1e..2eab391afd 100644
--- a/engines/sci/graphics/picture.cpp
+++ b/engines/sci/graphics/picture.cpp
@@ -209,12 +209,12 @@ void GfxPicture::drawSci32Vga(int16 celNo, int16 drawX, int16 drawY, int16 pictu
}
// Header
- // [headerSize:WORD] [celCount:BYTE] [Unknown:BYTE] [Unknown:WORD] [paletteOffset:DWORD] [Unknown:DWORD]
+ // 0[headerSize:WORD] 2[celCount:BYTE] 3[Unknown:BYTE] 4[celHeaderSize:WORD] 6[paletteOffset:DWORD] 10[Unknown:WORD] 12[Unknown:WORD]
// cel-header follow afterwards, each is 42 bytes
// Cel-Header
- // [width:WORD] [height:WORD] [displaceX:WORD] [displaceY:WORD] [clearColor:BYTE] [compressed:BYTE]
+ // 0[width:WORD] 2[height:WORD] 4[displaceX:WORD] 6[displaceY:WORD] 8[clearColor:BYTE] 9[compressed:BYTE]
// offset 10-23 is unknown
- // [rleOffset:DWORD] [literalOffset:DWORD] [Unknown:WORD] [Unknown:WORD] [priority:WORD] [relativeXpos:WORD] [relativeYpos:WORD]
+ // 24[rleOffset:DWORD] 28[literalOffset:DWORD] 32[Unknown:WORD] 34[Unknown:WORD] 36[priority:WORD] 38[relativeXpos:WORD] 40[relativeYpos:WORD]
cel_headerPos += 42 * celNo;
diff --git a/engines/sci/graphics/picture.h b/engines/sci/graphics/picture.h
index 2404f99b41..942fa0f107 100644
--- a/engines/sci/graphics/picture.h
+++ b/engines/sci/graphics/picture.h
@@ -38,6 +38,9 @@ enum {
class GfxPorts;
class GfxScreen;
class GfxPalette;
+class GfxCoordAdjuster;
+class ResourceManager;
+class Resource;
/**
* Picture class, handles loading and displaying of picture resources
diff --git a/engines/sci/graphics/plane32.cpp b/engines/sci/graphics/plane32.cpp
new file mode 100644
index 0000000000..d05e4f79e1
--- /dev/null
+++ b/engines/sci/graphics/plane32.cpp
@@ -0,0 +1,907 @@
+/* ScummVM - Graphic Adventure Engine
+ *
+ * ScummVM is the legal property of its developers, whose names
+ * are too numerous to list here. Please refer to the COPYRIGHT
+ * file distributed with this source distribution.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ *
+ */
+
+#include "sci/console.h"
+#include "sci/engine/kernel.h"
+#include "sci/engine/selector.h"
+#include "sci/engine/state.h"
+#include "sci/graphics/frameout.h"
+#include "sci/graphics/lists32.h"
+#include "sci/graphics/plane32.h"
+#include "sci/graphics/remap.h"
+#include "sci/graphics/screen.h"
+#include "sci/graphics/screen_item32.h"
+
+namespace Sci {
+#pragma mark DrawList
+void DrawList::add(ScreenItem *screenItem, const Common::Rect &rect) {
+ DrawItem *drawItem = new DrawItem;
+ drawItem->screenItem = screenItem;
+ drawItem->rect = rect;
+ DrawListBase::add(drawItem);
+}
+
+#pragma mark -
+#pragma mark Plane
+uint16 Plane::_nextObjectId = 20000;
+
+Plane::Plane(const Common::Rect &gameRect, PlanePictureCodes pictureId) :
+_width(g_sci->_gfxFrameout->getCurrentBuffer().scriptWidth),
+_height(g_sci->_gfxFrameout->getCurrentBuffer().scriptHeight),
+_pictureId(pictureId),
+_mirrored(false),
+_back(0),
+_priorityChanged(0),
+_object(make_reg(0, _nextObjectId++)),
+_redrawAllCount(g_sci->_gfxFrameout->getScreenCount()),
+_created(g_sci->_gfxFrameout->getScreenCount()),
+_updated(0),
+_deleted(0),
+_moved(0),
+_gameRect(gameRect) {
+ convertGameRectToPlaneRect();
+ _priority = MAX(10000, g_sci->_gfxFrameout->getPlanes().getTopPlanePriority() + 1);
+ setType();
+ _screenRect = _planeRect;
+}
+
+Plane::Plane(reg_t object) :
+_width(g_sci->_gfxFrameout->getCurrentBuffer().scriptWidth),
+_height(g_sci->_gfxFrameout->getCurrentBuffer().scriptHeight),
+_priorityChanged(false),
+_object(object),
+_redrawAllCount(g_sci->_gfxFrameout->getScreenCount()),
+_created(g_sci->_gfxFrameout->getScreenCount()),
+_updated(0),
+_deleted(0),
+_moved(0) {
+ SegManager *segMan = g_sci->getEngineState()->_segMan;
+ _vanishingPoint.x = readSelectorValue(segMan, object, SELECTOR(vanishingX));
+ _vanishingPoint.y = readSelectorValue(segMan, object, SELECTOR(vanishingY));
+
+ _gameRect.left = readSelectorValue(segMan, object, SELECTOR(inLeft));
+ _gameRect.top = readSelectorValue(segMan, object, SELECTOR(inTop));
+ _gameRect.right = readSelectorValue(segMan, object, SELECTOR(inRight)) + 1;
+ _gameRect.bottom = readSelectorValue(segMan, object, SELECTOR(inBottom)) + 1;
+ convertGameRectToPlaneRect();
+
+ _back = readSelectorValue(segMan, object, SELECTOR(back));
+ _priority = readSelectorValue(segMan, object, SELECTOR(priority));
+ _pictureId = readSelectorValue(segMan, object, SELECTOR(picture));
+ setType();
+
+ _mirrored = readSelectorValue(segMan, object, SELECTOR(mirrored));
+ _screenRect = _planeRect;
+ changePic();
+}
+
+Plane::Plane(const Plane &other) :
+_pictureId(other._pictureId),
+_mirrored(other._mirrored),
+_field_34(other._field_34), _field_38(other._field_38),
+_field_3C(other._field_3C), _field_40(other._field_40),
+_back(other._back),
+_object(other._object),
+_priority(other._priority),
+_planeRect(other._planeRect),
+_gameRect(other._gameRect),
+_screenRect(other._screenRect),
+_screenItemList(other._screenItemList) {}
+
+void Plane::operator=(const Plane &other) {
+ _gameRect = other._gameRect;
+ _planeRect = other._planeRect;
+ _vanishingPoint = other._vanishingPoint;
+ _pictureId = other._pictureId;
+ _type = other._type;
+ _mirrored = other._mirrored;
+ _priority = other._priority;
+ _back = other._back;
+ _width = other._width;
+ _field_34 = other._field_34;
+ _height = other._height;
+ _screenRect = other._screenRect;
+ _field_3C = other._field_3C;
+ _priorityChanged = other._priorityChanged;
+}
+
+void Plane::init() {
+ _nextObjectId = 20000;
+}
+
+void Plane::convertGameRectToPlaneRect() {
+ const int16 screenWidth = g_sci->_gfxFrameout->getCurrentBuffer().screenWidth;
+ const int16 screenHeight = g_sci->_gfxFrameout->getCurrentBuffer().screenHeight;
+ const int16 scriptWidth = g_sci->_gfxFrameout->getCurrentBuffer().scriptWidth;
+ const int16 scriptHeight = g_sci->_gfxFrameout->getCurrentBuffer().scriptHeight;
+
+ const Ratio ratioX = Ratio(screenWidth, scriptWidth);
+ const Ratio ratioY = Ratio(screenHeight, scriptHeight);
+
+ _planeRect = _gameRect;
+ mulru(_planeRect, ratioX, ratioY, 1);
+}
+
+void Plane::printDebugInfo(Console *con) const {
+ Common::String name;
+
+ if (_object.isNumber()) {
+ name = "-scummvm-";
+ } else {
+ name = g_sci->getEngineState()->_segMan->getObjectName(_object);
+ }
+
+ con->debugPrintf("%04x:%04x (%s): type %d, prio %d, pic %d, mirror %d, back %d\n",
+ PRINT_REG(_object),
+ name.c_str(),
+ _type,
+ _priority,
+ _pictureId,
+ _mirrored,
+ _back
+ );
+ con->debugPrintf(" game rect: (%d, %d, %d, %d), plane rect: (%d, %d, %d, %d)\n screen rect: (%d, %d, %d, %d)\n",
+ PRINT_RECT(_gameRect),
+ PRINT_RECT(_planeRect),
+ PRINT_RECT(_screenRect)
+ );
+ con->debugPrintf(" # screen items: %d\n", _screenItemList.size());
+}
+
+#pragma mark -
+#pragma mark Plane - Pic
+
+void Plane::addPicInternal(const GuiResourceId pictureId, const Common::Point *position, const bool mirrorX) {
+
+ uint16 celCount = 1000;
+ for (uint16 celNo = 0; celNo < celCount; ++celNo) {
+ CelObjPic *celObj = new CelObjPic(pictureId, celNo);
+ if (celCount == 1000) {
+ celCount = celObj->_celCount;
+ }
+
+ ScreenItem *screenItem = new ScreenItem(_object, celObj->_info);
+ screenItem->_pictureId = pictureId;
+ screenItem->_mirrorX = mirrorX;
+ screenItem->_priority = celObj->_priority;
+ screenItem->_fixPriority = true;
+ if (position != nullptr) {
+ screenItem->_position = *position + celObj->_relativePosition;
+ } else {
+ screenItem->_position = celObj->_relativePosition;
+ }
+ _screenItemList.add(screenItem);
+
+ delete screenItem->_celObj;
+ screenItem->_celObj = celObj;
+ }
+}
+
+void Plane::addPic(const GuiResourceId pictureId, const Common::Point &position, const bool mirrorX) {
+ deletePic(pictureId);
+ addPicInternal(pictureId, &position, mirrorX);
+ // NOTE: In SCI engine this method returned the pictureId of the
+ // plane, but this return value was never used
+}
+
+void Plane::changePic() {
+ _pictureChanged = false;
+
+ if (_type != kPlaneTypePicture) {
+ return;
+ }
+
+ addPicInternal(_pictureId, nullptr, _mirrored);
+}
+
+void Plane::deletePic(const GuiResourceId pictureId) {
+ for (ScreenItemList::iterator it = _screenItemList.begin(); it != _screenItemList.end(); ++it) {
+ ScreenItem *screenItem = *it;
+ if (screenItem->_pictureId == pictureId) {
+ screenItem->_created = 0;
+ screenItem->_updated = 0;
+ screenItem->_deleted = g_sci->_gfxFrameout->getScreenCount();
+ }
+ }
+}
+
+void Plane::deletePic(const GuiResourceId oldPictureId, const GuiResourceId newPictureId) {
+ deletePic(oldPictureId);
+ _pictureId = newPictureId;
+}
+
+void Plane::deleteAllPics() {
+ for (ScreenItemList::iterator it = _screenItemList.begin(); it != _screenItemList.end(); ++it) {
+ ScreenItem *screenItem = *it;
+ if (screenItem != nullptr && screenItem->_celInfo.type == kCelTypePic) {
+ if (screenItem->_created == 0) {
+ screenItem->_created = 0;
+ screenItem->_updated = 0;
+ screenItem->_deleted = g_sci->_gfxFrameout->getScreenCount();
+ } else {
+ _screenItemList.erase(it);
+ }
+ }
+ }
+
+ _screenItemList.pack();
+}
+
+#pragma mark -
+#pragma mark Plane - Rendering
+
+void Plane::breakDrawListByPlanes(DrawList &drawList, const PlaneList &planeList) const {
+ int index = planeList.findIndexByObject(_object);
+
+ for (DrawList::size_type i = 0; i < drawList.size(); ++i) {
+ for (PlaneList::size_type j = index + 1; j < planeList.size(); ++j) {
+ if (planeList[j]->_type != kPlaneTypeTransparent) {
+ Common::Rect ptr[4];
+ int count = splitRects(drawList[i]->rect, planeList[j]->_screenRect, ptr);
+ if (count != -1) {
+ for (int k = count - 1; k >= 0; --k) {
+ drawList.add(drawList[i]->screenItem, ptr[k]);
+ }
+
+ drawList.erase_at(i);
+ break;
+ }
+ }
+ }
+ }
+ drawList.pack();
+}
+
+void Plane::breakEraseListByPlanes(RectList &eraseList, const PlaneList &planeList) const {
+ int index = planeList.findIndexByObject(_object);
+
+ for (RectList::size_type i = 0; i < eraseList.size(); ++i) {
+ for (PlaneList::size_type j = index + 1; j < planeList.size(); ++j) {
+ if (planeList[j]->_type != kPlaneTypeTransparent) {
+ Common::Rect ptr[4];
+
+ int count = splitRects(*eraseList[i], planeList[j]->_screenRect, ptr);
+ if (count != -1) {
+ for (int k = count - 1; k >= 0; --k) {
+ eraseList.add(ptr[k]);
+ }
+
+ eraseList.erase_at(i);
+ break;
+ }
+ }
+ }
+ }
+ eraseList.pack();
+}
+
+void Plane::calcLists(Plane &visiblePlane, const PlaneList &planeList, DrawList &drawList, RectList &eraseList) {
+ ScreenItemList::size_type planeItemCount = _screenItemList.size();
+ ScreenItemList::size_type visiblePlaneItemCount = visiblePlane._screenItemList.size();
+
+ for (ScreenItemList::size_type i = 0; i < planeItemCount; ++i) {
+ ScreenItem *vitem = nullptr;
+ // NOTE: The original engine used an array without bounds checking
+ // so could just get the visible screen item directly; we need to
+ // verify that the index is actually within the valid range for
+ // the visible plane before accessing the item to avoid a range
+ // error.
+ if (i < visiblePlaneItemCount) {
+ vitem = visiblePlane._screenItemList[i];
+ }
+ ScreenItem *item = _screenItemList[i];
+
+ if (i < _screenItemList.size() && item != nullptr) {
+ if (item->_deleted) {
+ // add item's rect to erase list
+ if (
+ i < visiblePlane._screenItemList.size() &&
+ vitem != nullptr &&
+ !vitem->_screenRect.isEmpty()
+ ) {
+ if (g_sci->_gfxRemap32->getRemapCount()) {
+ mergeToRectList(vitem->_screenRect, eraseList);
+ } else {
+ eraseList.add(vitem->_screenRect);
+ }
+ }
+ } else if (item->_created) {
+ // add item to draw list
+ item->calcRects(*this);
+
+ if(!item->_screenRect.isEmpty()) {
+ if (g_sci->_gfxRemap32->getRemapCount()) {
+ drawList.add(item, item->_screenRect);
+ mergeToRectList(item->_screenRect, eraseList);
+ } else {
+ drawList.add(item, item->_screenRect);
+ }
+ }
+ } else if (item->_updated) {
+ // add old rect to erase list, new item to draw list
+ item->calcRects(*this);
+ if (g_sci->_gfxRemap32->getRemapCount()) {
+ // if item and vitem don't overlap, ...
+ if (item->_screenRect.isEmpty() ||
+ i >= visiblePlaneItemCount ||
+ vitem == nullptr ||
+ vitem->_screenRect.isEmpty() ||
+ !vitem->_screenRect.intersects(item->_screenRect)
+ ) {
+ // add item to draw list, and old rect to erase list
+ if (!item->_screenRect.isEmpty()) {
+ drawList.add(item, item->_screenRect);
+ mergeToRectList(item->_screenRect, eraseList);
+ }
+ if (
+ i < visiblePlaneItemCount &&
+ vitem != nullptr &&
+ !vitem->_screenRect.isEmpty()
+ ) {
+ mergeToRectList(vitem->_screenRect, eraseList);
+ }
+ } else {
+ // otherwise, add bounding box of old+new to erase list,
+ // and item to draw list
+
+ // TODO: This was changed from disasm, verify please!
+ Common::Rect extendedScreenRect = vitem->_screenRect;
+ extendedScreenRect.extend(item->_screenRect);
+
+ drawList.add(item, item->_screenRect);
+ mergeToRectList(extendedScreenRect, eraseList);
+ }
+ } else {
+ // if no active remaps, just add item to draw list and old rect
+ // to erase list
+ if (!item->_screenRect.isEmpty()) {
+ drawList.add(item, item->_screenRect);
+ }
+ if (
+ i < visiblePlaneItemCount &&
+ vitem != nullptr &&
+ !vitem->_screenRect.isEmpty()
+ ) {
+ eraseList.add(vitem->_screenRect);
+ }
+ }
+ }
+ }
+ }
+
+ // Remove parts of eraselist/drawlist that are covered by other planes
+ breakEraseListByPlanes(eraseList, planeList);
+ breakDrawListByPlanes(drawList, planeList);
+
+ // We store the current size of the drawlist, as we want to loop
+ // over the currently inserted entries later.
+ DrawList::size_type drawListSizePrimary = drawList.size();
+
+ if (/* TODO: dword_C6288 */ false) { // "high resolution pictures"????
+ _screenItemList.sort();
+ bool encounteredPic = false;
+ bool v81 = false;
+
+ for (RectList::size_type i = 0; i < eraseList.size(); ++i) {
+ const Common::Rect *rect = eraseList[i];
+
+ for (ScreenItemList::size_type j = 0; j < _screenItemList.size(); ++j) {
+ ScreenItem *item = _screenItemList[j];
+
+ if (j < _screenItemList.size() && item != nullptr) {
+ if (rect->intersects(item->_screenRect)) {
+ const Common::Rect intersection = rect->findIntersectingRect(item->_screenRect);
+ if (!item->_deleted) {
+ if (encounteredPic) {
+ if (item->_celInfo.type == kCelTypePic) {
+ if (v81 || item->_celInfo.celNo == 0) {
+ drawList.add(item, intersection);
+ }
+ } else {
+ if (!item->_updated && !item->_created) {
+ drawList.add(item, intersection);
+ }
+ v81 = true;
+ }
+ } else {
+ if (!item->_updated && !item->_created) {
+ drawList.add(item, intersection);
+ }
+ if (item->_celInfo.type == kCelTypePic) {
+ encounteredPic = true;
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+
+ _screenItemList.unsort();
+ } else {
+ // add all items overlapping the erase list to the draw list
+ for (RectList::size_type i = 0; i < eraseList.size(); ++i) {
+ for (ScreenItemList::size_type j = 0; j < _screenItemList.size(); ++j) {
+ ScreenItem *item = _screenItemList[j];
+ if (
+ item != nullptr &&
+ !item->_created && !item->_updated && !item->_deleted &&
+ eraseList[i]->intersects(item->_screenRect)
+ ) {
+ drawList.add(item, eraseList[i]->findIntersectingRect(item->_screenRect));
+ }
+ }
+ }
+ }
+
+ if (g_sci->_gfxRemap32->getRemapCount() == 0) { // no remaps active?
+ // Add all items that overlap with items in the drawlist and have higher
+ // priority.
+
+ // We only loop over "primary" items in the draw list, skipping
+ // those that were added because of the erase list in the previous loop,
+ // or those to be added in this loop.
+ for (DrawList::size_type i = 0; i < drawListSizePrimary; ++i) {
+ DrawItem *dli = drawList[i];
+
+ for (ScreenItemList::size_type j = 0; j < planeItemCount; ++j) {
+ ScreenItem *sli = _screenItemList[j];
+
+ if (
+ i < drawList.size() && dli != nullptr &&
+ j < _screenItemList.size() && sli != nullptr &&
+ !sli->_created && !sli->_updated && !sli->_deleted
+ ) {
+ ScreenItem *item = dli->screenItem;
+
+ if (
+ (sli->_priority > item->_priority || (sli->_priority == item->_priority && sli->_object > item->_object)) &&
+ dli->rect.intersects(sli->_screenRect)
+ ) {
+ drawList.add(sli, dli->rect.findIntersectingRect(sli->_screenRect));
+ }
+ }
+ }
+ }
+ }
+
+ decrementScreenItemArrayCounts(&visiblePlane, false);
+ _screenItemList.pack();
+ visiblePlane._screenItemList.pack();
+}
+
+void Plane::decrementScreenItemArrayCounts(Plane *visiblePlane, const bool forceUpdate) {
+ // The size of the screenItemList may change, so it is
+ // critical to re-check the size on each iteration
+ for (ScreenItemList::size_type i = 0; i < _screenItemList.size(); ++i) {
+ ScreenItem *item = _screenItemList[i];
+
+ if (item != nullptr) {
+ // update item in visiblePlane if item is updated
+ if (
+ item->_updated ||
+ (
+ forceUpdate &&
+ visiblePlane != nullptr &&
+ visiblePlane->_screenItemList.findByObject(item->_object) != nullptr
+ )
+ ) {
+ *visiblePlane->_screenItemList[i] = *_screenItemList[i];
+ }
+
+ if (item->_updated) {
+ item->_updated--;
+ }
+
+ // create new item in visiblePlane if item was added
+ if (item->_created) {
+ item->_created--;
+ if (visiblePlane != nullptr) {
+ visiblePlane->_screenItemList.add(new ScreenItem(*item));
+ }
+ }
+
+ // delete item from both planes if it was deleted
+ if (item->_deleted) {
+ item->_deleted--;
+ if (!item->_deleted) {
+ visiblePlane->_screenItemList.erase_at(i);
+ _screenItemList.erase_at(i);
+ }
+ }
+ }
+ }
+}
+
+void Plane::filterDownEraseRects(DrawList &drawList, RectList &eraseList, RectList &transparentEraseList) const {
+ if (_type == kPlaneTypeTransparent) {
+ for (RectList::size_type i = 0; i < transparentEraseList.size(); ++i) {
+ const Common::Rect *r = transparentEraseList[i];
+ for (ScreenItemList::size_type j = 0; j < _screenItemList.size(); ++j) {
+ ScreenItem *item = _screenItemList[j];
+ if (item != nullptr) {
+ if (r->intersects(item->_screenRect)) {
+ mergeToDrawList(j, *r, drawList);
+ }
+ }
+ }
+ }
+ } else {
+ for (RectList::size_type i = 0; i < transparentEraseList.size(); ++i) {
+ Common::Rect *r = transparentEraseList[i];
+ if (r->intersects(_screenRect)) {
+ r->clip(_screenRect);
+ mergeToRectList(*r, eraseList);
+
+ for (ScreenItemList::size_type j = 0; j < _screenItemList.size(); ++j) {
+ ScreenItem *item = _screenItemList[j];
+
+ if (item != nullptr) {
+ if (r->intersects(item->_screenRect)) {
+ mergeToDrawList(j, *r, drawList);
+ }
+ }
+ }
+
+ Common::Rect ptr[4];
+ const Common::Rect *r2 = transparentEraseList[i];
+ int count = splitRects(*r2, *r, ptr);
+ for (int k = count - 1; k >= 0; --k) {
+ transparentEraseList.add(ptr[k]);
+ }
+ transparentEraseList.erase_at(i);
+ }
+ }
+
+ transparentEraseList.pack();
+ }
+}
+
+void Plane::filterUpDrawRects(DrawList &transparentDrawList, const DrawList &drawList) const {
+ for (DrawList::size_type i = 0; i < drawList.size(); ++i) {
+ const Common::Rect &r = drawList[i]->rect;
+
+ for (ScreenItemList::size_type j = 0; j < _screenItemList.size(); ++j) {
+ ScreenItem *item = _screenItemList[j];
+ if (item != nullptr) {
+ if (r.intersects(item->_screenRect)) {
+ mergeToDrawList(j, r, transparentDrawList);
+ }
+ }
+ }
+ }
+}
+
+void Plane::filterUpEraseRects(DrawList &drawList, RectList &eraseList) const {
+ for (RectList::size_type i = 0; i < eraseList.size(); ++i) {
+ const Common::Rect &r = *eraseList[i];
+ for (ScreenItemList::size_type j = 0; j < _screenItemList.size(); ++j) {
+ ScreenItem *item = _screenItemList[j];
+
+ if (item != nullptr) {
+ if (r.intersects(item->_screenRect)) {
+ mergeToDrawList(j, r, drawList);
+ }
+ }
+ }
+ }
+}
+
+void Plane::mergeToDrawList(const ScreenItemList::size_type index, const Common::Rect &rect, DrawList &drawList) const {
+ RectList rects;
+
+ ScreenItem *item = _screenItemList[index];
+ Common::Rect r = item->_screenRect;
+ r.clip(rect);
+ rects.add(r);
+
+ for (RectList::size_type i = 0; i < rects.size(); ++i) {
+ r = *rects[i];
+
+ for (DrawList::size_type j = 0; j < drawList.size(); ++j) {
+ const DrawItem *drawitem = drawList[j];
+ if (item->_object == drawitem->screenItem->_object) {
+ if (drawitem->rect.contains(r)) {
+ rects.erase_at(i);
+ break;
+ }
+
+ Common::Rect outRects[4];
+ const int count = splitRects(r, drawitem->rect, outRects);
+ if (count != -1) {
+ for (int k = count - 1; k >= 0; --k) {
+ rects.add(outRects[k]);
+ }
+
+ rects.erase_at(i);
+
+ // proceed to the next rect
+ r = *rects[++i];
+ }
+ }
+ }
+ }
+
+ rects.pack();
+
+ for (RectList::size_type i = 0; i < rects.size(); ++i) {
+ drawList.add(item, *rects[i]);
+ }
+}
+
+void Plane::mergeToRectList(const Common::Rect &rect, RectList &rectList) const {
+ RectList temp;
+ temp.add(rect);
+
+ for (RectList::size_type i = 0; i < temp.size(); ++i) {
+ Common::Rect r = *temp[i];
+
+ for (RectList::size_type j = 0; j < rectList.size(); ++j) {
+ const Common::Rect *innerRect = rectList[j];
+ if (innerRect->contains(r)) {
+ temp.erase_at(i);
+ break;
+ }
+
+ Common::Rect out[4];
+ const int count = splitRects(r, *innerRect, out);
+ if (count != -1) {
+ for (int k = count - 1; k >= 0; --k) {
+ temp.add(out[k]);
+ }
+
+ temp.erase_at(i);
+
+ // proceed to the next rect
+ r = *temp[++i];
+ }
+ }
+ }
+
+ temp.pack();
+
+ for (RectList::size_type i = 0; i < temp.size(); ++i) {
+ rectList.add(*temp[i]);
+ }
+}
+
+void Plane::redrawAll(Plane *visiblePlane, const PlaneList &planeList, DrawList &drawList, RectList &eraseList) {
+ for (ScreenItemList::const_iterator screenItemPtr = _screenItemList.begin(); screenItemPtr != _screenItemList.end(); ++screenItemPtr) {
+ if (*screenItemPtr != nullptr) {
+ ScreenItem &screenItem = **screenItemPtr;
+ if (!screenItem._deleted) {
+ screenItem.calcRects(*this);
+ if (!screenItem._screenRect.isEmpty()) {
+ drawList.add(&screenItem, screenItem._screenRect);
+ }
+ }
+ }
+ }
+
+ eraseList.clear();
+
+ if (!_screenRect.isEmpty() && _type != kPlaneTypePicture && _type != kPlaneTypeOpaque) {
+ eraseList.add(_screenRect);
+ }
+ breakEraseListByPlanes(eraseList, planeList);
+ breakDrawListByPlanes(drawList, planeList);
+ --_redrawAllCount;
+ decrementScreenItemArrayCounts(visiblePlane, true);
+ _screenItemList.pack();
+ if (visiblePlane != nullptr) {
+ visiblePlane->_screenItemList.pack();
+ }
+}
+
+void Plane::setType() {
+ if (_pictureId == kPlanePicOpaque) {
+ _type = kPlaneTypeOpaque;
+ } else if (_pictureId == kPlanePicTransparent) {
+ _type = kPlaneTypeTransparent;
+ } else if (_pictureId == kPlanePicColored) {
+ _type = kPlaneTypeColored;
+ } else {
+ _type = kPlaneTypePicture;
+ }
+}
+
+void Plane::sync(const Plane *other, const Common::Rect &screenRect) {
+ if (other == nullptr) {
+ if (_pictureChanged) {
+ deleteAllPics();
+ setType();
+ changePic();
+ _redrawAllCount = g_sci->_gfxFrameout->getScreenCount();
+ } else {
+ setType();
+ }
+ } else {
+ if (
+ _planeRect.top != other->_planeRect.top ||
+ _planeRect.left != other->_planeRect.left ||
+ _planeRect.right > other->_planeRect.right ||
+ _planeRect.bottom > other->_planeRect.bottom
+ ) {
+ _redrawAllCount = g_sci->_gfxFrameout->getScreenCount();
+ _updated = g_sci->_gfxFrameout->getScreenCount();
+ } else if (_planeRect != other->_planeRect) {
+ _updated = g_sci->_gfxFrameout->getScreenCount();
+ }
+
+ if (_priority != other->_priority) {
+ _priorityChanged = g_sci->_gfxFrameout->getScreenCount();
+ }
+
+ if (_pictureId != other->_pictureId || _mirrored != other->_mirrored || _pictureChanged) {
+ deleteAllPics();
+ setType();
+ changePic();
+ _redrawAllCount = g_sci->_gfxFrameout->getScreenCount();
+ }
+
+ if (_back != other->_back) {
+ _redrawAllCount = g_sci->_gfxFrameout->getScreenCount();
+ }
+ }
+
+ _deleted = 0;
+ if (_created == 0) {
+ _moved = g_sci->_gfxFrameout->getScreenCount();
+ }
+
+ convertGameRectToPlaneRect();
+ _width = g_sci->_gfxFrameout->getCurrentBuffer().scriptWidth;
+ _height = g_sci->_gfxFrameout->getCurrentBuffer().scriptHeight;
+ _screenRect = _planeRect;
+ // NOTE: screenRect originally was retrieved through globals
+ // instead of being passed into the function
+ clipScreenRect(screenRect);
+}
+
+void Plane::update(const reg_t object) {
+ SegManager *segMan = g_sci->getEngineState()->_segMan;
+ _vanishingPoint.x = readSelectorValue(segMan, object, SELECTOR(vanishingX));
+ _vanishingPoint.y = readSelectorValue(segMan, object, SELECTOR(vanishingY));
+ _gameRect.left = readSelectorValue(segMan, object, SELECTOR(inLeft));
+ _gameRect.top = readSelectorValue(segMan, object, SELECTOR(inTop));
+ _gameRect.right = readSelectorValue(segMan, object, SELECTOR(inRight)) + 1;
+ _gameRect.bottom = readSelectorValue(segMan, object, SELECTOR(inBottom)) + 1;
+ convertGameRectToPlaneRect();
+
+ _priority = readSelectorValue(segMan, object, SELECTOR(priority));
+ GuiResourceId pictureId = readSelectorValue(segMan, object, SELECTOR(picture));
+ if (_pictureId != pictureId) {
+ _pictureId = pictureId;
+ _pictureChanged = true;
+ }
+
+ _mirrored = readSelectorValue(segMan, object, SELECTOR(mirrored));
+ _back = readSelectorValue(segMan, object, SELECTOR(back));
+}
+
+void Plane::scrollScreenItems(const int16 deltaX, const int16 deltaY, const bool scrollPics) {
+ _redrawAllCount = g_sci->_gfxFrameout->getScreenCount();
+
+ for (ScreenItemList::iterator it = _screenItemList.begin(); it != _screenItemList.end(); ++it) {
+ if (*it != nullptr) {
+ ScreenItem &screenItem = **it;
+ if (!screenItem._deleted && (screenItem._celInfo.type != kCelTypePic || scrollPics)) {
+ screenItem._position.x += deltaX;
+ screenItem._position.y += deltaY;
+ }
+ }
+ }
+}
+
+void Plane::remapMarkRedraw() {
+ for (ScreenItemList::const_iterator screenItemPtr = _screenItemList.begin(); screenItemPtr != _screenItemList.end(); ++screenItemPtr) {
+ if (*screenItemPtr != nullptr) {
+ ScreenItem &screenItem = **screenItemPtr;
+ if (screenItem.getCelObj()._remap && !screenItem._deleted && !screenItem._created) {
+ screenItem._updated = g_sci->_gfxFrameout->getScreenCount();
+ }
+ }
+ }
+}
+
+#pragma mark -
+#pragma mark PlaneList
+void PlaneList::add(Plane *plane) {
+ for (iterator it = begin(); it != end(); ++it) {
+ if ((*it)->_priority > plane->_priority) {
+ insert(it, plane);
+ return;
+ }
+ }
+
+ push_back(plane);
+}
+
+void PlaneList::clear() {
+ for (iterator it = begin(); it != end(); ++it) {
+ delete *it;
+ }
+
+ PlaneListBase::clear();
+}
+
+void PlaneList::erase(Plane *plane) {
+ for (iterator it = begin(); it != end(); ++it) {
+ if (*it == plane) {
+ erase(it);
+ break;
+ }
+ }
+}
+
+PlaneList::iterator PlaneList::erase(iterator it) {
+ delete *it;
+ return PlaneListBase::erase(it);
+}
+
+int PlaneList::findIndexByObject(const reg_t object) const {
+ for (size_type i = 0; i < size(); ++i) {
+ if ((*this)[i] != nullptr && (*this)[i]->_object == object) {
+ return i;
+ }
+ }
+
+ return -1;
+}
+
+Plane *PlaneList::findByObject(const reg_t object) const {
+ const_iterator planeIt = Common::find_if(begin(), end(), FindByObject<Plane *>(object));
+
+ if (planeIt == end()) {
+ return nullptr;
+ }
+
+ return *planeIt;
+}
+
+int16 PlaneList::getTopPlanePriority() const {
+ if (size() > 0) {
+ return (*this)[size() - 1]->_priority;
+ }
+
+ return 0;
+}
+
+int16 PlaneList::getTopSciPlanePriority() const {
+ int16 priority = 0;
+
+ for (const_iterator it = begin(); it != end(); ++it) {
+ if ((*it)->_priority >= 10000) {
+ break;
+ }
+
+ priority = (*it)->_priority;
+ }
+
+ return priority;
+}
+
+void PlaneList::remove_at(size_type index) {
+ delete PlaneListBase::remove_at(index);
+}
+
+} // End of namespace Sci
diff --git a/engines/sci/graphics/plane32.h b/engines/sci/graphics/plane32.h
new file mode 100644
index 0000000000..770a6fa445
--- /dev/null
+++ b/engines/sci/graphics/plane32.h
@@ -0,0 +1,485 @@
+/* ScummVM - Graphic Adventure Engine
+ *
+ * ScummVM is the legal property of its developers, whose names
+ * are too numerous to list here. Please refer to the COPYRIGHT
+ * file distributed with this source distribution.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ *
+ */
+
+#ifndef SCI_GRAPHICS_PLANE32_H
+#define SCI_GRAPHICS_PLANE32_H
+
+#include "common/array.h"
+#include "common/rect.h"
+#include "sci/engine/vm_types.h"
+#include "sci/graphics/helpers.h"
+#include "sci/graphics/lists32.h"
+#include "sci/graphics/screen_item32.h"
+
+namespace Sci {
+enum PlaneType {
+ kPlaneTypeColored = 0,
+ kPlaneTypePicture = 1,
+ kPlaneTypeTransparent = 2,
+ kPlaneTypeOpaque = 3
+};
+
+enum PlanePictureCodes {
+ // NOTE: Any value at or below 65532 means the plane
+ // is a kPlaneTypePicture.
+ kPlanePic = 65532,
+ kPlanePicOpaque = 65533,
+ kPlanePicTransparent = 65534,
+ kPlanePicColored = 65535
+};
+
+#pragma mark -
+#pragma mark RectList
+
+typedef StablePointerArray<Common::Rect, 200> RectListBase;
+class RectList : public RectListBase {
+public:
+ void add(const Common::Rect &rect) {
+ RectListBase::add(new Common::Rect(rect));
+ }
+};
+
+#pragma mark -
+#pragma mark DrawList
+
+struct DrawItem {
+ ScreenItem *screenItem;
+ Common::Rect rect;
+
+ inline bool operator<(const DrawItem &other) const {
+ return *screenItem < *other.screenItem;
+ }
+};
+
+typedef StablePointerArray<DrawItem, 250> DrawListBase;
+class DrawList : public DrawListBase {
+private:
+ inline static bool sortHelper(const DrawItem *a, const DrawItem *b) {
+ return *a < *b;
+ }
+public:
+ void add(ScreenItem *screenItem, const Common::Rect &rect);
+ inline void sort() {
+ pack();
+ Common::sort(begin(), end(), sortHelper);
+ }
+};
+
+class PlaneList;
+
+#pragma mark -
+#pragma mark Plane
+
+/**
+ * A plane is a grouped layer of screen items.
+ */
+class Plane {
+private:
+ /**
+ * A serial used for planes that are generated inside
+ * the graphics engine, rather than the interpreter.
+ */
+ static uint16 _nextObjectId;
+
+ /**
+ * The dimensions of the plane, in game script
+ * coordinates.
+ * TODO: These are never used and are always
+ * scriptWidth x scriptHeight in SCI engine? The actual
+ * dimensions of the plane are always in
+ * gameRect/planeRect.
+ */
+ int16 _width, _height;
+
+ /**
+ * For planes that are used to render picture data, the
+ * resource ID of the picture to be displayed. This
+ * value may also be one of the special
+ * PlanePictureCodes, in which case the plane becomes a
+ * non-picture plane.
+ */
+ GuiResourceId _pictureId;
+
+ /**
+ * Whether or not the contents of picture planes should
+ * be drawn horizontally mirrored. Only applies to
+ * planes of type kPlaneTypePicture.
+ */
+ bool _mirrored;
+
+ /**
+ * Whether the picture ID for this plane has changed.
+ * This flag is set when the plane is created or updated
+ * from a VM object, and is cleared when the plane is
+ * synchronised to another plane (which calls
+ * changePic).
+ */
+ bool _pictureChanged;
+
+ // TODO: Are these ever actually used?
+ int _field_34, _field_38; // probably a point or ratio
+ int _field_3C, _field_40; // probably a point or ratio
+
+ /**
+ * Converts the dimensions of the game rect used by
+ * scripts to the dimensions of the plane rect used to
+ * render content to the screen. Coordinates with
+ * remainders are rounded up to the next whole pixel.
+ */
+ void convertGameRectToPlaneRect();
+
+ /**
+ * Sets the type of the plane according to its assigned
+ * picture resource ID.
+ */
+ void setType();
+
+public:
+ /**
+ * The color to use when erasing the plane. Only
+ * applies to planes of type kPlaneTypeColored.
+ */
+ byte _back;
+
+ /**
+ * Whether the priority of this plane has changed.
+ * This flag is set when the plane is updated from
+ * another plane and cleared when draw list calculation
+ * occurs.
+ */
+ int _priorityChanged;
+
+ /**
+ * A handle to the VM object corresponding to this
+ * plane. Some planes are generated purely within the
+ * graphics engine and have a numeric object value.
+ */
+ reg_t _object;
+
+ /**
+ * The rendering priority of the plane. Higher
+ * priorities are drawn above lower priorities.
+ */
+ int16 _priority;
+
+ /**
+ * Whether or not all screen items in this plane should
+ * be redrawn on the next frameout, instead of just
+ * the screen items marked as updated. This is set when
+ * visual changes to the plane itself are made that
+ * affect the rendering of the entire plane, and cleared
+ * once those changes are rendered by `redrawAll`.
+ */
+ int _redrawAllCount;
+
+ PlaneType _type;
+
+ /**
+ * Flags indicating the state of the plane.
+ * - `created` is set when the plane is first created,
+ * either from a VM object or from within the engine
+ * itself
+ * - `updated` is set when the plane is updated from
+ * another plane and the two planes' `planeRect`s do
+ * not match
+ * - `deleted` is set when the plane is deleted by a
+ * kernel call
+ * - `moved` is set when the plane is synchronised from
+ * another plane and is not already in the "created"
+ * state
+ */
+ int _created, _updated, _deleted, _moved;
+
+ /**
+ * The vanishing point for the plane. Used when
+ * calculating the correct scaling of the plane's screen
+ * items according to their position.
+ */
+ Common::Point _vanishingPoint;
+
+ /**
+ * The position & dimensions of the plane in screen
+ * coordinates. This rect is not clipped to the screen,
+ * so may include coordinates that are offscreen.
+ */
+ Common::Rect _planeRect;
+
+ /**
+ * The position & dimensions of the plane in game script
+ * coordinates.
+ */
+ Common::Rect _gameRect;
+
+ /**
+ * The position & dimensions of the plane in screen
+ * coordinates. This rect is clipped to the screen.
+ */
+ Common::Rect _screenRect;
+
+ /**
+ * The list of screen items grouped within this plane.
+ */
+ ScreenItemList _screenItemList;
+
+public:
+ /**
+ * Initialises static Plane members.
+ */
+ static void init();
+
+ // NOTE: This constructor signature originally did not accept a
+ // picture ID, but some calls to construct planes with this signature
+ // immediately set the picture ID and then called setType again, so
+ // it made more sense to just make the picture ID a parameter instead.
+ Plane(const Common::Rect &gameRect, PlanePictureCodes pictureId = kPlanePicColored);
+
+ Plane(const reg_t object);
+
+ Plane(const Plane &other);
+
+ void operator=(const Plane &other);
+
+ inline bool operator<(const Plane &other) const {
+ if (_priority < other._priority) {
+ return true;
+ }
+
+ if (_priority == other._priority) {
+ return _object < other._object;
+ }
+
+ return false;
+ }
+
+ /**
+ * Clips the screen rect of this plane to fit within the
+ * given screen rect.
+ */
+ inline void clipScreenRect(const Common::Rect &screenRect) {
+ if (_screenRect.intersects(screenRect)) {
+ _screenRect.clip(screenRect);
+ } else {
+ _screenRect.left = 0;
+ _screenRect.top = 0;
+ _screenRect.right = 0;
+ _screenRect.bottom = 0;
+ }
+ }
+
+ void printDebugInfo(Console *con) const;
+
+ /**
+ * Compares the properties of the current plane against
+ * the properties of the `other` plane (which is the
+ * corresponding plane from the visible plane list) to
+ * discover which properties have been changed on this
+ * plane by a call to `update(reg_t)`.
+ *
+ * @note This method was originally called UpdatePlane
+ * in SCI engine.
+ */
+ void sync(const Plane *other, const Common::Rect &screenRect);
+
+ /**
+ * Updates the plane to match the state of the plane
+ * object from the virtual machine.
+ *
+ * @note This method was originally called UpdatePlane
+ * in SCI engine.
+ */
+ void update(const reg_t object);
+
+ /**
+ * Modifies the position of all non-pic screen items
+ * by the given delta. If `scrollPics` is true, pic
+ * items are also repositioned.
+ */
+ void scrollScreenItems(const int16 deltaX, const int16 deltaY, const bool scrollPics);
+
+#pragma mark -
+#pragma mark Plane - Pic
+private:
+ /**
+ * Adds all cels from the specified picture resource to
+ * the plane as screen items. If a position is provided,
+ * the screen items will be given that position;
+ * otherwise, the default relative positions for each
+ * cel will be taken from the picture resource data.
+ */
+ inline void addPicInternal(const GuiResourceId pictureId, const Common::Point *position, const bool mirrorX);
+
+ /**
+ * Marks all screen items to be deleted that are within
+ * this plane and match the given picture ID.
+ */
+ void deletePic(const GuiResourceId pictureId);
+
+ /**
+ * Marks all screen items to be deleted that are within
+ * this plane and match the given picture ID, then sets
+ * the picture ID of the plane to the new picture ID
+ * without adding any screen items.
+ */
+ void deletePic(const GuiResourceId oldPictureId, const GuiResourceId newPictureId);
+
+ /**
+ * Marks all screen items to be deleted that are within
+ * this plane and are picture cels.
+ */
+ void deleteAllPics();
+
+public:
+ /**
+ * Marks all existing screen items matching the current
+ * picture to be deleted, then adds all cels from the
+ * new picture resource to the plane at the given
+ * position.
+ */
+ void addPic(const GuiResourceId pictureId, const Common::Point &position, const bool mirrorX);
+
+ /**
+ * If the plane is a picture plane, re-adds all cels
+ * from its picture resource to the plane. Otherwise,
+ * just clears the _pictureChanged flag.
+ */
+ void changePic();
+
+#pragma mark -
+#pragma mark Plane - Rendering
+private:
+ /**
+ * Splits all rects in the given draw list at the edges
+ * of all non-transparent planes above the current
+ * plane.
+ */
+ void breakDrawListByPlanes(DrawList &drawList, const PlaneList &planeList) const;
+
+ /**
+ * Splits all rects in the given erase list rects at the
+ * edges of all non-transparent planes above the current
+ * plane.
+ */
+ void breakEraseListByPlanes(RectList &eraseList, const PlaneList &planeList) const;
+
+ /**
+ * Synchronises changes to screen items from the current
+ * plane to the visible plane and deletes screen items
+ * from the current plane that have been marked as
+ * deleted. If `forceUpdate` is true, all screen items
+ * on the visible plane will be updated, even if they
+ * are not marked as having changed.
+ */
+ void decrementScreenItemArrayCounts(Plane *visiblePlane, const bool forceUpdate);
+
+ /**
+ * Merges the screen item from this plane at the given
+ * index into the given draw list, clipped to the given
+ * rect. TODO: Finish documenting
+ */
+ void mergeToDrawList(const DrawList::size_type index, const Common::Rect &rect, DrawList &drawList) const;
+
+ /**
+ * Adds the given rect into the given rect list,
+ * merging it with other rects already inside the list,
+ * if possible, to avoid overdraw. TODO: Finish
+ * documenting
+ */
+ void mergeToRectList(const Common::Rect &rect, RectList &rectList) const;
+
+public:
+ /**
+ * Calculates the location and dimensions of dirty rects
+ * of the screen items in this plane and adds them to
+ * the given draw and erase lists, and synchronises this
+ * plane's list of screen items to the given visible
+ * plane.
+ */
+ void calcLists(Plane &visiblePlane, const PlaneList &planeList, DrawList &drawList, RectList &eraseList);
+
+ /**
+ * TODO: Documentation
+ */
+ void filterDownEraseRects(DrawList &drawList, RectList &eraseList, RectList &transparentEraseList) const;
+
+ /**
+ * TODO: Documentation
+ */
+ void filterUpEraseRects(DrawList &drawList, RectList &eraseList) const;
+
+ /**
+ * TODO: Documentation
+ */
+ void filterUpDrawRects(DrawList &transparentDrawList, const DrawList &drawList) const;
+
+ /**
+ * Updates all of the plane's non-deleted screen items
+ * and adds them to the given draw and erase lists.
+ */
+ void redrawAll(Plane *visiblePlane, const PlaneList &planeList, DrawList &drawList, RectList &eraseList);
+
+ void remapMarkRedraw();
+};
+
+#pragma mark -
+#pragma mark PlaneList
+
+typedef Common::Array<Plane *> PlaneListBase;
+class PlaneList : public PlaneListBase {
+private:
+ inline static bool sortHelper(const Plane *a, const Plane *b) {
+ return *a < *b;
+ }
+
+ using PlaneListBase::push_back;
+
+public:
+ // A method for finding the index of a plane inside a
+ // PlaneList is used because entries in the main plane
+ // list and visible plane list of GfxFrameout are
+ // synchronised by index
+ int findIndexByObject(const reg_t object) const;
+ Plane *findByObject(const reg_t object) const;
+
+ /**
+ * Gets the priority of the top plane in the plane list.
+ */
+ int16 getTopPlanePriority() const;
+
+ /**
+ * Gets the priority of the top plane in the plane list
+ * created by a game script.
+ */
+ int16 getTopSciPlanePriority() const;
+
+ void add(Plane *plane);
+ void clear();
+ iterator erase(iterator it);
+ void erase(Plane *plane);
+ inline void sort() {
+ Common::sort(begin(), end(), sortHelper);
+ }
+ void remove_at(size_type index);
+};
+
+} // End of namespace Sci
+
+#endif
diff --git a/engines/sci/graphics/remap.cpp b/engines/sci/graphics/remap.cpp
new file mode 100644
index 0000000000..e331eaf971
--- /dev/null
+++ b/engines/sci/graphics/remap.cpp
@@ -0,0 +1,386 @@
+/* ScummVM - Graphic Adventure Engine
+ *
+ * ScummVM is the legal property of its developers, whose names
+ * are too numerous to list here. Please refer to the COPYRIGHT
+ * file distributed with this source distribution.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ *
+ */
+
+#include "sci/sci.h"
+#include "sci/resource.h"
+#include "sci/graphics/palette.h"
+#include "sci/graphics/palette32.h"
+#include "sci/graphics/remap.h"
+#include "sci/graphics/screen.h"
+
+namespace Sci {
+
+#pragma mark -
+#pragma mark SCI16 remapping (QFG4 demo)
+
+GfxRemap::GfxRemap(GfxPalette *palette)
+ : _palette(palette) {
+ _remapOn = false;
+ resetRemapping();
+}
+
+GfxRemap::~GfxRemap() {
+}
+
+byte GfxRemap::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 GfxRemap::resetRemapping() {
+ _remapOn = false;
+ _remappingPercentToSet = 0;
+
+ for (int i = 0; i < 256; i++) {
+ _remappingType[i] = kRemappingNone;
+ _remappingByPercent[i] = i;
+ _remappingByRange[i] = i;
+ }
+}
+
+void GfxRemap::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 = _palette->_sysPalette.colors[i].r * _remappingPercentToSet / 100;
+ byte g = _palette->_sysPalette.colors[i].g * _remappingPercentToSet / 100;
+ byte b = _palette->_sysPalette.colors[i].b * _remappingPercentToSet / 100;
+ _remappingByPercent[i] = _palette->kernelFindColor(r, g, b);
+ }
+
+ _remappingType[color] = kRemappingByPercent;
+}
+
+void GfxRemap::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;
+}
+
+void GfxRemap::updateRemapping() {
+ // Check if we need to reset remapping by percent with the new colors.
+ if (_remappingPercentToSet) {
+ for (int i = 0; i < 256; i++) {
+ byte r = _palette->_sysPalette.colors[i].r * _remappingPercentToSet / 100;
+ byte g = _palette->_sysPalette.colors[i].g * _remappingPercentToSet / 100;
+ byte b = _palette->_sysPalette.colors[i].b * _remappingPercentToSet / 100;
+ _remappingByPercent[i] = _palette->kernelFindColor(r, g, b);
+ }
+ }
+}
+
+#pragma mark -
+#pragma mark SCI32 remapping
+
+#ifdef ENABLE_SCI32
+
+GfxRemap32::GfxRemap32(GfxPalette32 *palette) : _palette(palette) {
+ for (int i = 0; i < REMAP_COLOR_COUNT; i++)
+ _remaps[i] = RemapParams(0, 0, 0, 0, 100, kRemappingNone);
+ _noMapStart = _noMapCount = 0;
+ _update = false;
+ _remapCount = 0;
+
+ // The remap range was 245 - 254 in SCI2, but was changed to 235 - 244 in SCI21 middle
+ _remapEndColor = (getSciVersion() >= SCI_VERSION_2_1_MIDDLE) ? 244 : 254;
+}
+
+void GfxRemap32::remapOff(byte color) {
+ if (!color) {
+ for (int i = 0; i < REMAP_COLOR_COUNT; i++)
+ _remaps[i] = RemapParams(0, 0, 0, 0, 100, kRemappingNone);
+
+ _remapCount = 0;
+ } else {
+ assert(_remapEndColor - color >= 0 && _remapEndColor - color < REMAP_COLOR_COUNT);
+ const byte index = _remapEndColor - color;
+ _remaps[index] = RemapParams(0, 0, 0, 0, 100, kRemappingNone);
+ _remapCount--;
+ }
+
+ _update = true;
+}
+
+void GfxRemap32::setRemappingRange(byte color, byte from, byte to, byte base) {
+ assert(_remapEndColor - color >= 0 && _remapEndColor - color < REMAP_COLOR_COUNT);
+ _remaps[_remapEndColor - color] = RemapParams(from, to, base, 0, 100, kRemappingByRange);
+ initColorArrays(_remapEndColor - color);
+ _remapCount++;
+ _update = true;
+}
+
+void GfxRemap32::setRemappingPercent(byte color, byte percent) {
+ assert(_remapEndColor - color >= 0 && _remapEndColor - color < REMAP_COLOR_COUNT);
+ _remaps[_remapEndColor - color] = RemapParams(0, 0, 0, 0, percent, kRemappingByPercent);
+ initColorArrays(_remapEndColor - color);
+ _remapCount++;
+ _update = true;
+}
+
+void GfxRemap32::setRemappingToGray(byte color, byte gray) {
+ assert(_remapEndColor - color >= 0 && _remapEndColor - color < REMAP_COLOR_COUNT);
+ _remaps[_remapEndColor - color] = RemapParams(0, 0, 0, gray, 100, kRemappingToGray);
+ initColorArrays(_remapEndColor - color);
+ _remapCount++;
+ _update = true;
+}
+
+void GfxRemap32::setRemappingToPercentGray(byte color, byte gray, byte percent) {
+ assert(_remapEndColor - color >= 0 && _remapEndColor - color < REMAP_COLOR_COUNT);
+ _remaps[_remapEndColor - color] = RemapParams(0, 0, 0, gray, percent, kRemappingToPercentGray);
+ initColorArrays(_remapEndColor - color);
+ _remapCount++;
+ _update = true;
+}
+
+void GfxRemap32::setNoMatchRange(byte from, byte count) {
+ _noMapStart = from;
+ _noMapCount = count;
+}
+
+bool GfxRemap32::remapEnabled(byte color) const {
+ assert(_remapEndColor - color >= 0 && _remapEndColor - color < REMAP_COLOR_COUNT);
+ const byte index = _remapEndColor - color;
+ return (_remaps[index].type != kRemappingNone);
+}
+
+byte GfxRemap32::remapColor(byte color, byte target) {
+ assert(_remapEndColor - color >= 0 && _remapEndColor - color < REMAP_COLOR_COUNT);
+ const byte index = _remapEndColor - color;
+ if (_remaps[index].type != kRemappingNone)
+ return _remaps[index].remap[target];
+ else
+ return target;
+}
+
+void GfxRemap32::initColorArrays(byte index) {
+ Palette *curPalette = &_palette->_sysPalette;
+ RemapParams *curRemap = &_remaps[index];
+
+ memcpy(curRemap->curColor, curPalette->colors, NON_REMAPPED_COLOR_COUNT * sizeof(Color));
+ memcpy(curRemap->targetColor, curPalette->colors, NON_REMAPPED_COLOR_COUNT * sizeof(Color));
+}
+
+bool GfxRemap32::updateRemap(byte index, bool palChanged) {
+ int result;
+ RemapParams *curRemap = &_remaps[index];
+ const Palette *curPalette = &_palette->_sysPalette;
+ const Palette *nextPalette = _palette->getNextPalette();
+ bool changed = false;
+
+ if (!_update && !palChanged)
+ return false;
+
+ Common::fill(_targetChanged, _targetChanged + NON_REMAPPED_COLOR_COUNT, false);
+
+ switch (curRemap->type) {
+ case kRemappingNone:
+ return false;
+ case kRemappingByRange:
+ for (int i = 0; i < NON_REMAPPED_COLOR_COUNT; i++) {
+ if (curRemap->from <= i && i <= curRemap->to)
+ result = i + curRemap->base;
+ else
+ result = i;
+
+ if (curRemap->remap[i] != result) {
+ changed = true;
+ curRemap->remap[i] = result;
+ }
+
+ curRemap->colorChanged[i] = true;
+ }
+ return changed;
+ case kRemappingByPercent:
+ for (int i = 1; i < NON_REMAPPED_COLOR_COUNT; i++) {
+ // NOTE: This method uses nextPalette instead of curPalette
+ Color color = nextPalette->colors[i];
+
+ if (curRemap->curColor[i] != color) {
+ curRemap->colorChanged[i] = true;
+ curRemap->curColor[i] = color;
+ }
+
+ if (curRemap->percent != curRemap->oldPercent || curRemap->colorChanged[i]) {
+ byte red = CLIP<byte>(color.r * curRemap->percent / 100, 0, 255);
+ byte green = CLIP<byte>(color.g * curRemap->percent / 100, 0, 255);
+ byte blue = CLIP<byte>(color.b * curRemap->percent / 100, 0, 255);
+ byte used = curRemap->targetColor[i].used;
+
+ Color newColor = { used, red, green, blue };
+ if (curRemap->targetColor[i] != newColor) {
+ _targetChanged[i] = true;
+ curRemap->targetColor[i] = newColor;
+ }
+ }
+ }
+
+ changed = applyRemap(index);
+ Common::fill(curRemap->colorChanged, curRemap->colorChanged + NON_REMAPPED_COLOR_COUNT, false);
+ curRemap->oldPercent = curRemap->percent;
+ return changed;
+ case kRemappingToGray:
+ for (int i = 1; i < NON_REMAPPED_COLOR_COUNT; i++) {
+ Color color = curPalette->colors[i];
+
+ if (curRemap->curColor[i] != color) {
+ curRemap->colorChanged[i] = true;
+ curRemap->curColor[i] = color;
+ }
+
+ if (curRemap->gray != curRemap->oldGray || curRemap->colorChanged[i]) {
+ byte lumosity = ((color.r * 77) + (color.g * 151) + (color.b * 28)) >> 8;
+ byte red = CLIP<byte>(color.r - ((color.r - lumosity) * curRemap->gray / 100), 0, 255);
+ byte green = CLIP<byte>(color.g - ((color.g - lumosity) * curRemap->gray / 100), 0, 255);
+ byte blue = CLIP<byte>(color.b - ((color.b - lumosity) * curRemap->gray / 100), 0, 255);
+ byte used = curRemap->targetColor[i].used;
+
+ Color newColor = { used, red, green, blue };
+ if (curRemap->targetColor[i] != newColor) {
+ _targetChanged[i] = true;
+ curRemap->targetColor[i] = newColor;
+ }
+ }
+ }
+
+ changed = applyRemap(index);
+ Common::fill(curRemap->colorChanged, curRemap->colorChanged + NON_REMAPPED_COLOR_COUNT, false);
+ curRemap->oldGray = curRemap->gray;
+ return changed;
+ case kRemappingToPercentGray:
+ for (int i = 1; i < NON_REMAPPED_COLOR_COUNT; i++) {
+ Color color = curPalette->colors[i];
+
+ if (curRemap->curColor[i] != color) {
+ curRemap->colorChanged[i] = true;
+ curRemap->curColor[i] = color;
+ }
+
+ if (curRemap->percent != curRemap->oldPercent || curRemap->gray != curRemap->oldGray || curRemap->colorChanged[i]) {
+ byte lumosity = ((color.r * 77) + (color.g * 151) + (color.b * 28)) >> 8;
+ lumosity = lumosity * curRemap->percent / 100;
+ byte red = CLIP<byte>(color.r - ((color.r - lumosity) * curRemap->gray / 100), 0, 255);
+ byte green = CLIP<byte>(color.g - ((color.g - lumosity) * curRemap->gray / 100), 0, 255);
+ byte blue = CLIP<byte>(color.b - ((color.b - lumosity) * curRemap->gray / 100), 0, 255);
+ byte used = curRemap->targetColor[i].used;
+
+ Color newColor = { used, red, green, blue };
+ if (curRemap->targetColor[i] != newColor) {
+ _targetChanged[i] = true;
+ curRemap->targetColor[i] = newColor;
+ }
+ }
+ }
+
+ changed = applyRemap(index);
+ Common::fill(curRemap->colorChanged, curRemap->colorChanged + NON_REMAPPED_COLOR_COUNT, false);
+ curRemap->oldPercent = curRemap->percent;
+ curRemap->oldGray = curRemap->gray;
+ return changed;
+ default:
+ return false;
+ }
+}
+
+static int colorDistance(Color a, Color b) {
+ int rDiff = (a.r - b.r) * (a.r - b.r);
+ int gDiff = (a.g - b.g) * (a.g - b.g);
+ int bDiff = (a.b - b.b) * (a.b - b.b);
+ return rDiff + gDiff + bDiff;
+}
+
+bool GfxRemap32::applyRemap(byte index) {
+ RemapParams *curRemap = &_remaps[index];
+ const bool *cycleMap = _palette->getCyclemap();
+ bool unmappedColors[NON_REMAPPED_COLOR_COUNT];
+ Color newColors[NON_REMAPPED_COLOR_COUNT];
+ bool changed = false;
+
+ Common::fill(unmappedColors, unmappedColors + NON_REMAPPED_COLOR_COUNT, false);
+ if (_noMapCount)
+ Common::fill(unmappedColors + _noMapStart, unmappedColors + _noMapStart + _noMapCount, true);
+
+ for (int i = 0; i < NON_REMAPPED_COLOR_COUNT; i++) {
+ if (cycleMap[i])
+ unmappedColors[i] = true;
+ }
+
+ int curColor = 0;
+ for (int i = 1; i < NON_REMAPPED_COLOR_COUNT; i++) {
+ if (curRemap->colorChanged[i] && !unmappedColors[i])
+ newColors[curColor++] = curRemap->curColor[i];
+ }
+
+ for (int i = 1; i < NON_REMAPPED_COLOR_COUNT; i++) {
+ Color targetColor = curRemap->targetColor[i];
+ bool colorChanged = curRemap->colorChanged[curRemap->remap[i]];
+
+ if (!_targetChanged[i] && !colorChanged)
+ continue;
+
+ if (_targetChanged[i] && colorChanged)
+ if (curRemap->distance[i] < 100 && colorDistance(targetColor, curRemap->curColor[curRemap->remap[i]]) <= curRemap->distance[i])
+ continue;
+
+ int diff = 0;
+ int16 result = _palette->matchColor(targetColor.r, targetColor.g, targetColor.b, curRemap->distance[i], diff, unmappedColors);
+ if (result != -1 && curRemap->remap[i] != result) {
+ changed = true;
+ curRemap->remap[i] = result;
+ curRemap->distance[i] = diff;
+ }
+ }
+
+ return changed;
+}
+
+bool GfxRemap32::remapAllTables(bool palChanged) {
+ bool changed = false;
+
+ for (int i = 0; i < REMAP_COLOR_COUNT; i++) {
+ changed |= updateRemap(i, palChanged);
+ }
+
+ _update = false;
+ return changed;
+}
+
+#endif
+
+} // End of namespace Sci
diff --git a/engines/sci/graphics/remap.h b/engines/sci/graphics/remap.h
new file mode 100644
index 0000000000..d012568f7f
--- /dev/null
+++ b/engines/sci/graphics/remap.h
@@ -0,0 +1,154 @@
+/* ScummVM - Graphic Adventure Engine
+ *
+ * ScummVM is the legal property of its developers, whose names
+ * are too numerous to list here. Please refer to the COPYRIGHT
+ * file distributed with this source distribution.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ *
+ */
+
+#ifndef SCI_GRAPHICS_REMAP_H
+#define SCI_GRAPHICS_REMAP_H
+
+#include "common/array.h"
+#include "sci/graphics/helpers.h"
+
+namespace Sci {
+
+class GfxScreen;
+
+enum ColorRemappingType {
+ kRemappingNone = 0,
+ kRemappingByRange = 1,
+ kRemappingByPercent = 2,
+ kRemappingToGray = 3,
+ kRemappingToPercentGray = 4
+};
+
+#define REMAP_COLOR_COUNT 9
+#define NON_REMAPPED_COLOR_COUNT 236
+
+/**
+ * Remap class, handles color remapping
+ */
+class GfxRemap {
+public:
+ GfxRemap(GfxPalette *_palette);
+ ~GfxRemap();
+
+ void resetRemapping();
+ void setRemappingPercent(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 updateRemapping();
+
+private:
+ GfxScreen *_screen;
+ GfxPalette *_palette;
+
+ bool _remapOn;
+ ColorRemappingType _remappingType[256];
+ byte _remappingByPercent[256];
+ byte _remappingByRange[256];
+ uint16 _remappingPercentToSet;
+};
+
+#ifdef ENABLE_SCI32
+
+struct RemapParams {
+ byte from;
+ byte to;
+ byte base;
+ byte gray;
+ byte oldGray;
+ byte percent;
+ byte oldPercent;
+ ColorRemappingType type;
+ Color curColor[256];
+ Color targetColor[256];
+ byte distance[256];
+ byte remap[256];
+ bool colorChanged[256];
+
+ RemapParams() {
+ from = to = base = gray = oldGray = percent = oldPercent = 0;
+ type = kRemappingNone;
+
+ // curColor and targetColor are initialized in GfxRemap32::initColorArrays
+ memset(curColor, 0, 256 * sizeof(Color));
+ memset(targetColor, 0, 256 * sizeof(Color));
+ memset(distance, 0, 256);
+ for (int i = 0; i < NON_REMAPPED_COLOR_COUNT; i++)
+ remap[i] = i;
+ Common::fill(colorChanged, colorChanged + ARRAYSIZE(colorChanged), true);
+ }
+
+ RemapParams(byte from_, byte to_, byte base_, byte gray_, byte percent_, ColorRemappingType type_) {
+ from = from_;
+ to = to_;
+ base = base_;
+ gray = oldGray = gray_;
+ percent = oldPercent = percent_;
+ type = type_;
+
+ // curColor and targetColor are initialized in GfxRemap32::initColorArrays
+ memset(curColor, 0, 256 * sizeof(Color));
+ memset(targetColor, 0, 256 * sizeof(Color));
+ memset(distance, 0, 256);
+ for (int i = 0; i < NON_REMAPPED_COLOR_COUNT; i++)
+ remap[i] = i;
+ Common::fill(colorChanged, colorChanged + ARRAYSIZE(colorChanged), true);
+ }
+};
+
+class GfxRemap32 {
+public:
+ GfxRemap32(GfxPalette32 *palette);
+ ~GfxRemap32() {}
+
+ void remapOff(byte color);
+ void setRemappingRange(byte color, byte from, byte to, byte base);
+ void setRemappingPercent(byte color, byte percent);
+ void setRemappingToGray(byte color, byte gray);
+ void setRemappingToPercentGray(byte color, byte gray, byte percent);
+ void setNoMatchRange(byte from, byte count);
+ bool remapEnabled(byte color) const;
+ byte remapColor(byte color, byte target);
+ bool remapAllTables(bool palChanged);
+ int getRemapCount() const { return _remapCount; }
+ int getStartColor() const { return _remapEndColor - REMAP_COLOR_COUNT + 1; }
+ int getEndColor() const { return _remapEndColor; }
+private:
+ GfxPalette32 *_palette;
+ RemapParams _remaps[REMAP_COLOR_COUNT];
+ bool _update;
+ byte _noMapStart, _noMapCount;
+ bool _targetChanged[NON_REMAPPED_COLOR_COUNT];
+ byte _remapEndColor;
+ int _remapCount;
+
+ void initColorArrays(byte index);
+ bool applyRemap(byte index);
+ bool updateRemap(byte index, bool palChanged);
+};
+#endif
+
+} // End of namespace Sci
+
+#endif
diff --git a/engines/sci/graphics/screen.cpp b/engines/sci/graphics/screen.cpp
index 4cd6344600..c977a93817 100644
--- a/engines/sci/graphics/screen.cpp
+++ b/engines/sci/graphics/screen.cpp
@@ -214,36 +214,6 @@ GfxScreen::GfxScreen(ResourceManager *resMan) : _resMan(resMan) {
error("Unknown SCI1.1 Mac game");
} else
initGraphics(_displayWidth, _displayHeight, _displayWidth > 320);
-
- // Initialize code pointers
- _vectorAdjustCoordinatePtr = &GfxScreen::vectorAdjustCoordinateNOP;
- _vectorAdjustLineCoordinatesPtr = &GfxScreen::vectorAdjustLineCoordinatesNOP;
- _vectorIsFillMatchPtr = &GfxScreen::vectorIsFillMatchNormal;
- _vectorPutPixelPtr = &GfxScreen::putPixelNormal;
- _vectorPutLinePixelPtr = &GfxScreen::putPixel;
- _vectorGetPixelPtr = &GfxScreen::getPixelNormal;
- _putPixelPtr = &GfxScreen::putPixelNormal;
- _getPixelPtr = &GfxScreen::getPixelNormal;
-
- switch (_upscaledHires) {
- case GFX_SCREEN_UPSCALED_480x300:
- _vectorAdjustCoordinatePtr = &GfxScreen::vectorAdjustCoordinate480x300Mac;
- _vectorAdjustLineCoordinatesPtr = &GfxScreen::vectorAdjustLineCoordinates480x300Mac;
- // vectorPutPixel -> we already adjust coordinates for vector code, that's why we can set pixels directly
- // vectorGetPixel -> see vectorPutPixel
- _vectorPutLinePixelPtr = &GfxScreen::vectorPutLinePixel480x300Mac;
- _putPixelPtr = &GfxScreen::putPixelAllUpscaled;
- _getPixelPtr = &GfxScreen::getPixelUpscaled;
- break;
- case GFX_SCREEN_UPSCALED_640x400:
- case GFX_SCREEN_UPSCALED_640x440:
- case GFX_SCREEN_UPSCALED_640x480:
- _vectorPutPixelPtr = &GfxScreen::putPixelDisplayUpscaled;
- _putPixelPtr = &GfxScreen::putPixelDisplayUpscaled;
- break;
- case GFX_SCREEN_UPSCALED_DISABLED:
- break;
- }
}
GfxScreen::~GfxScreen() {
@@ -270,17 +240,26 @@ void GfxScreen::copyToScreen() {
}
void GfxScreen::copyFromScreen(byte *buffer) {
- // TODO this ignores the pitch
Graphics::Surface *screen = g_system->lockScreen();
- memcpy(buffer, screen->getPixels(), _displayPixels);
+
+ if (screen->pitch == _displayWidth) {
+ memcpy(buffer, screen->getPixels(), _displayPixels);
+ } else {
+ const byte *src = (const byte *)screen->getPixels();
+ uint height = _displayHeight;
+
+ while (height--) {
+ memcpy(buffer, src, _displayWidth);
+ buffer += _displayWidth;
+ src += screen->pitch;
+ }
+ }
+
g_system->unlockScreen();
}
void GfxScreen::kernelSyncWithFramebuffer() {
- // TODO this ignores the pitch
- Graphics::Surface *screen = g_system->lockScreen();
- memcpy(_displayScreen, screen->getPixels(), _displayPixels);
- g_system->unlockScreen();
+ copyFromScreen(_displayScreen);
}
void GfxScreen::copyRectToScreen(const Common::Rect &rect) {
@@ -325,40 +304,68 @@ byte GfxScreen::getDrawingMask(byte color, byte prio, byte control) {
return flag;
}
-void GfxScreen::vectorAdjustCoordinateNOP(int16 *x, int16 *y) {
+void GfxScreen::vectorAdjustLineCoordinates(int16 *left, int16 *top, int16 *right, int16 *bottom, byte drawMask, byte color, byte priority, byte control) {
+ switch (_upscaledHires) {
+ case GFX_SCREEN_UPSCALED_480x300: {
+ int16 displayLeft = (*left * 3) / 2;
+ int16 displayRight = (*right * 3) / 2;
+ int16 displayTop = (*top * 3) / 2;
+ int16 displayBottom = (*bottom * 3) / 2;
+
+ if (displayLeft < displayRight) {
+ // one more pixel to the left, one more pixel to the right
+ if (displayLeft > 0)
+ vectorPutLinePixel(displayLeft - 1, displayTop, drawMask, color, priority, control);
+ vectorPutLinePixel(displayRight + 1, displayBottom, drawMask, color, priority, control);
+ } else if (displayLeft > displayRight) {
+ if (displayRight > 0)
+ vectorPutLinePixel(displayRight - 1, displayBottom, drawMask, color, priority, control);
+ vectorPutLinePixel(displayLeft + 1, displayTop, drawMask, color, priority, control);
+ }
+ *left = displayLeft;
+ *top = displayTop;
+ *right = displayRight;
+ *bottom = displayBottom;
+ break;
+ }
+ default:
+ break;
+ }
}
-void GfxScreen::vectorAdjustCoordinate480x300Mac(int16 *x, int16 *y) {
- *x = _upscaledWidthMapping[*x];
- *y = _upscaledHeightMapping[*y];
+// This is called from vector drawing to put a pixel at a certain location
+void GfxScreen::vectorPutLinePixel(int16 x, int16 y, byte drawMask, byte color, byte priority, byte control) {
+ if (_upscaledHires == GFX_SCREEN_UPSCALED_480x300) {
+ vectorPutLinePixel480x300(x, y, drawMask, color, priority, control);
+ return;
+ }
+
+ // For anything else forward to the regular putPixel
+ putPixel(x, y, drawMask, color, priority, control);
}
-void GfxScreen::vectorAdjustLineCoordinatesNOP(int16 *left, int16 *top, int16 *right, int16 *bottom, byte drawMask, byte color, byte priority, byte control) {
+// Special 480x300 Mac putPixel for vector line drawing, also draws an additional pixel below the actual one
+void GfxScreen::vectorPutLinePixel480x300(int16 x, int16 y, byte drawMask, byte color, byte priority, byte control) {
+ int offset = y * _width + x;
+
+ if (drawMask & GFX_SCREEN_MASK_VISUAL) {
+ // also set pixel below actual pixel
+ _visualScreen[offset] = color;
+ _visualScreen[offset + _width] = color;
+ _displayScreen[offset] = color;
+ _displayScreen[offset + _displayWidth] = color;
+ }
+ if (drawMask & GFX_SCREEN_MASK_PRIORITY) {
+ _priorityScreen[offset] = priority;
+ _priorityScreen[offset + _width] = priority;
+ }
+ if (drawMask & GFX_SCREEN_MASK_CONTROL) {
+ _controlScreen[offset] = control;
+ _controlScreen[offset + _width] = control;
+ }
}
-void GfxScreen::vectorAdjustLineCoordinates480x300Mac(int16 *left, int16 *top, int16 *right, int16 *bottom, byte drawMask, byte color, byte priority, byte control) {
- int16 displayLeft = _upscaledWidthMapping[*left];
- int16 displayRight = _upscaledWidthMapping[*right];
- int16 displayTop = _upscaledHeightMapping[*top];
- int16 displayBottom = _upscaledHeightMapping[*bottom];
-
- if (displayLeft < displayRight) {
- // one more pixel to the left, one more pixel to the right
- if (displayLeft > 0)
- vectorPutLinePixel(displayLeft - 1, displayTop, drawMask, color, priority, control);
- vectorPutLinePixel(displayRight + 1, displayBottom, drawMask, color, priority, control);
- } else if (displayLeft > displayRight) {
- if (displayRight > 0)
- vectorPutLinePixel(displayRight - 1, displayBottom, drawMask, color, priority, control);
- vectorPutLinePixel(displayLeft + 1, displayTop, drawMask, color, priority, control);
- }
- *left = displayLeft;
- *top = displayTop;
- *right = displayRight;
- *bottom = displayBottom;
-}
-
-byte GfxScreen::vectorIsFillMatchNormal(int16 x, int16 y, byte screenMask, byte checkForColor, byte checkForPriority, byte checkForControl, bool isEGA) {
+byte GfxScreen::vectorIsFillMatch(int16 x, int16 y, byte screenMask, byte checkForColor, byte checkForPriority, byte checkForControl, bool isEGA) {
int offset = y * _width + x;
byte match = 0;
@@ -388,132 +395,6 @@ byte GfxScreen::vectorIsFillMatchNormal(int16 x, int16 y, byte screenMask, byte
return match;
}
-// Special 480x300 Mac putPixel for vector line drawing, also draws an additional pixel below the actual one
-void GfxScreen::vectorPutLinePixel480x300Mac(int16 x, int16 y, byte drawMask, byte color, byte priority, byte control) {
- int offset = y * _width + x;
-
- if (drawMask & GFX_SCREEN_MASK_VISUAL) {
- _visualScreen[offset] = color;
- _visualScreen[offset + _width] = color;
- _displayScreen[offset] = color;
- // also set pixel below actual pixel
- _displayScreen[offset + _displayWidth] = color;
- }
- if (drawMask & GFX_SCREEN_MASK_PRIORITY) {
- _priorityScreen[offset] = priority;
- _priorityScreen[offset + _width] = priority;
- }
- if (drawMask & GFX_SCREEN_MASK_CONTROL) {
- _controlScreen[offset] = control;
- _controlScreen[offset + _width] = control;
- }
-}
-
-// Directly sets a pixel on various screens, display is not upscaled
-void GfxScreen::putPixelNormal(int16 x, int16 y, byte drawMask, byte color, byte priority, byte control) {
- int offset = y * _width + x;
-
- if (drawMask & GFX_SCREEN_MASK_VISUAL) {
- _visualScreen[offset] = color;
- _displayScreen[offset] = color;
- }
- if (drawMask & GFX_SCREEN_MASK_PRIORITY)
- _priorityScreen[offset] = priority;
- if (drawMask & GFX_SCREEN_MASK_CONTROL)
- _controlScreen[offset] = control;
-}
-
-// Directly sets a pixel on various screens, display IS upscaled
-void GfxScreen::putPixelDisplayUpscaled(int16 x, int16 y, byte drawMask, byte color, byte priority, byte control) {
- int offset = y * _width + x;
-
- if (drawMask & GFX_SCREEN_MASK_VISUAL) {
- _visualScreen[offset] = color;
- putScaledPixelOnScreen(_displayScreen, x, y, color);
- }
- if (drawMask & GFX_SCREEN_MASK_PRIORITY)
- _priorityScreen[offset] = priority;
- if (drawMask & GFX_SCREEN_MASK_CONTROL)
- _controlScreen[offset] = control;
-}
-
-// Directly sets a pixel on various screens, ALL screens ARE upscaled
-void GfxScreen::putPixelAllUpscaled(int16 x, int16 y, byte drawMask, byte color, byte priority, byte control) {
- if (drawMask & GFX_SCREEN_MASK_VISUAL) {
- putScaledPixelOnScreen(_visualScreen, x, y, color);
- putScaledPixelOnScreen(_displayScreen, x, y, color);
- }
- if (drawMask & GFX_SCREEN_MASK_PRIORITY)
- putScaledPixelOnScreen(_priorityScreen, x, y, priority);
- if (drawMask & GFX_SCREEN_MASK_CONTROL)
- putScaledPixelOnScreen(_controlScreen, x, y, control);
-}
-
-/**
- * This is used to put font pixels onto the screen - we adjust differently, so that we won't
- * do triple pixel lines in any case on upscaled hires. That way the font will not get distorted
- * Sierra SCI didn't do this
- */
-void GfxScreen::putFontPixel(int16 startingY, int16 x, int16 y, byte color) {
- int16 actualY = startingY + y;
- if (_fontIsUpscaled) {
- // Do not scale ourselves, but put it on the display directly
- putPixelOnDisplay(x, actualY, color);
- } else {
- int offset = actualY * _width + x;
-
- _visualScreen[offset] = color;
- switch (_upscaledHires) {
- case GFX_SCREEN_UPSCALED_DISABLED:
- _displayScreen[offset] = color;
- break;
- case GFX_SCREEN_UPSCALED_640x400:
- case GFX_SCREEN_UPSCALED_640x440:
- case GFX_SCREEN_UPSCALED_640x480: {
- // to 1-> 4 pixels upscaling for all of those, so that fonts won't look weird
- int displayOffset = (_upscaledHeightMapping[startingY] + y * 2) * _displayWidth + x * 2;
- _displayScreen[displayOffset] = color;
- _displayScreen[displayOffset + 1] = color;
- displayOffset += _displayWidth;
- _displayScreen[displayOffset] = color;
- _displayScreen[displayOffset + 1] = color;
- break;
- }
- default:
- putScaledPixelOnScreen(_displayScreen, x, actualY, color);
- break;
- }
- }
-}
-
-/**
- * This will just change a pixel directly on displayscreen. It is supposed to be
- * only used on upscaled-Hires games where hires content needs to get drawn ONTO
- * the upscaled display screen (like japanese fonts, hires portraits, etc.).
- */
-void GfxScreen::putPixelOnDisplay(int16 x, int16 y, byte color) {
- int offset = y * _displayWidth + x;
- _displayScreen[offset] = color;
-}
-
-//void GfxScreen::putScaledPixelOnDisplay(int16 x, int16 y, byte color) {
-//}
-
-void GfxScreen::putScaledPixelOnScreen(byte *screen, int16 x, int16 y, byte data) {
- int displayOffset = _upscaledHeightMapping[y] * _displayWidth + _upscaledWidthMapping[x];
- int heightOffsetBreak = (_upscaledHeightMapping[y + 1] - _upscaledHeightMapping[y]) * _displayWidth;
- int heightOffset = 0;
- int widthOffsetBreak = _upscaledWidthMapping[x + 1] - _upscaledWidthMapping[x];
- do {
- int widthOffset = 0;
- do {
- screen[displayOffset + heightOffset + widthOffset] = data;
- widthOffset++;
- } while (widthOffset != widthOffsetBreak);
- heightOffset += _displayWidth;
- } while (heightOffset != heightOffsetBreak);
-}
-
/**
* Sierra's Bresenham line drawing.
* WARNING: Do not replace this with Graphics::drawLine(), as this causes issues
@@ -595,16 +476,6 @@ void GfxScreen::putKanjiChar(Graphics::FontSJIS *commonFont, int16 x, int16 y, u
commonFont->drawChar(displayPtr, chr, _displayWidth, 1, color, 0, -1, -1);
}
-byte GfxScreen::getPixelNormal(byte *screen, int16 x, int16 y) {
- return screen[y * _width + x];
-}
-
-byte GfxScreen::getPixelUpscaled(byte *screen, int16 x, int16 y) {
- int16 mappedX = _upscaledWidthMapping[x];
- int16 mappedY = _upscaledHeightMapping[y];
- return screen[mappedY * _width + mappedX];
-}
-
int GfxScreen::bitsGetDataSize(Common::Rect rect, byte mask) {
int byteCount = sizeof(rect) + sizeof(mask);
int pixels = rect.width() * rect.height();
@@ -615,7 +486,7 @@ int GfxScreen::bitsGetDataSize(Common::Rect rect, byte mask) {
} else {
int rectHeight = _upscaledHeightMapping[rect.bottom] - _upscaledHeightMapping[rect.top];
int rectWidth = _upscaledWidthMapping[rect.right] - _upscaledWidthMapping[rect.left];
- byteCount += rectHeight * rect.width() * rectWidth; // _displayScreen (upscaled hires)
+ byteCount += rectHeight * rectWidth; // _displayScreen (upscaled hires)
}
}
if (mask & GFX_SCREEN_MASK_PRIORITY) {
@@ -629,7 +500,6 @@ int GfxScreen::bitsGetDataSize(Common::Rect rect, byte mask) {
error("bitsGetDataSize() called w/o being in upscaled hires mode");
byteCount += pixels; // _displayScreen (coordinates actually are given to us for hires displayScreen)
}
-
return byteCount;
}
@@ -796,7 +666,7 @@ void GfxScreen::dither(bool addToFlag) {
*displayPtr = color;
break;
default:
- putScaledPixelOnScreen(_displayScreen, x, y, color);
+ putScaledPixelOnDisplay(x, y, color);
break;
}
*visualPtr = color;
@@ -828,7 +698,7 @@ void GfxScreen::dither(bool addToFlag) {
*displayPtr = ditheredColor;
break;
default:
- putScaledPixelOnScreen(_displayScreen, x, y, ditheredColor);
+ putScaledPixelOnDisplay(x, y, ditheredColor);
break;
}
color = ((x^y) & 1) ? color >> 4 : color & 0x0F;
diff --git a/engines/sci/graphics/screen.h b/engines/sci/graphics/screen.h
index 1c946ef02f..65416252f6 100644
--- a/engines/sci/graphics/screen.h
+++ b/engines/sci/graphics/screen.h
@@ -76,6 +76,11 @@ public:
byte getColorWhite() { return _colorWhite; }
byte getColorDefaultVectorData() { return _colorDefaultVectorData; }
+#ifdef ENABLE_SCI32
+ byte *getDisplayScreen() { return _displayScreen; }
+ byte *getPriorityScreen() { return _priorityScreen; }
+#endif
+
void clearForRestoreGame();
void copyToScreen();
void copyFromScreen(byte *buffer);
@@ -84,51 +89,16 @@ public:
void copyDisplayRectToScreen(const Common::Rect &rect);
void copyRectToScreen(const Common::Rect &rect, int16 x, int16 y);
- // calls to code pointers
- void inline vectorAdjustCoordinate (int16 *x, int16 *y) {
- (this->*_vectorAdjustCoordinatePtr)(x, y);
- }
- void inline vectorAdjustLineCoordinates (int16 *left, int16 *top, int16 *right, int16 *bottom, byte drawMask, byte color, byte priority, byte control) {
- (this->*_vectorAdjustLineCoordinatesPtr)(left, top, right, bottom, drawMask, color, priority, control);
- }
- byte inline vectorIsFillMatch (int16 x, int16 y, byte screenMask, byte t_color, byte t_pri, byte t_con, bool isEGA) {
- return (this->*_vectorIsFillMatchPtr)(x, y, screenMask, t_color, t_pri, t_con, isEGA);
- }
- void inline vectorPutPixel(int16 x, int16 y, byte drawMask, byte color, byte priority, byte control) {
- (this->*_vectorPutPixelPtr)(x, y, drawMask, color, priority, control);
- }
- void inline vectorPutLinePixel(int16 x, int16 y, byte drawMask, byte color, byte priority, byte control) {
- (this->*_vectorPutLinePixelPtr)(x, y, drawMask, color, priority, control);
- }
- byte inline vectorGetVisual(int16 x, int16 y) {
- return (this->*_vectorGetPixelPtr)(_visualScreen, x, y);
- }
- byte inline vectorGetPriority(int16 x, int16 y) {
- return (this->*_vectorGetPixelPtr)(_priorityScreen, x, y);
- }
- byte inline vectorGetControl(int16 x, int16 y) {
- return (this->*_vectorGetPixelPtr)(_controlScreen, x, y);
- }
-
-
- void inline putPixel(int16 x, int16 y, byte drawMask, byte color, byte priority, byte control) {
- (this->*_putPixelPtr)(x, y, drawMask, color, priority, control);
- }
+ // Vector drawing
+private:
+ void vectorPutLinePixel(int16 x, int16 y, byte drawMask, byte color, byte priority, byte control);
+ void vectorPutLinePixel480x300(int16 x, int16 y, byte drawMask, byte color, byte priority, byte control);
- byte inline getVisual(int16 x, int16 y) {
- return (this->*_getPixelPtr)(_visualScreen, x, y);
- }
- byte inline getPriority(int16 x, int16 y) {
- return (this->*_getPixelPtr)(_priorityScreen, x, y);
- }
- byte inline getControl(int16 x, int16 y) {
- return (this->*_getPixelPtr)(_controlScreen, x, y);
- }
+public:
+ void vectorAdjustLineCoordinates(int16 *left, int16 *top, int16 *right, int16 *bottom, byte drawMask, byte color, byte priority, byte control);
+ byte vectorIsFillMatch(int16 x, int16 y, byte screenMask, byte checkForColor, byte checkForPriority, byte checkForControl, bool isEGA);
byte getDrawingMask(byte color, byte prio, byte control);
- //void putPixel(int16 x, int16 y, byte drawMask, byte color, byte prio, byte control);
- void putFontPixel(int16 startingY, int16 x, int16 y, byte color);
- void putPixelOnDisplay(int16 x, int16 y, byte color);
void drawLine(Common::Point startPoint, Common::Point endPoint, byte color, byte prio, byte control);
void drawLine(int16 left, int16 top, int16 right, int16 bottom, byte color, byte prio, byte control) {
drawLine(Common::Point(left, top), Common::Point(right, bottom), color, prio, control);
@@ -206,8 +176,8 @@ private:
byte *_controlScreen;
/**
- * This screen is the one that is actually displayed to the user. It may be
- * 640x400 for japanese SCI1 games. SCI0 games may be undithered in here.
+ * This screen is the one, where pixels are copied out of into the frame buffer.
+ * It may be 640x400 for japanese SCI1 games. SCI0 games may be undithered in here.
* Only read from this buffer for Save/ShowBits usage.
*/
byte *_displayScreen;
@@ -215,8 +185,8 @@ private:
ResourceManager *_resMan;
/**
- * Pointer to the currently active screen (changing it only required for
- * debug purposes).
+ * Pointer to the currently active screen (changing only required for
+ * debug purposes, to show for example the priority screen).
*/
byte *_activeScreen;
@@ -239,38 +209,241 @@ private:
*/
bool _fontIsUpscaled;
- // dynamic code
- void (GfxScreen::*_vectorAdjustCoordinatePtr) (int16 *x, int16 *y);
- void vectorAdjustCoordinateNOP (int16 *x, int16 *y);
- void vectorAdjustCoordinate480x300Mac (int16 *x, int16 *y);
- void (GfxScreen::*_vectorAdjustLineCoordinatesPtr) (int16 *left, int16 *top, int16 *right, int16 *bottom, byte drawMask, byte color, byte priority, byte control);
- void vectorAdjustLineCoordinatesNOP (int16 *left, int16 *top, int16 *right, int16 *bottom, byte drawMask, byte color, byte priority, byte control);
- void vectorAdjustLineCoordinates480x300Mac (int16 *left, int16 *top, int16 *right, int16 *bottom, byte drawMask, byte color, byte priority, byte control);
-
- byte (GfxScreen::*_vectorIsFillMatchPtr) (int16 x, int16 y, byte screenMask, byte t_color, byte t_pri, byte t_con, bool isEGA);
- byte vectorIsFillMatchNormal (int16 x, int16 y, byte screenMask, byte t_color, byte t_pri, byte t_con, bool isEGA);
- byte vectorIsFillMatch480x300Mac (int16 x, int16 y, byte screenMask, byte t_color, byte t_pri, byte t_con, bool isEGA);
+ // pixel related code, in header so that it can be inlined for performance
+public:
+ void putPixel(int16 x, int16 y, byte drawMask, byte color, byte priority, byte control) {
+ if (_upscaledHires == GFX_SCREEN_UPSCALED_480x300) {
+ putPixel480x300(x, y, drawMask, color, priority, control);
+ return;
+ }
+
+ // Set pixel for visual, priority and control map directly, those are not upscaled
+ int offset = y * _width + x;
+
+ if (drawMask & GFX_SCREEN_MASK_VISUAL) {
+ _visualScreen[offset] = color;
+
+ int displayOffset = 0;
+
+ switch (_upscaledHires) {
+ case GFX_SCREEN_UPSCALED_DISABLED:
+ displayOffset = offset;
+ _displayScreen[displayOffset] = color;
+ break;
+
+ case GFX_SCREEN_UPSCALED_640x400:
+ case GFX_SCREEN_UPSCALED_640x440:
+ case GFX_SCREEN_UPSCALED_640x480:
+ putScaledPixelOnDisplay(x, y, color);
+ break;
+ default:
+ break;
+ }
+ }
+ if (drawMask & GFX_SCREEN_MASK_PRIORITY) {
+ _priorityScreen[offset] = priority;
+ }
+ if (drawMask & GFX_SCREEN_MASK_CONTROL) {
+ _controlScreen[offset] = control;
+ }
+ }
- void (GfxScreen::*_vectorPutPixelPtr) (int16 x, int16 y, byte drawMask, byte color, byte priority, byte control);
- void vectorPutPixel480x300Mac (int16 x, int16 y, byte drawMask, byte color, byte priority, byte control);
+ void putPixel480x300(int16 x, int16 y, byte drawMask, byte color, byte priority, byte control) {
+ int offset = ((y * 3) / 2 * _width) + ((x * 3) / 2);
+
+ // All maps are upscaled
+ // TODO: figure out, what Sierra exactly did on Mac for these games
+ if (drawMask & GFX_SCREEN_MASK_VISUAL) {
+ putPixel480x300Worker(x, y, offset, _visualScreen, color);
+ putPixel480x300Worker(x, y, offset, _displayScreen, color);
+ }
+ if (drawMask & GFX_SCREEN_MASK_PRIORITY) {
+ putPixel480x300Worker(x, y, offset, _priorityScreen, priority);
+ }
+ if (drawMask & GFX_SCREEN_MASK_CONTROL) {
+ putPixel480x300Worker(x, y, offset, _controlScreen, control);
+ }
+ }
+ void putPixel480x300Worker(int16 x, int16 y, int offset, byte *screen, byte byteToSet) {
+ screen[offset] = byteToSet;
+ if (x & 1)
+ screen[offset + 1] = byteToSet;
+ if (y & 1)
+ screen[offset + _width] = byteToSet;
+ if ((x & 1) && (y & 1))
+ screen[offset + _width + 1] = byteToSet;
+ }
- void (GfxScreen::*_vectorPutLinePixelPtr) (int16 x, int16 y, byte drawMask, byte color, byte priority, byte control);
- void vectorPutLinePixel480x300Mac (int16 x, int16 y, byte drawMask, byte color, byte priority, byte control);
+ // This is called from vector drawing to put a pixel at a certain location
+ void vectorPutPixel(int16 x, int16 y, byte drawMask, byte color, byte priority, byte control) {
+ switch (_upscaledHires) {
+ case GFX_SCREEN_UPSCALED_640x400:
+ case GFX_SCREEN_UPSCALED_640x440:
+ case GFX_SCREEN_UPSCALED_640x480:
+ // For regular upscaled modes forward to the regular putPixel
+ putPixel(x, y, drawMask, color, priority, control);
+ return;
+ break;
+
+ default:
+ break;
+ }
+
+ // For non-upscaled mode and 480x300 Mac put pixels directly
+ int offset = y * _width + x;
+
+ if (drawMask & GFX_SCREEN_MASK_VISUAL) {
+ _visualScreen[offset] = color;
+ _displayScreen[offset] = color;
+ }
+ if (drawMask & GFX_SCREEN_MASK_PRIORITY) {
+ _priorityScreen[offset] = priority;
+ }
+ if (drawMask & GFX_SCREEN_MASK_CONTROL) {
+ _controlScreen[offset] = control;
+ }
+ }
- byte (GfxScreen::*_vectorGetPixelPtr) (byte *screen, int16 x, int16 y);
+ /**
+ * This will just change a pixel directly on displayscreen. It is supposed to be
+ * only used on upscaled-Hires games where hires content needs to get drawn ONTO
+ * the upscaled display screen (like japanese fonts, hires portraits, etc.).
+ */
+ void putPixelOnDisplay(int16 x, int16 y, byte color) {
+ int offset = y * _displayWidth + x;
+ _displayScreen[offset] = color;
+ }
- void (GfxScreen::*_putPixelPtr) (int16 x, int16 y, byte drawMask, byte color, byte priority, byte control);
- void putPixelNormal (int16 x, int16 y, byte drawMask, byte color, byte priority, byte control);
- void putPixelDisplayUpscaled (int16 x, int16 y, byte drawMask, byte color, byte priority, byte control);
- void putPixelAllUpscaled (int16 x, int16 y, byte drawMask, byte color, byte priority, byte control);
+ // Upscales a pixel and puts it on display screen only
+ void putScaledPixelOnDisplay(int16 x, int16 y, byte color) {
+ int displayOffset = 0;
+
+ switch (_upscaledHires) {
+ case GFX_SCREEN_UPSCALED_640x400:
+ displayOffset = (y * 2) * _displayWidth + x * 2; // straight 1 pixel -> 2 mapping
+
+ _displayScreen[displayOffset] = color;
+ _displayScreen[displayOffset + 1] = color;
+ _displayScreen[displayOffset + _displayWidth] = color;
+ _displayScreen[displayOffset + _displayWidth + 1] = color;
+ break;
+
+ case GFX_SCREEN_UPSCALED_640x440: {
+ int16 startY = (y * 11) / 5;
+ int16 endY = ((y + 1) * 11) / 5;
+ displayOffset = (startY * _displayWidth) + x * 2;
+
+ for (int16 curY = startY; curY < endY; curY++) {
+ _displayScreen[displayOffset] = color;
+ _displayScreen[displayOffset + 1] = color;
+ displayOffset += _displayWidth;
+ }
+ break;
+ }
+ case GFX_SCREEN_UPSCALED_640x480: {
+ int16 startY = (y * 12) / 5;
+ int16 endY = ((y + 1) * 12) / 5;
+ displayOffset = (startY * _displayWidth) + x * 2;
+
+ for (int16 curY = startY; curY < endY; curY++) {
+ _displayScreen[displayOffset] = color;
+ _displayScreen[displayOffset + 1] = color;
+ displayOffset += _displayWidth;
+ }
+ break;
+ }
+ default:
+ break;
+ }
+ }
- byte (GfxScreen::*_getPixelPtr) (byte *screen, int16 x, int16 y);
- byte getPixelNormal (byte *screen, int16 x, int16 y);
- byte getPixelUpscaled (byte *screen, int16 x, int16 y);
+ /**
+ * This is used to put font pixels onto the screen - we adjust differently, so that we won't
+ * do triple pixel lines in any case on upscaled hires. That way the font will not get distorted
+ * Sierra SCI didn't do this
+ */
+ void putFontPixel(int16 startingY, int16 x, int16 y, byte color) {
+ int16 actualY = startingY + y;
+ if (_fontIsUpscaled) {
+ // Do not scale ourselves, but put it on the display directly
+ putPixelOnDisplay(x, actualY, color);
+ } else {
+ int offset = actualY * _width + x;
+
+ _visualScreen[offset] = color;
+ switch (_upscaledHires) {
+ case GFX_SCREEN_UPSCALED_DISABLED:
+ _displayScreen[offset] = color;
+ break;
+ case GFX_SCREEN_UPSCALED_640x400:
+ case GFX_SCREEN_UPSCALED_640x440:
+ case GFX_SCREEN_UPSCALED_640x480: {
+ // to 1-> 4 pixels upscaling for all of those, so that fonts won't look weird
+ int displayOffset = (_upscaledHeightMapping[startingY] + y * 2) * _displayWidth + x * 2;
+ _displayScreen[displayOffset] = color;
+ _displayScreen[displayOffset + 1] = color;
+ displayOffset += _displayWidth;
+ _displayScreen[displayOffset] = color;
+ _displayScreen[displayOffset + 1] = color;
+ break;
+ }
+ default:
+ putScaledPixelOnDisplay(x, actualY, color);
+ break;
+ }
+ }
+ }
- // pixel helper
- void putScaledPixelOnScreen(byte *screen, int16 x, int16 y, byte color);
+ byte getPixel(byte *screen, int16 x, int16 y) {
+ switch (_upscaledHires) {
+ case GFX_SCREEN_UPSCALED_480x300: {
+ int offset = ((y * 3) / 2) * _width + ((y * 3) / 2);
+
+ return screen[offset];
+ break;
+ }
+ default:
+ break;
+ }
+ return screen[y * _width + x];
+ }
+
+ byte getVisual(int16 x, int16 y) {
+ return getPixel(_visualScreen, x, y);
+ }
+ byte getPriority(int16 x, int16 y) {
+ return getPixel(_priorityScreen, x, y);
+ }
+ byte getControl(int16 x, int16 y) {
+ return getPixel(_controlScreen, x, y);
+ }
+
+ // Vector related public code - in here, so that it can be inlined
+ byte vectorGetPixel(byte *screen, int16 x, int16 y) {
+ return screen[y * _width + x];
+ }
+
+ byte vectorGetVisual(int16 x, int16 y) {
+ return vectorGetPixel(_visualScreen, x, y);
+ }
+ byte vectorGetPriority(int16 x, int16 y) {
+ return vectorGetPixel(_priorityScreen, x, y);
+ }
+ byte vectorGetControl(int16 x, int16 y) {
+ return vectorGetPixel(_controlScreen, x, y);
+ }
+
+ void vectorAdjustCoordinate(int16 *x, int16 *y) {
+ switch (_upscaledHires) {
+ case GFX_SCREEN_UPSCALED_480x300:
+ *x = (*x * 3) / 2;
+ *y = (*y * 3) / 2;
+ break;
+ default:
+ break;
+ }
+ }
};
} // End of namespace Sci
diff --git a/engines/sci/graphics/screen_item32.cpp b/engines/sci/graphics/screen_item32.cpp
new file mode 100644
index 0000000000..c3fdbb6845
--- /dev/null
+++ b/engines/sci/graphics/screen_item32.cpp
@@ -0,0 +1,647 @@
+/* ScummVM - Graphic Adventure Engine
+ *
+ * ScummVM is the legal property of its developers, whose names
+ * are too numerous to list here. Please refer to the COPYRIGHT
+ * file distributed with this source distribution.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ *
+ */
+
+#include "sci/console.h"
+#include "sci/resource.h"
+#include "sci/engine/kernel.h"
+#include "sci/engine/selector.h"
+#include "sci/engine/state.h"
+#include "sci/graphics/celobj32.h"
+#include "sci/graphics/frameout.h"
+#include "sci/graphics/screen_item32.h"
+#include "sci/graphics/view.h"
+
+namespace Sci {
+#pragma mark ScreenItem
+
+uint16 ScreenItem::_nextObjectId = 20000;
+
+ScreenItem::ScreenItem(const reg_t object) :
+_celObj(nullptr),
+_object(object),
+_pictureId(-1),
+_created(g_sci->_gfxFrameout->getScreenCount()),
+_updated(0),
+_deleted(0),
+_mirrorX(false) {
+ SegManager *segMan = g_sci->getEngineState()->_segMan;
+
+ setFromObject(segMan, object, true, true);
+ _plane = readSelector(segMan, object, SELECTOR(plane));
+}
+
+ScreenItem::ScreenItem(const reg_t plane, const CelInfo32 &celInfo) :
+_plane(plane),
+_useInsetRect(false),
+_z(0),
+_celInfo(celInfo),
+_celObj(nullptr),
+_fixPriority(false),
+_position(0, 0),
+_object(make_reg(0, _nextObjectId++)),
+_pictureId(-1),
+_created(g_sci->_gfxFrameout->getScreenCount()),
+_updated(0),
+_deleted(0),
+_mirrorX(false) {}
+
+ScreenItem::ScreenItem(const reg_t plane, const CelInfo32 &celInfo, const Common::Rect &rect) :
+_plane(plane),
+_useInsetRect(false),
+_z(0),
+_celInfo(celInfo),
+_celObj(nullptr),
+_fixPriority(false),
+_position(rect.left, rect.top),
+_object(make_reg(0, _nextObjectId++)),
+_pictureId(-1),
+_created(g_sci->_gfxFrameout->getScreenCount()),
+_updated(0),
+_deleted(0),
+_mirrorX(false) {
+ if (celInfo.type == kCelTypeColor) {
+ _insetRect = rect;
+ }
+}
+
+ScreenItem::ScreenItem(const reg_t plane, const CelInfo32 &celInfo, const Common::Point &position, const ScaleInfo &scaleInfo) :
+_plane(plane),
+_scale(scaleInfo),
+_useInsetRect(false),
+_z(0),
+_celInfo(celInfo),
+_celObj(nullptr),
+_fixPriority(false),
+_position(position),
+_object(make_reg(0, _nextObjectId++)),
+_pictureId(-1),
+_created(g_sci->_gfxFrameout->getScreenCount()),
+_updated(0),
+_deleted(0),
+_mirrorX(false) {}
+
+ScreenItem::ScreenItem(const ScreenItem &other) :
+_plane(other._plane),
+_scale(other._scale),
+_useInsetRect(other._useInsetRect),
+_celInfo(other._celInfo),
+_celObj(nullptr),
+_object(other._object),
+_mirrorX(other._mirrorX),
+_scaledPosition(other._scaledPosition),
+_screenRect(other._screenRect) {
+ if (other._useInsetRect) {
+ _insetRect = other._insetRect;
+ }
+}
+
+void ScreenItem::operator=(const ScreenItem &other) {
+ // NOTE: The original engine did not check for differences in `_celInfo`
+ // to clear `_celObj` here; instead, it unconditionally set `_celInfo`,
+ // didn't clear `_celObj`, and did hacky stuff in `kIsOnMe` to avoid
+ // testing a mismatched `_celObj`. See `GfxFrameout::kernelIsOnMe` for
+ // more detail.
+ if (_celInfo != other._celInfo) {
+ _celInfo = other._celInfo;
+ delete _celObj;
+ _celObj = nullptr;
+ }
+
+ _screenRect = other._screenRect;
+ _mirrorX = other._mirrorX;
+ _useInsetRect = other._useInsetRect;
+ if (other._useInsetRect) {
+ _insetRect = other._insetRect;
+ }
+ _scale = other._scale;
+ _scaledPosition = other._scaledPosition;
+}
+
+ScreenItem::~ScreenItem() {
+ delete _celObj;
+}
+
+void ScreenItem::init() {
+ _nextObjectId = 20000;
+}
+
+void ScreenItem::setFromObject(SegManager *segMan, const reg_t object, const bool updateCel, const bool updateBitmap) {
+ _position.x = readSelectorValue(segMan, object, SELECTOR(x));
+ _position.y = readSelectorValue(segMan, object, SELECTOR(y));
+ _scale.x = readSelectorValue(segMan, object, SELECTOR(scaleX));
+ _scale.y = readSelectorValue(segMan, object, SELECTOR(scaleY));
+ _scale.max = readSelectorValue(segMan, object, SELECTOR(maxScale));
+ _scale.signal = (ScaleSignals32)(readSelectorValue(segMan, object, SELECTOR(scaleSignal)) & 3);
+
+ if (updateCel) {
+ _celInfo.resourceId = (GuiResourceId)readSelectorValue(segMan, object, SELECTOR(view));
+ _celInfo.loopNo = readSelectorValue(segMan, object, SELECTOR(loop));
+ _celInfo.celNo = readSelectorValue(segMan, object, SELECTOR(cel));
+
+ if (_celInfo.resourceId <= kPlanePic) {
+ // TODO: Enhance GfxView or ResourceManager to allow
+ // metadata for resources to be retrieved once, from a
+ // single location
+ Resource *view = g_sci->getResMan()->findResource(ResourceId(kResourceTypeView, _celInfo.resourceId), false);
+ if (!view) {
+ error("Failed to load resource %d", _celInfo.resourceId);
+ }
+
+ // NOTE: +2 because the header size field itself is excluded from
+ // the header size in the data
+ const uint16 headerSize = READ_SCI11ENDIAN_UINT16(view->data) + 2;
+ const uint8 loopCount = view->data[2];
+ const uint8 loopSize = view->data[12];
+
+ if (_celInfo.loopNo >= loopCount) {
+ const int maxLoopNo = loopCount - 1;
+ _celInfo.loopNo = maxLoopNo;
+ writeSelectorValue(segMan, object, SELECTOR(loop), maxLoopNo);
+ }
+
+ byte *loopData = view->data + headerSize + (_celInfo.loopNo * loopSize);
+ const int8 seekEntry = loopData[0];
+ if (seekEntry != -1) {
+ loopData = view->data + headerSize + (seekEntry * loopSize);
+ }
+ const uint8 celCount = loopData[2];
+ if (_celInfo.celNo >= celCount) {
+ const int maxCelNo = celCount - 1;
+ _celInfo.celNo = maxCelNo;
+ writeSelectorValue(segMan, object, SELECTOR(cel), maxCelNo);
+ }
+ }
+ }
+
+ if (updateBitmap) {
+ const reg_t bitmap = readSelector(segMan, object, SELECTOR(bitmap));
+ if (!bitmap.isNull()) {
+ _celInfo.bitmap = bitmap;
+ _celInfo.type = kCelTypeMem;
+ } else {
+ _celInfo.bitmap = NULL_REG;
+ _celInfo.type = kCelTypeView;
+ }
+ }
+
+ if (updateCel || updateBitmap) {
+ delete _celObj;
+ _celObj = nullptr;
+ }
+
+ if (readSelectorValue(segMan, object, SELECTOR(fixPriority))) {
+ _fixPriority = true;
+ _priority = readSelectorValue(segMan, object, SELECTOR(priority));
+ } else {
+ _fixPriority = false;
+ writeSelectorValue(segMan, object, SELECTOR(priority), _position.y);
+ }
+
+ _z = readSelectorValue(segMan, object, SELECTOR(z));
+ _position.y -= _z;
+
+ if (readSelectorValue(segMan, object, SELECTOR(useInsetRect))) {
+ _useInsetRect = true;
+ _insetRect.left = readSelectorValue(segMan, object, SELECTOR(inLeft));
+ _insetRect.top = readSelectorValue(segMan, object, SELECTOR(inTop));
+ _insetRect.right = readSelectorValue(segMan, object, SELECTOR(inRight)) + 1;
+ _insetRect.bottom = readSelectorValue(segMan, object, SELECTOR(inBottom)) + 1;
+ } else {
+ _useInsetRect = false;
+ }
+
+ segMan->getObject(object)->clearInfoSelectorFlag(kInfoFlagViewVisible);
+}
+
+void ScreenItem::calcRects(const Plane &plane) {
+ const int16 scriptWidth = g_sci->_gfxFrameout->getCurrentBuffer().scriptWidth;
+ const int16 scriptHeight = g_sci->_gfxFrameout->getCurrentBuffer().scriptHeight;
+ const int16 screenWidth = g_sci->_gfxFrameout->getCurrentBuffer().screenWidth;
+ const int16 screenHeight = g_sci->_gfxFrameout->getCurrentBuffer().screenHeight;
+
+ const CelObj &celObj = getCelObj();
+
+ Common::Rect celRect(celObj._width, celObj._height);
+ if (_useInsetRect) {
+ if (_insetRect.intersects(celRect)) {
+ _insetRect.clip(celRect);
+ } else {
+ _insetRect = Common::Rect();
+ }
+ } else {
+ _insetRect = celRect;
+ }
+
+ Ratio scaleX, scaleY;
+
+ if (_scale.signal & kScaleSignalDoScaling32) {
+ if (_scale.signal & kScaleSignalUseVanishingPoint) {
+ int num = _scale.max * (_position.y - plane._vanishingPoint.y) / (scriptWidth - plane._vanishingPoint.y);
+ scaleX = Ratio(num, 128);
+ scaleY = Ratio(num, 128);
+ } else {
+ scaleX = Ratio(_scale.x, 128);
+ scaleY = Ratio(_scale.y, 128);
+ }
+ }
+
+ if (scaleX.getNumerator() && scaleY.getNumerator()) {
+ _screenItemRect = _insetRect;
+
+ const Ratio celToScreenX(screenWidth, celObj._scaledWidth);
+ const Ratio celToScreenY(screenHeight, celObj._scaledHeight);
+
+ // Cel may use a coordinate system that is not the same size as the
+ // script coordinate system (usually this means high-resolution
+ // pictures with low-resolution scripts)
+ if (celObj._scaledWidth != scriptWidth || celObj._scaledHeight != scriptHeight) {
+ if (_useInsetRect) {
+ const Ratio scriptToCelX(celObj._scaledWidth, scriptWidth);
+ const Ratio scriptToCelY(celObj._scaledHeight, scriptHeight);
+ mulru(_screenItemRect, scriptToCelX, scriptToCelY, 0);
+
+ if (_screenItemRect.intersects(celRect)) {
+ _screenItemRect.clip(celRect);
+ } else {
+ _screenItemRect = Common::Rect();
+ }
+ }
+
+ int displaceX = celObj._displace.x;
+ int displaceY = celObj._displace.y;
+
+ if (_mirrorX != celObj._mirrorX && _celInfo.type != kCelTypePic) {
+ displaceX = celObj._width - celObj._displace.x - 1;
+ }
+
+ if (!scaleX.isOne() || !scaleY.isOne()) {
+ mulinc(_screenItemRect, scaleX, scaleY);
+ displaceX = (displaceX * scaleX).toInt();
+ displaceY = (displaceY * scaleY).toInt();
+ }
+
+ mulinc(_screenItemRect, celToScreenX, celToScreenY);
+ displaceX = (displaceX * celToScreenX).toInt();
+ displaceY = (displaceY * celToScreenY).toInt();
+
+ const Ratio scriptToScreenX = Ratio(screenWidth, scriptWidth);
+ const Ratio scriptToScreenY = Ratio(screenHeight, scriptHeight);
+
+ if (/* TODO: dword_C6288 */ false && _celInfo.type == kCelTypePic) {
+ _scaledPosition.x = _position.x;
+ _scaledPosition.y = _position.y;
+ } else {
+ _scaledPosition.x = (_position.x * scriptToScreenX).toInt() - displaceX;
+ _scaledPosition.y = (_position.y * scriptToScreenY).toInt() - displaceY;
+ }
+
+ _screenItemRect.translate(_scaledPosition.x, _scaledPosition.y);
+
+ if (_mirrorX != celObj._mirrorX && _celInfo.type == kCelTypePic) {
+ Common::Rect temp(_insetRect);
+
+ if (!scaleX.isOne()) {
+ mulinc(temp, scaleX, Ratio());
+ }
+
+ mulinc(temp, celToScreenX, Ratio());
+
+ CelObjPic *celObjPic = dynamic_cast<CelObjPic *>(_celObj);
+ temp.translate((celObjPic->_relativePosition.x * scriptToScreenX).toInt() - displaceX, 0);
+
+ // TODO: This is weird.
+ int deltaX = plane._planeRect.width() - temp.right - 1 - temp.left;
+
+ _scaledPosition.x += deltaX;
+ _screenItemRect.translate(deltaX, 0);
+ }
+
+ _scaledPosition.x += plane._planeRect.left;
+ _scaledPosition.y += plane._planeRect.top;
+ _screenItemRect.translate(plane._planeRect.left, plane._planeRect.top);
+
+ _ratioX = scaleX * celToScreenX;
+ _ratioY = scaleY * celToScreenY;
+ } else {
+ int displaceX = celObj._displace.x;
+ if (_mirrorX != celObj._mirrorX && _celInfo.type != kCelTypePic) {
+ displaceX = celObj._width - celObj._displace.x - 1;
+ }
+
+ if (!scaleX.isOne() || !scaleY.isOne()) {
+ mulinc(_screenItemRect, scaleX, scaleY);
+ // TODO: This was in the original code, baked into the
+ // multiplication though it is not immediately clear
+ // why this is the only one that reduces the BR corner
+ _screenItemRect.right -= 1;
+ _screenItemRect.bottom -= 1;
+ }
+
+ _scaledPosition.x = _position.x - (displaceX * scaleX).toInt();
+ _scaledPosition.y = _position.y - (celObj._displace.y * scaleY).toInt();
+ _screenItemRect.translate(_scaledPosition.x, _scaledPosition.y);
+
+ if (_mirrorX != celObj._mirrorX && _celInfo.type == kCelTypePic) {
+ Common::Rect temp(_insetRect);
+
+ if (!scaleX.isOne()) {
+ mulinc(temp, scaleX, Ratio());
+ temp.right -= 1;
+ }
+
+ CelObjPic *celObjPic = dynamic_cast<CelObjPic *>(_celObj);
+ temp.translate(celObjPic->_relativePosition.x - (displaceX * scaleX).toInt(), celObjPic->_relativePosition.y - (celObj._displace.y * scaleY).toInt());
+
+ // TODO: This is weird.
+ int deltaX = plane._gameRect.width() - temp.right - 1 - temp.left;
+
+ _scaledPosition.x += deltaX;
+ _screenItemRect.translate(deltaX, 0);
+ }
+
+ _scaledPosition.x += plane._gameRect.left;
+ _scaledPosition.y += plane._gameRect.top;
+ _screenItemRect.translate(plane._gameRect.left, plane._gameRect.top);
+
+ if (celObj._scaledWidth != screenWidth || celObj._scaledHeight != screenHeight) {
+ mulru(_scaledPosition, celToScreenX, celToScreenY);
+ mulru(_screenItemRect, celToScreenX, celToScreenY, 1);
+ }
+
+ _ratioX = scaleX * celToScreenX;
+ _ratioY = scaleY * celToScreenY;
+ }
+
+ _screenRect = _screenItemRect;
+
+ if (_screenRect.intersects(plane._screenRect)) {
+ _screenRect.clip(plane._screenRect);
+ } else {
+ _screenRect.right = 0;
+ _screenRect.bottom = 0;
+ _screenRect.left = 0;
+ _screenRect.top = 0;
+ }
+
+ if (!_fixPriority) {
+ _priority = _z + _position.y;
+ }
+ } else {
+ _screenRect.left = 0;
+ _screenRect.top = 0;
+ _screenRect.right = 0;
+ _screenRect.bottom = 0;
+ }
+}
+
+CelObj &ScreenItem::getCelObj() const {
+ if (_celObj == nullptr) {
+ switch (_celInfo.type) {
+ case kCelTypeView:
+ _celObj = new CelObjView(_celInfo.resourceId, _celInfo.loopNo, _celInfo.celNo);
+ break;
+ case kCelTypePic:
+ error("Internal error, pic screen item with no cel.");
+ break;
+ case kCelTypeMem:
+ _celObj = new CelObjMem(_celInfo.bitmap);
+ break;
+ case kCelTypeColor:
+ _celObj = new CelObjColor(_celInfo.color, _insetRect.width(), _insetRect.height());
+ break;
+ }
+ }
+
+ return *_celObj;
+}
+
+void ScreenItem::printDebugInfo(Console *con) const {
+ con->debugPrintf("%04x:%04x (%s), prio %d, x %d, y %d, z: %d, scaledX: %d, scaledY: %d flags: %d\n",
+ _object.getSegment(), _object.getOffset(),
+ g_sci->getEngineState()->_segMan->getObjectName(_object),
+ _priority,
+ _position.x,
+ _position.y,
+ _z,
+ _scaledPosition.x,
+ _scaledPosition.y,
+ _created | (_updated << 1) | (_deleted << 2)
+ );
+ con->debugPrintf(" screen rect (%d, %d, %d, %d)\n", PRINT_RECT(_screenRect));
+ if (_useInsetRect) {
+ con->debugPrintf(" inset rect: (%d, %d, %d, %d)\n", PRINT_RECT(_insetRect));
+ }
+
+ Common::String celType;
+ switch (_celInfo.type) {
+ case kCelTypePic:
+ celType = "pic";
+ break;
+ case kCelTypeView:
+ celType = "view";
+ break;
+ case kCelTypeColor:
+ celType = "color";
+ break;
+ case kCelTypeMem:
+ celType = "mem";
+ break;
+ }
+
+ con->debugPrintf(" type: %s, res %d, loop %d, cel %d, bitmap %04x:%04x, color: %d\n",
+ celType.c_str(),
+ _celInfo.resourceId,
+ _celInfo.loopNo,
+ _celInfo.celNo,
+ PRINT_REG(_celInfo.bitmap),
+ _celInfo.color
+ );
+ if (_celObj != nullptr) {
+ con->debugPrintf(" width %d, height %d, scaledWidth %d, scaledHeight %d\n",
+ _celObj->_width,
+ _celObj->_height,
+ _celObj->_scaledWidth,
+ _celObj->_scaledHeight
+ );
+ }
+}
+
+void ScreenItem::update(const reg_t object) {
+ SegManager *segMan = g_sci->getEngineState()->_segMan;
+
+ const GuiResourceId view = readSelectorValue(segMan, object, SELECTOR(view));
+ const int16 loopNo = readSelectorValue(segMan, object, SELECTOR(loop));
+ const int16 celNo = readSelectorValue(segMan, object, SELECTOR(cel));
+
+ const bool updateCel = (
+ _celInfo.resourceId != view ||
+ _celInfo.loopNo != loopNo ||
+ _celInfo.celNo != celNo
+ );
+
+ const bool updateBitmap = !readSelector(segMan, object, SELECTOR(bitmap)).isNull();
+
+ setFromObject(segMan, object, updateCel, updateBitmap);
+
+ if (!_created) {
+ _updated = g_sci->_gfxFrameout->getScreenCount();
+ }
+
+ _deleted = 0;
+}
+
+// TODO: This code is quite similar to calcRects, so try to deduplicate
+// if possible
+Common::Rect ScreenItem::getNowSeenRect(const Plane &plane) const {
+ CelObj &celObj = getCelObj();
+
+ Common::Rect celObjRect(celObj._width, celObj._height);
+ Common::Rect nsRect;
+
+ if (_useInsetRect) {
+ // TODO: This is weird. Checking to see if the inset rect is
+ // fully inside the bounds of the celObjRect, and then
+ // clipping to the celObjRect, is pretty useless.
+ if (_insetRect.right > 0 && _insetRect.bottom > 0 && _insetRect.left < celObj._width && _insetRect.top < celObj._height) {
+ nsRect = _insetRect;
+ nsRect.clip(celObjRect);
+ } else {
+ nsRect = Common::Rect();
+ }
+ } else {
+ nsRect = celObjRect;
+ }
+
+ const uint16 scriptWidth = g_sci->_gfxFrameout->getCurrentBuffer().scriptWidth;
+ const uint16 scriptHeight = g_sci->_gfxFrameout->getCurrentBuffer().scriptHeight;
+
+ Ratio scaleX, scaleY;
+ if (_scale.signal & kScaleSignalDoScaling32) {
+ if (_scale.signal & kScaleSignalUseVanishingPoint) {
+ int num = _scale.max * (_position.y - plane._vanishingPoint.y) / (scriptWidth - plane._vanishingPoint.y);
+ scaleX = Ratio(num, 128);
+ scaleY = Ratio(num, 128);
+ } else {
+ scaleX = Ratio(_scale.x, 128);
+ scaleY = Ratio(_scale.y, 128);
+ }
+ }
+
+ if (scaleX.getNumerator() == 0 || scaleY.getNumerator() == 0) {
+ return Common::Rect();
+ }
+
+ int16 displaceX = celObj._displace.x;
+ int16 displaceY = celObj._displace.y;
+
+ if (_mirrorX != celObj._mirrorX && _celInfo.type != kCelTypePic) {
+ displaceX = celObj._width - displaceX - 1;
+ }
+
+ if (celObj._scaledWidth != scriptWidth || celObj._scaledHeight != scriptHeight) {
+ if (_useInsetRect) {
+ Ratio scriptToCelX(celObj._scaledWidth, scriptWidth);
+ Ratio scriptToCelY(celObj._scaledHeight, scriptHeight);
+ mulru(nsRect, scriptToCelX, scriptToCelY, 0);
+
+ // TODO: This is weird. Checking to see if the inset rect is
+ // fully inside the bounds of the celObjRect, and then
+ // clipping to the celObjRect, is pretty useless.
+ if (nsRect.right > 0 && nsRect.bottom > 0 && nsRect.left < celObj._width && nsRect.top < celObj._height) {
+ nsRect.clip(celObjRect);
+ } else {
+ nsRect = Common::Rect();
+ }
+ }
+
+ if (!scaleX.isOne() || !scaleY.isOne()) {
+ mulinc(nsRect, scaleX, scaleY);
+ // TODO: This was in the original code, baked into the
+ // multiplication though it is not immediately clear
+ // why this is the only one that reduces the BR corner
+ nsRect.right -= 1;
+ nsRect.bottom -= 1;
+ }
+
+ Ratio celToScriptX(scriptWidth, celObj._scaledWidth);
+ Ratio celToScriptY(scriptHeight, celObj._scaledHeight);
+
+ displaceX = (displaceX * scaleX * celToScriptX).toInt();
+ displaceY = (displaceY * scaleY * celToScriptY).toInt();
+
+ mulinc(nsRect, celToScriptX, celToScriptY);
+ nsRect.translate(_position.x - displaceX, _position.y - displaceY);
+ } else {
+ if (!scaleX.isOne() || !scaleY.isOne()) {
+ mulinc(nsRect, scaleX, scaleY);
+ // TODO: This was in the original code, baked into the
+ // multiplication though it is not immediately clear
+ // why this is the only one that reduces the BR corner
+ nsRect.right -= 1;
+ nsRect.bottom -= 1;
+ }
+
+ displaceX = (displaceX * scaleX).toInt();
+ displaceY = (displaceY * scaleY).toInt();
+ nsRect.translate(_position.x - displaceX, _position.y - displaceY);
+
+ if (_mirrorX != celObj._mirrorX && _celInfo.type != kCelTypePic) {
+ nsRect.translate(plane._gameRect.width() - nsRect.width(), 0);
+ }
+ }
+
+ return nsRect;
+}
+
+#pragma mark -
+#pragma mark ScreenItemList
+ScreenItem *ScreenItemList::findByObject(const reg_t object) const {
+ const_iterator screenItemIt = Common::find_if(begin(), end(), FindByObject<ScreenItem *>(object));
+
+ if (screenItemIt == end()) {
+ return nullptr;
+ }
+
+ return *screenItemIt;
+}
+void ScreenItemList::sort() {
+ // TODO: SCI engine used _unsorted as an array of indexes into the
+ // list itself and then performed the same swap operations on the
+ // _unsorted array as the _storage array during sorting, but the
+ // only reason to do this would be if some of the pointers in the
+ // list were replaced so the pointer values themselves couldn’t
+ // simply be recorded and then restored later. It is not yet
+ // verified whether this simplification of the sort/unsort is
+ // safe.
+ for (size_type i = 0; i < size(); ++i) {
+ _unsorted[i] = (*this)[i];
+ }
+
+ Common::sort(begin(), end(), sortHelper);
+}
+void ScreenItemList::unsort() {
+ for (size_type i = 0; i < size(); ++i) {
+ (*this)[i] = _unsorted[i];
+ }
+}
+
+} // End of namespace Sci
diff --git a/engines/sci/graphics/screen_item32.h b/engines/sci/graphics/screen_item32.h
new file mode 100644
index 0000000000..977d80ebad
--- /dev/null
+++ b/engines/sci/graphics/screen_item32.h
@@ -0,0 +1,288 @@
+/* ScummVM - Graphic Adventure Engine
+ *
+ * ScummVM is the legal property of its developers, whose names
+ * are too numerous to list here. Please refer to the COPYRIGHT
+ * file distributed with this source distribution.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ *
+ */
+
+#ifndef SCI_GRAPHICS_SCREEN_ITEM32_H
+#define SCI_GRAPHICS_SCREEN_ITEM32_H
+
+#include "common/rect.h"
+#include "sci/graphics/celobj32.h"
+#include "sci/graphics/lists32.h"
+
+namespace Sci {
+
+enum ScaleSignals32 {
+ kScaleSignalNone = 0,
+ kScaleSignalDoScaling32 = 1, // enables scaling when drawing that cel (involves scaleX and scaleY)
+ kScaleSignalUseVanishingPoint = 2,
+ // TODO: Is this actually a thing? I have not seen it and
+ // the original engine masks &3 where it uses scale signals.
+ kScaleSignalDisableGlobalScaling32 = 4
+};
+
+struct ScaleInfo {
+ int x, y, max;
+ ScaleSignals32 signal;
+ ScaleInfo() : x(128), y(128), max(100), signal(kScaleSignalNone) {}
+};
+
+class CelObj;
+class Plane;
+class SegManager;
+
+#pragma mark -
+#pragma mark ScreenItem
+
+/**
+ * A ScreenItem is the engine-side representation of a
+ * game script View.
+ */
+class ScreenItem {
+private:
+ /**
+ * A serial used for screen items that are generated
+ * inside the graphics engine, rather than the
+ * interpreter.
+ */
+ static uint16 _nextObjectId;
+
+ /**
+ * The parent plane of this screen item.
+ */
+ reg_t _plane;
+
+public:
+ /**
+ * Scaling data used to calculate the final screen
+ * dimensions of the screen item as well as the scaling
+ * ratios used when drawing the item to screen.
+ */
+ ScaleInfo _scale;
+
+private:
+ /**
+ * The position & dimensions of the screen item in
+ * screen coordinates. This rect includes the offset
+ * of the parent plane, but is not clipped to the
+ * screen, so may include coordinates that are
+ * offscreen.
+ */
+ Common::Rect _screenItemRect;
+
+ /**
+ * TODO: Document
+ */
+ bool _useInsetRect;
+
+ /**
+ * TODO: Documentation
+ * The insetRect is also used to describe the fill
+ * rectangle of a screen item that is drawn using
+ * CelObjColor.
+ */
+ Common::Rect _insetRect;
+
+ /**
+ * The z-index of the screen item in pseudo-3D space.
+ * Higher values are drawn on top of lower values.
+ */
+ int _z;
+
+ /**
+ * Sets the common properties of a screen item that must
+ * be set both during creation and update of a screen
+ * item.
+ */
+ void setFromObject(SegManager *segMan, const reg_t object, const bool updateCel, const bool updateBitmap);
+
+public:
+ /**
+ * A descriptor for the cel object represented by the
+ * screen item.
+ */
+ CelInfo32 _celInfo;
+
+ /**
+ * The cel object used to actually render the screen
+ * item. This member is populated by calling
+ * `getCelObj`.
+ */
+ mutable CelObj *_celObj;
+
+ /**
+ * If set, the priority for this screen item is fixed
+ * in place. Otherwise, the priority of the screen item
+ * is calculated from its y-position + z-index.
+ */
+ bool _fixPriority;
+
+ /**
+ * The rendering priority of the screen item, relative
+ * only to the other screen items within the same plane.
+ * Higher priorities are drawn above lower priorities.
+ */
+ int16 _priority;
+
+ /**
+ * The top-left corner of the screen item, in game
+ * script coordinates, relative to the parent plane.
+ */
+ Common::Point _position;
+
+ /**
+ * The associated View script object that was
+ * used to create the ScreenItem, or a numeric
+ * value in the case of a ScreenItem that was
+ * generated outside of the VM.
+ */
+ reg_t _object;
+
+ /**
+ * For screen items representing picture resources,
+ * the resource ID of the picture.
+ */
+ GuiResourceId _pictureId;
+
+ /**
+ * Flags indicating the state of the screen item.
+ * - `created` is set when the screen item is first
+ * created, either from a VM object or from within the
+ * engine itself
+ * - `updated` is set when `created` is not already set
+ * and the screen item is updated from a VM object
+ * - `deleted` is set by the parent plane, if the parent
+ * plane is a pic type and its picture resource ID has
+ * changed
+ */
+ int _created, _updated, _deleted; // ?
+
+ /**
+ * For screen items that represent picture cels, this
+ * value is set to match the `_mirrorX` property of the
+ * parent plane and indicates that the cel should be
+ * drawn horizontally mirrored. For final drawing, it is
+ * XORed with the `_mirrorX` property of the cel object.
+ * The cel object's `_mirrorX` property comes from the
+ * resource data itself.
+ */
+ bool _mirrorX;
+
+ /**
+ * The scaling ratios to use when drawing this screen
+ * item. These values are calculated according to the
+ * scale info whenever the screen item is updated.
+ */
+ Ratio _ratioX, _ratioY;
+
+ /**
+ * The top-left corner of the screen item, in screen
+ * coordinates.
+ */
+ Common::Point _scaledPosition;
+
+ /**
+ * The position & dimensions of the screen item in
+ * screen coordinates. This rect includes the offset of
+ * the parent plane and is clipped to the screen.
+ */
+ Common::Rect _screenRect;
+
+ /**
+ * Initialises static Plane members.
+ */
+ static void init();
+
+ ScreenItem(const reg_t screenItem);
+ ScreenItem(const reg_t plane, const CelInfo32 &celInfo);
+ ScreenItem(const reg_t plane, const CelInfo32 &celInfo, const Common::Rect &rect);
+ ScreenItem(const reg_t plane, const CelInfo32 &celInfo, const Common::Point &position, const ScaleInfo &scaleInfo);
+ ScreenItem(const ScreenItem &other);
+ ~ScreenItem();
+ void operator=(const ScreenItem &);
+
+ inline bool operator<(const ScreenItem &other) const {
+ if (_priority < other._priority) {
+ return true;
+ }
+
+ if (_priority == other._priority) {
+ if (_position.y + _z < other._position.y + other._z) {
+ return true;
+ }
+
+ if (_position.y + _z == other._position.y + other._z) {
+ return _object < other._object;
+ }
+ }
+
+ return false;
+ }
+
+ /**
+ * Calculates the dimensions and scaling parameters for
+ * the screen item, using the given plane as the parent
+ * plane for screen rect positioning.
+ *
+ * @note This method was called Update in SCI engine.
+ */
+ void calcRects(const Plane &plane);
+
+ /**
+ * Retrieves the corresponding cel object for this
+ * screen item. If a cel object does not already exist,
+ * one will be created and assigned.
+ */
+ CelObj &getCelObj() const;
+
+ void printDebugInfo(Console *con) const;
+
+ /**
+ * Updates the properties of the screen item from a
+ * VM object.
+ */
+ void update(const reg_t object);
+
+ /**
+ * Gets the "now seen" rect for the screen item, which
+ * represents the current size and position of the
+ * screen item on the screen in script coordinates.
+ */
+ Common::Rect getNowSeenRect(const Plane &plane) const;
+};
+
+#pragma mark -
+#pragma mark ScreenItemList
+
+typedef StablePointerArray<ScreenItem, 250> ScreenItemListBase;
+class ScreenItemList : public ScreenItemListBase {
+ static bool inline sortHelper(const ScreenItem *a, const ScreenItem *b) {
+ return *a < *b;
+ }
+public:
+ ScreenItem *_unsorted[250];
+
+ ScreenItem *findByObject(const reg_t object) const;
+ void sort();
+ void unsort();
+};
+} // End of namespace Sci
+
+#endif
diff --git a/engines/sci/graphics/text32.cpp b/engines/sci/graphics/text32.cpp
index 56ce73e8fa..99ffc6e328 100644
--- a/engines/sci/graphics/text32.cpp
+++ b/engines/sci/graphics/text32.cpp
@@ -29,363 +29,629 @@
#include "sci/engine/selector.h"
#include "sci/engine/state.h"
#include "sci/graphics/cache.h"
+#include "sci/graphics/celobj32.h"
#include "sci/graphics/compare.h"
#include "sci/graphics/font.h"
+#include "sci/graphics/frameout.h"
#include "sci/graphics/screen.h"
#include "sci/graphics/text32.h"
namespace Sci {
-#define BITMAP_HEADER_SIZE 46
+int16 GfxText32::_defaultFontId = 0;
+
+GfxText32::GfxText32(SegManager *segMan, GfxCache *fonts) :
+ _segMan(segMan),
+ _cache(fonts),
+ _scaledWidth(g_sci->_gfxFrameout->getCurrentBuffer().scriptWidth),
+ _scaledHeight(g_sci->_gfxFrameout->getCurrentBuffer().scriptHeight),
+ // Not a typo, the original engine did not initialise height, only width
+ _width(0),
+ _text(""),
+ _field_20(0),
+ _field_2C(2),
+ _field_30(0),
+ _field_34(0),
+ _field_38(0),
+ _field_3C(0),
+ _bitmap(NULL_REG) {
+ _fontId = _defaultFontId;
+ _font = _cache->getFont(_defaultFontId);
+ }
-#define SCI_TEXT32_ALIGNMENT_RIGHT -1
-#define SCI_TEXT32_ALIGNMENT_CENTER 1
-#define SCI_TEXT32_ALIGNMENT_LEFT 0
+reg_t GfxText32::createFontBitmap(int16 width, int16 height, const Common::Rect &rect, const Common::String &text, const uint8 foreColor, const uint8 backColor, const uint8 skipColor, const GuiResourceId fontId, const TextAlign alignment, const int16 borderColor, const bool dimmed, const bool doScaling) {
-GfxText32::GfxText32(SegManager *segMan, GfxCache *fonts, GfxScreen *screen)
- : _segMan(segMan), _cache(fonts), _screen(screen) {
-}
+ _field_22 = 0;
+ _borderColor = borderColor;
+ _text = text;
+ _textRect = rect;
+ _width = width;
+ _height = height;
+ _foreColor = foreColor;
+ _backColor = backColor;
+ _skipColor = skipColor;
+ _alignment = alignment;
+ _dimmed = dimmed;
-GfxText32::~GfxText32() {
-}
+ setFont(fontId);
-reg_t GfxText32::createScrollTextBitmap(Common::String text, reg_t textObject, uint16 maxWidth, uint16 maxHeight, reg_t prevHunk) {
- return createTextBitmapInternal(text, textObject, maxWidth, maxHeight, prevHunk);
+ if (doScaling) {
+ int16 scriptWidth = g_sci->_gfxFrameout->getCurrentBuffer().scriptWidth;
+ int16 scriptHeight = g_sci->_gfxFrameout->getCurrentBuffer().scriptHeight;
-}
-reg_t GfxText32::createTextBitmap(reg_t textObject, uint16 maxWidth, uint16 maxHeight, reg_t prevHunk) {
- reg_t stringObject = readSelector(_segMan, textObject, SELECTOR(text));
- // The object in the text selector of the item can be either a raw string
- // or a Str object. In the latter case, we need to access the object's data
- // selector to get the raw string.
- if (_segMan->isHeapObject(stringObject))
- stringObject = readSelector(_segMan, stringObject, SELECTOR(data));
+ Ratio scaleX(_scaledWidth, scriptWidth);
+ Ratio scaleY(_scaledHeight, scriptHeight);
- Common::String text = _segMan->getString(stringObject);
+ _width = (_width * scaleX).toInt();
+ _height = (_height * scaleY).toInt();
+ mulinc(_textRect, scaleX, scaleY);
+ }
- return createTextBitmapInternal(text, textObject, maxWidth, maxHeight, prevHunk);
-}
+ // _textRect represents where text is drawn inside the
+ // bitmap; clipRect is the entire bitmap
+ Common::Rect bitmapRect(_width, _height);
-reg_t GfxText32::createTextBitmapInternal(Common::String &text, reg_t textObject, uint16 maxWidth, uint16 maxHeight, reg_t prevHunk) {
- // HACK: The character offsets of the up and down arrow buttons are off by one
- // in GK1, for some unknown reason. Fix them here.
- if (text.size() == 1 && (text[0] == 29 || text[0] == 30)) {
- text.setChar(text[0] + 1, 0);
- }
- GuiResourceId fontId = readSelectorValue(_segMan, textObject, SELECTOR(font));
- GfxFont *font = _cache->getFont(fontId);
- bool dimmed = readSelectorValue(_segMan, textObject, SELECTOR(dimmed));
- int16 alignment = readSelectorValue(_segMan, textObject, SELECTOR(mode));
- uint16 foreColor = readSelectorValue(_segMan, textObject, SELECTOR(fore));
- uint16 backColor = readSelectorValue(_segMan, textObject, SELECTOR(back));
-
- Common::Rect nsRect = g_sci->_gfxCompare->getNSRect(textObject);
- uint16 width = nsRect.width() + 1;
- uint16 height = nsRect.height() + 1;
-
- // Limit rectangle dimensions, if requested
- if (maxWidth > 0)
- width = maxWidth;
- if (maxHeight > 0)
- height = maxHeight;
-
- // Upscale the coordinates/width if the fonts are already upscaled
- if (_screen->fontIsUpscaled()) {
- width = width * _screen->getDisplayWidth() / _screen->getWidth();
- height = height * _screen->getDisplayHeight() / _screen->getHeight();
+ if (_textRect.intersects(bitmapRect)) {
+ _textRect.clip(bitmapRect);
+ } else {
+ _textRect = Common::Rect();
}
- int entrySize = width * height + BITMAP_HEADER_SIZE;
- reg_t memoryId = NULL_REG;
- if (prevHunk.isNull()) {
- memoryId = _segMan->allocateHunkEntry("TextBitmap()", entrySize);
+ BitmapResource bitmap(_segMan, _width, _height, _skipColor, 0, 0, _scaledWidth, _scaledHeight, 0, false);
+ _bitmap = bitmap.getObject();
- // Scroll text objects have no bitmap selector!
- ObjVarRef varp;
- if (lookupSelector(_segMan, textObject, SELECTOR(bitmap), &varp, NULL) == kSelectorVariable)
- writeSelector(_segMan, textObject, SELECTOR(bitmap), memoryId);
- } else {
- memoryId = prevHunk;
+ erase(bitmapRect, false);
+
+ if (_borderColor > -1) {
+ drawFrame(bitmapRect, 1, _borderColor, false);
}
- byte *memoryPtr = _segMan->getHunkPointer(memoryId);
- if (prevHunk.isNull())
- memset(memoryPtr, 0, BITMAP_HEADER_SIZE);
+ drawTextBox();
+ return _bitmap;
+}
- byte *bitmap = memoryPtr + BITMAP_HEADER_SIZE;
- memset(bitmap, backColor, width * height);
+reg_t GfxText32::createFontBitmap(const CelInfo32 &celInfo, const Common::Rect &rect, const Common::String &text, const int16 foreColor, const int16 backColor, const GuiResourceId fontId, const int16 skipColor, const int16 borderColor, const bool dimmed) {
+ _field_22 = 0;
+ _borderColor = borderColor;
+ _text = text;
+ _textRect = rect;
+ _foreColor = foreColor;
+ _dimmed = dimmed;
- // Save totalWidth, totalHeight
- WRITE_LE_UINT16(memoryPtr, width);
- WRITE_LE_UINT16(memoryPtr + 2, height);
+ setFont(fontId);
- int16 charCount = 0;
- uint16 curX = 0, curY = 0;
- const char *txt = text.c_str();
- int16 textWidth, textHeight, totalHeight = 0, offsetX = 0, offsetY = 0;
- uint16 start = 0;
+ int16 scriptWidth = g_sci->_gfxFrameout->getCurrentBuffer().scriptWidth;
+ int16 scriptHeight = g_sci->_gfxFrameout->getCurrentBuffer().scriptHeight;
- // Calculate total text height
- while (*txt) {
- charCount = GetLongest(txt, width, font);
- if (charCount == 0)
- break;
+ int borderSize = 1;
+ mulinc(_textRect, Ratio(_scaledWidth, scriptWidth), Ratio(_scaledHeight, scriptHeight));
- Width(txt, 0, (int16)strlen(txt), fontId, textWidth, textHeight, true);
+ CelObjView view(celInfo.resourceId, celInfo.loopNo, celInfo.celNo);
+ _skipColor = view._transparentColor;
+ _width = view._width * _scaledWidth / view._scaledWidth;
+ _height = view._height * _scaledHeight / view._scaledHeight;
- totalHeight += textHeight;
- txt += charCount;
- while (*txt == ' ')
- txt++; // skip over breaking spaces
+ Common::Rect bitmapRect(_width, _height);
+ if (_textRect.intersects(bitmapRect)) {
+ _textRect.clip(bitmapRect);
+ } else {
+ _textRect = Common::Rect();
}
- txt = text.c_str();
-
- // Draw text in buffer
- while (*txt) {
- charCount = GetLongest(txt, width, font);
- if (charCount == 0)
- break;
- Width(txt, start, charCount, fontId, textWidth, textHeight, true);
-
- switch (alignment) {
- case SCI_TEXT32_ALIGNMENT_RIGHT:
- offsetX = width - textWidth;
- break;
- case SCI_TEXT32_ALIGNMENT_CENTER:
- // Center text both horizontally and vertically
- offsetX = (width - textWidth) / 2;
- offsetY = (height - totalHeight) / 2;
- break;
- case SCI_TEXT32_ALIGNMENT_LEFT:
- offsetX = 0;
- break;
-
- default:
- warning("Invalid alignment %d used in TextBox()", alignment);
- }
+ BitmapResource bitmap(_segMan, _width, _height, _skipColor, 0, 0, _scaledWidth, _scaledHeight, 0, false);
+ _bitmap = bitmap.getObject();
+ Buffer buffer(_width, _height, bitmap.getPixels());
+
+ // NOTE: The engine filled the bitmap pixels with 11 here, which is silly
+ // because then it just erased the bitmap using the skip color. So we don't
+ // fill the bitmap redundantly here.
- byte curChar;
-
- for (int i = 0; i < charCount; i++) {
- curChar = txt[i];
-
- switch (curChar) {
- case 0x0A:
- case 0x0D:
- case 0:
- break;
- case 0x7C:
- warning("Code processing isn't implemented in SCI32");
- break;
- default:
- font->drawToBuffer(curChar, curY + offsetY, curX + offsetX, foreColor, dimmed, bitmap, width, height);
- curX += font->getCharWidth(curChar);
- break;
+ _backColor = _skipColor;
+ erase(bitmapRect, false);
+ _backColor = backColor;
+
+ view.draw(buffer, bitmapRect, Common::Point(0, 0), false, Ratio(_scaledWidth, view._scaledWidth), Ratio(_scaledHeight, view._scaledHeight));
+
+ if (_backColor != skipColor && _foreColor != skipColor) {
+ erase(_textRect, false);
+ }
+
+ if (text.size() > 0) {
+ if (_foreColor == skipColor) {
+ error("TODO: Implement transparent text");
+ } else {
+ if (borderColor != -1) {
+ drawFrame(bitmapRect, borderSize, _borderColor, false);
}
- }
- curX = 0;
- curY += font->getHeight();
- txt += charCount;
- while (*txt == ' ')
- txt++; // skip over breaking spaces
+ drawTextBox();
+ }
}
- return memoryId;
+ return _bitmap;
}
-void GfxText32::disposeTextBitmap(reg_t hunkId) {
- _segMan->freeHunkEntry(hunkId);
+reg_t GfxText32::createTitledBitmap(const int16 width, const int16 height, const Common::Rect &textRect, const Common::String &text, const int16 foreColor, const int16 backColor, const int16 skipColor, const GuiResourceId fontId, const TextAlign alignment, const int16 borderColor, Common::String &title, const int16 titleForeColor, const int16 titleBackColor, const GuiResourceId titleFontId, const bool doScaling) {
+ warning("TODO: createTitledBitmap incomplete !");
+ return createFontBitmap(width, height, textRect, text, foreColor, backColor, skipColor, fontId, alignment, borderColor, false, doScaling);
}
-void GfxText32::drawTextBitmap(int16 x, int16 y, Common::Rect planeRect, reg_t textObject) {
- reg_t hunkId = readSelector(_segMan, textObject, SELECTOR(bitmap));
- drawTextBitmapInternal(x, y, planeRect, textObject, hunkId);
+void GfxText32::setFont(const GuiResourceId fontId) {
+ // NOTE: In SCI engine this calls FontMgr::BuildFontTable and then a font
+ // table is built on the FontMgr directly; instead, because we already have
+ // font resources, this code just grabs a font out of GfxCache.
+ if (fontId != _fontId) {
+ _fontId = fontId == -1 ? _defaultFontId : fontId;
+ _font = _cache->getFont(_fontId);
+ }
}
-void GfxText32::drawScrollTextBitmap(reg_t textObject, reg_t hunkId, uint16 x, uint16 y) {
- /*reg_t plane = readSelector(_segMan, textObject, SELECTOR(plane));
- Common::Rect planeRect;
- planeRect.top = readSelectorValue(_segMan, plane, SELECTOR(top));
- planeRect.left = readSelectorValue(_segMan, plane, SELECTOR(left));
- planeRect.bottom = readSelectorValue(_segMan, plane, SELECTOR(bottom));
- planeRect.right = readSelectorValue(_segMan, plane, SELECTOR(right));
+void GfxText32::drawFrame(const Common::Rect &rect, const int16 size, const uint8 color, const bool doScaling) {
+ Common::Rect targetRect = doScaling ? scaleRect(rect) : rect;
- drawTextBitmapInternal(x, y, planeRect, textObject, hunkId);*/
+ byte *bitmap = _segMan->getHunkPointer(_bitmap);
+ byte *pixels = bitmap + READ_SCI11ENDIAN_UINT32(bitmap + 28) + rect.top * _width + rect.left;
- // HACK: we pretty much ignore the plane rect and x, y...
- drawTextBitmapInternal(0, 0, Common::Rect(20, 390, 600, 460), textObject, hunkId);
+ // NOTE: Not fully disassembled, but this should be right
+ int16 rectWidth = targetRect.width();
+ int16 sidesHeight = targetRect.height() - size * 2;
+ int16 centerWidth = rectWidth - size * 2;
+ int16 stride = _width - rectWidth;
+
+ for (int16 y = 0; y < size; ++y) {
+ memset(pixels, color, rectWidth);
+ pixels += _width;
+ }
+ for (int16 y = 0; y < sidesHeight; ++y) {
+ for (int16 x = 0; x < size; ++x) {
+ *pixels++ = color;
+ }
+ pixels += centerWidth;
+ for (int16 x = 0; x < size; ++x) {
+ *pixels++ = color;
+ }
+ pixels += stride;
+ }
+ for (int16 y = 0; y < size; ++y) {
+ memset(pixels, color, rectWidth);
+ pixels += _width;
+ }
}
-void GfxText32::drawTextBitmapInternal(int16 x, int16 y, Common::Rect planeRect, reg_t textObject, reg_t hunkId) {
- int16 backColor = (int16)readSelectorValue(_segMan, textObject, SELECTOR(back));
- // Sanity check: Check if the hunk is set. If not, either the game scripts
- // didn't set it, or an old saved game has been loaded, where it wasn't set.
- if (hunkId.isNull())
- return;
+void GfxText32::drawChar(const char charIndex) {
+ byte *bitmap = _segMan->getHunkPointer(_bitmap);
+ byte *pixels = bitmap + READ_SCI11ENDIAN_UINT32(bitmap + 28);
- // Negative coordinates indicate that text shouldn't be displayed
- if (x < 0 || y < 0)
- return;
+ _font->drawToBuffer(charIndex, _drawPosition.y, _drawPosition.x, _foreColor, _dimmed, pixels, _width, _height);
+ _drawPosition.x += _font->getCharWidth(charIndex);
+}
- byte *memoryPtr = _segMan->getHunkPointer(hunkId);
+uint16 GfxText32::getCharWidth(const char charIndex, const bool doScaling) const {
+ uint16 width = _font->getCharWidth(charIndex);
+ if (doScaling) {
+ width = scaleUpWidth(width);
+ }
+ return width;
+}
- if (!memoryPtr) {
- // Happens when restoring in some SCI32 games (e.g. SQ6).
- // Commented out to reduce console spam
- //warning("Attempt to draw an invalid text bitmap");
+void GfxText32::drawTextBox() {
+ if (_text.size() == 0) {
return;
}
- byte *surface = memoryPtr + BITMAP_HEADER_SIZE;
+ const char *text = _text.c_str();
+ const char *sourceText = text;
+ int16 textRectWidth = _textRect.width();
+ _drawPosition.y = _textRect.top;
+ uint charIndex = 0;
+ if (getLongest(&charIndex, textRectWidth) == 0) {
+ error("DrawTextBox GetLongest=0");
+ }
+
+ charIndex = 0;
+ uint nextCharIndex = 0;
+ while (*text != '\0') {
+ _drawPosition.x = _textRect.left;
+
+ uint length = getLongest(&nextCharIndex, textRectWidth);
+ int16 textWidth = getTextWidth(charIndex, length);
+
+ if (_alignment == kTextAlignCenter) {
+ _drawPosition.x += (textRectWidth - textWidth) / 2;
+ } else if (_alignment == kTextAlignRight) {
+ _drawPosition.x += textRectWidth - textWidth;
+ }
+
+ drawText(charIndex, length);
+ charIndex = nextCharIndex;
+ text = sourceText + charIndex;
+ _drawPosition.y += _font->getHeight();
+ }
+}
+
+void GfxText32::drawTextBox(const Common::String &text) {
+ _text = text;
+ drawTextBox();
+}
+
+void GfxText32::drawText(const uint index, uint length) {
+ assert(index + length <= _text.size());
+
+ // NOTE: This draw loop implementation is somewhat different than the
+ // implementation in the actual engine, but should be accurate. Primarily
+ // the changes revolve around eliminating some extra temporaries and
+ // fixing the logic to match.
+ const char *text = _text.c_str() + index;
+ while (length-- > 0) {
+ char currentChar = *text++;
+
+ if (currentChar == '|') {
+ const char controlChar = *text++;
+ --length;
+
+ if (length == 0) {
+ return;
+ }
+
+ if (controlChar == 'a' || controlChar == 'c' || controlChar == 'f') {
+ uint16 value = 0;
+
+ while (length > 0) {
+ const char valueChar = *text;
+ if (valueChar < '0' || valueChar > '9') {
+ break;
+ }
+
+ ++text;
+ --length;
+ value = 10 * value + (valueChar - '0');
+ }
+
+ if (length == 0) {
+ return;
+ }
+
+ if (controlChar == 'a') {
+ _alignment = (TextAlign)value;
+ } else if (controlChar == 'c') {
+ _foreColor = value;
+ } else if (controlChar == 'f') {
+ setFont(value);
+ }
+ }
+
+ while (length > 0 && *text != '|') {
+ ++text;
+ --length;
+ }
+ } else {
+ drawChar(currentChar);
+ }
+ }
+}
+
+void GfxText32::invertRect(const reg_t bitmap, int16 bitmapStride, const Common::Rect &rect, const uint8 foreColor, const uint8 backColor, const bool doScaling) {
+ Common::Rect targetRect = rect;
+ if (doScaling) {
+ bitmapStride = bitmapStride * _scaledWidth / g_sci->_gfxFrameout->getCurrentBuffer().scriptWidth;
+ targetRect = scaleRect(rect);
+ }
+
+ byte *bitmapData = _segMan->getHunkPointer(bitmap);
- int curByte = 0;
- int16 skipColor = (int16)readSelectorValue(_segMan, textObject, SELECTOR(skip));
- uint16 textX = planeRect.left + x;
- uint16 textY = planeRect.top + y;
- // Get totalWidth, totalHeight
- uint16 width = READ_LE_UINT16(memoryPtr);
- uint16 height = READ_LE_UINT16(memoryPtr + 2);
+ // NOTE: SCI code is super weird here; it seems to be trying to look at the
+ // entire size of the bitmap including the header, instead of just the pixel
+ // data size. We just look at the pixel size. This function generally is an
+ // odd duck since the stride dimension for a bitmap is built in to the bitmap
+ // header, so perhaps it was once an unheadered bitmap format and this
+ // function was never updated to match? Or maybe they exploit the
+ // configurable stride length somewhere else to do stair stepping inverts...
+ uint32 invertSize = targetRect.height() * bitmapStride + targetRect.width();
+ uint32 bitmapSize = READ_SCI11ENDIAN_UINT32(bitmapData + 12);
- // Upscale the coordinates/width if the fonts are already upscaled
- if (_screen->fontIsUpscaled()) {
- textX = textX * _screen->getDisplayWidth() / _screen->getWidth();
- textY = textY * _screen->getDisplayHeight() / _screen->getHeight();
+ if (invertSize >= bitmapSize) {
+ error("InvertRect too big: %u >= %u", invertSize, bitmapSize);
}
- bool translucent = (skipColor == -1 && backColor == -1);
+ // NOTE: Actual engine just added the bitmap header size hardcoded here
+ byte *pixel = bitmapData + READ_SCI11ENDIAN_UINT32(bitmapData + 28) + bitmapStride * targetRect.top + targetRect.left;
- for (int curY = 0; curY < height; curY++) {
- for (int curX = 0; curX < width; curX++) {
- byte pixel = surface[curByte++];
- if ((!translucent && pixel != skipColor && pixel != backColor) ||
- (translucent && pixel != 0xFF))
- _screen->putFontPixel(textY, curX + textX, curY, pixel);
+ int16 stride = bitmapStride - targetRect.width();
+ int16 targetHeight = targetRect.height();
+ int16 targetWidth = targetRect.width();
+
+ for (int16 y = 0; y < targetHeight; ++y) {
+ for (int16 x = 0; x < targetWidth; ++x) {
+ if (*pixel == foreColor) {
+ *pixel = backColor;
+ } else if (*pixel == backColor) {
+ *pixel = foreColor;
+ }
+
+ ++pixel;
}
+
+ pixel += stride;
}
}
-int16 GfxText32::GetLongest(const char *text, int16 maxWidth, GfxFont *font) {
- uint16 curChar = 0;
- int16 maxChars = 0, curCharCount = 0;
- uint16 width = 0;
-
- while (width <= maxWidth) {
- curChar = (*(const byte *)text++);
-
- switch (curChar) {
- // We need to add 0xD, 0xA and 0xD 0xA to curCharCount and then exit
- // which means, we split text like
- // 'Mature, experienced software analyst available.' 0xD 0xA
- // 'Bug installation a proven speciality. "No version too clean."' (normal game text, this is from lsl2)
- // and 0xA '-------' 0xA (which is the official sierra subtitle separator)
- // Sierra did it the same way.
- case 0xD:
- // Check, if 0xA is following, if so include it as well
- if ((*(const unsigned char *)text) == 0xA)
- curCharCount++;
- // it's meant to pass through here
- case 0xA:
- curCharCount++;
- // and it's also meant to pass through here
- case 0:
- return curCharCount;
- case ' ':
- maxChars = curCharCount; // return count up to (but not including) breaking space
- break;
+uint GfxText32::getLongest(uint *charIndex, const int16 width) {
+ assert(width > 0);
+
+ uint testLength = 0;
+ uint length = 0;
+
+ const uint initialCharIndex = *charIndex;
+
+ // The index of the next word after the last word break
+ uint lastWordBreakIndex = *charIndex;
+
+ const char *text = _text.c_str() + *charIndex;
+
+ char currentChar;
+ while ((currentChar = *text++) != '\0') {
+ // NOTE: In the original engine, the font, color, and alignment were
+ // reset here to their initial values
+
+ // The text to render contains a line break; stop at the line break
+ if (currentChar == '\r' || currentChar == '\n') {
+ // Skip the rest of the line break if it is a Windows-style
+ // \r\n or non-standard \n\r
+ // NOTE: In the original engine, the `text` pointer had not been
+ // advanced yet so the indexes used to access characters were
+ // one higher
+ if (
+ (currentChar == '\r' && text[0] == '\n') ||
+ (currentChar == '\n' && text[0] == '\r' && text[1] != '\n')
+ ) {
+ ++*charIndex;
+ }
+
+ // We are at the end of a line but the last word in the line made
+ // it too wide to fit in the text area; return up to the previous
+ // word
+ if (length && getTextWidth(initialCharIndex, testLength) > width) {
+ *charIndex = lastWordBreakIndex;
+ return length;
+ }
+
+ // Skip the line break and return all text seen up to now
+ // NOTE: In original engine, the font, color, and alignment were
+ // reset, then getTextWidth was called to use its side-effects to
+ // set font, color, and alignment according to the text from
+ // `initialCharIndex` to `testLength`
+ ++*charIndex;
+ return testLength;
+ } else if (currentChar == ' ') {
+ // The last word in the line made it too wide to fit in the text area;
+ // return up to the previous word, then collapse the whitespace
+ // between that word and its next sibling word into the line break
+ if (getTextWidth(initialCharIndex, testLength) > width) {
+ *charIndex = lastWordBreakIndex;
+ const char *nextChar = _text.c_str() + lastWordBreakIndex;
+ while (*nextChar++ == ' ') {
+ ++*charIndex;
+ }
+
+ // NOTE: In original engine, the font, color, and alignment were
+ // set here to the values that were seen at the last space character
+ return length;
+ }
+
+ // NOTE: In the original engine, the values of _fontId, _foreColor,
+ // and _alignment were stored for use in the return path mentioned
+ // just above here
+
+ // We found a word break that was within the text area, memorise it
+ // and continue processing. +1 on the character index because it has
+ // not been incremented yet so currently points to the word break
+ // and not the word after the break
+ length = testLength;
+ lastWordBreakIndex = *charIndex + 1;
+ }
+
+ // In the middle of a line, keep processing
+ ++*charIndex;
+ ++testLength;
+
+ // NOTE: In the original engine, the font, color, and alignment were
+ // reset here to their initial values
+
+ // The text to render contained no word breaks yet but is already too
+ // wide for the text area; just split the word in half at the point
+ // where it overflows
+ if (length == 0 && getTextWidth(initialCharIndex, testLength) > width) {
+ *charIndex = --testLength + lastWordBreakIndex;
+ return testLength;
}
- if (width + font->getCharWidth(curChar) > maxWidth)
- break;
- width += font->getCharWidth(curChar);
- curCharCount++;
}
- return maxChars;
+ // The complete text to render was a single word, or was narrower than
+ // the text area, so return the entire line
+ if (length == 0 || getTextWidth(initialCharIndex, testLength) <= width) {
+ // NOTE: In original engine, the font, color, and alignment were
+ // reset, then getTextWidth was called to use its side-effects to
+ // set font, color, and alignment according to the text from
+ // `initialCharIndex` to `testLength`
+ return testLength;
+ }
+
+ // The last word in the line made it wider than the text area, so return
+ // up to the penultimate word
+ *charIndex = lastWordBreakIndex;
+ return length;
}
-void GfxText32::kernelTextSize(const char *text, int16 font, int16 maxWidth, int16 *textWidth, int16 *textHeight) {
- Common::Rect rect(0, 0, 0, 0);
- Size(rect, text, font, maxWidth);
- *textWidth = rect.width();
- *textHeight = rect.height();
+int16 GfxText32::getTextWidth(const uint index, uint length) const {
+ int16 width = 0;
+
+ const char *text = _text.c_str() + index;
+
+ GfxFont *font = _font;
+
+ char currentChar = *text++;
+ while (length > 0 && currentChar != '\0') {
+ // Control codes are in the format `|<code><value>|`
+ if (currentChar == '|') {
+ // NOTE: Original engine code changed the global state of the
+ // FontMgr here upon encountering any color, alignment, or
+ // font control code.
+ // To avoid requiring all callers to manually restore these
+ // values on every call, we ignore control codes other than
+ // font change (since alignment and color do not change the
+ // width of characters), and simply update the font pointer
+ // on stack instead of the member property font.
+ currentChar = *text++;
+ --length;
+
+ if (length > 0 && currentChar == 'f') {
+ GuiResourceId fontId = 0;
+ do {
+ currentChar = *text++;
+ --length;
+
+ fontId = fontId * 10 + currentChar - '0';
+ } while (length > 0 && currentChar >= '0' && currentChar <= '9');
+
+ if (length > 0) {
+ font = _cache->getFont(fontId);
+ }
+ }
+
+ // Forward through any more unknown control character data
+ while (length > 0 && currentChar != '|') {
+ ++text;
+ --length;
+ }
+ } else {
+ width += font->getCharWidth(currentChar);
+ }
+
+ currentChar = *text++;
+ --length;
+ }
+
+ return width;
}
-void GfxText32::StringWidth(const char *str, GuiResourceId fontId, int16 &textWidth, int16 &textHeight) {
- Width(str, 0, (int16)strlen(str), fontId, textWidth, textHeight, true);
+int16 GfxText32::getTextWidth(const Common::String &text, const uint index, const uint length) {
+ _text = text;
+ return scaleUpWidth(getTextWidth(index, length));
}
-void GfxText32::Width(const char *text, int16 from, int16 len, GuiResourceId fontId, int16 &textWidth, int16 &textHeight, bool restoreFont) {
- byte curChar;
- textWidth = 0; textHeight = 0;
-
- GfxFont *font = _cache->getFont(fontId);
-
- if (font) {
- text += from;
- while (len--) {
- curChar = (*(const byte *)text++);
- switch (curChar) {
- case 0x0A:
- case 0x0D:
- textHeight = MAX<int16> (textHeight, font->getHeight());
- break;
- case 0x7C:
- warning("Code processing isn't implemented in SCI32");
- break;
- default:
- textHeight = MAX<int16> (textHeight, font->getHeight());
- textWidth += font->getCharWidth(curChar);
- break;
+Common::Rect GfxText32::getTextSize(const Common::String &text, int16 maxWidth, bool doScaling) {
+ // NOTE: Like most of the text rendering code, this function was pretty
+ // weird in the original engine. The initial result rectangle was actually
+ // a 1x1 rectangle (0, 0, 0, 0), which was then "fixed" after the main
+ // text size loop finished running by subtracting 1 from the right and
+ // bottom edges. Like other functions in SCI32, this has been converted
+ // to use exclusive rects with inclusive rounding.
+
+ Common::Rect result;
+
+ int16 scriptWidth = g_sci->_gfxFrameout->getCurrentBuffer().scriptWidth;
+ int16 scriptHeight = g_sci->_gfxFrameout->getCurrentBuffer().scriptHeight;
+
+ maxWidth = maxWidth * _scaledWidth / scriptWidth;
+
+ _text = text;
+
+ if (maxWidth >= 0) {
+ if (maxWidth == 0) {
+ // TODO: This was hardcoded to 192, but guessing
+ // that it was originally 60% of the scriptWidth
+ // before the compiler took over.
+ // Verify this by looking at a game that uses a
+ // scriptWidth other than 320, like LSL7
+ maxWidth = _scaledWidth * (scriptWidth * 0.6) / scriptWidth;
+ }
+
+ result.right = maxWidth;
+
+ int16 textWidth = 0;
+ if (_text.size() > 0) {
+ const char *rawText = _text.c_str();
+ const char *sourceText = rawText;
+ uint charIndex = 0;
+ uint nextCharIndex = 0;
+ while (*rawText != '\0') {
+ uint length = getLongest(&nextCharIndex, result.width());
+ textWidth = MAX(textWidth, getTextWidth(charIndex, length));
+ charIndex = nextCharIndex;
+ rawText = sourceText + charIndex;
+ // TODO: Due to getLongest and getTextWidth not having side
+ // effects, it is possible that the currently loaded font's
+ // height is wrong for this line if it was changed inline
+ result.bottom += _font->getHeight();
}
}
+
+ if (textWidth < maxWidth) {
+ result.right = textWidth;
+ }
+ } else {
+ result.right = getTextWidth(0, 10000);
+ // NOTE: In the original engine code, the bottom was not decremented
+ // by 1, which means that the rect was actually a pixel taller than
+ // the height of the font. This was not the case in the other branch,
+ // which decremented the bottom by 1 at the end of the loop.
+ result.bottom = _font->getHeight() + 1;
}
+
+ if (doScaling) {
+ // NOTE: The original engine code also scaled top/left but these are
+ // always zero so there is no reason to do that.
+ result.right = ((result.right - 1) * scriptWidth + _scaledWidth - 1) / _scaledWidth + 1;
+ result.bottom = ((result.bottom - 1) * scriptHeight + _scaledHeight - 1) / _scaledHeight + 1;
+ }
+
+ return result;
}
-int16 GfxText32::Size(Common::Rect &rect, const char *text, GuiResourceId fontId, int16 maxWidth) {
- int16 charCount;
- int16 maxTextWidth = 0, textWidth;
- int16 totalHeight = 0, textHeight;
+void GfxText32::erase(const Common::Rect &rect, const bool doScaling) {
+ Common::Rect targetRect = doScaling ? scaleRect(rect) : rect;
- // Adjust maxWidth if we're using an upscaled font
- if (_screen->fontIsUpscaled())
- maxWidth = maxWidth * _screen->getDisplayWidth() / _screen->getWidth();
+ byte *bitmap = _segMan->getHunkPointer(_bitmap);
+ byte *pixels = bitmap + READ_SCI11ENDIAN_UINT32(bitmap + 28);
- rect.top = rect.left = 0;
- GfxFont *font = _cache->getFont(fontId);
+ // NOTE: There is an extra optimisation within the SCI code to
+ // do a single memset if the scaledRect is the same size as
+ // the bitmap, not implemented here.
+ Buffer buffer(_width, _height, pixels);
+ buffer.fillRect(targetRect, _backColor);
+}
- if (maxWidth < 0) { // force output as single line
- StringWidth(text, fontId, textWidth, textHeight);
- rect.bottom = textHeight;
- rect.right = textWidth;
- } else {
- // rect.right=found widest line with RTextWidth and GetLongest
- // rect.bottom=num. lines * GetPointSize
- rect.right = (maxWidth ? maxWidth : 192);
- const char *curPos = text;
- while (*curPos) {
- charCount = GetLongest(curPos, rect.right, font);
- if (charCount == 0)
- break;
- Width(curPos, 0, charCount, fontId, textWidth, textHeight, false);
- maxTextWidth = MAX(textWidth, maxTextWidth);
- totalHeight += textHeight;
- curPos += charCount;
- while (*curPos == ' ')
- curPos++; // skip over breaking spaces
- }
- rect.bottom = totalHeight;
- rect.right = maxWidth ? maxWidth : MIN(rect.right, maxTextWidth);
+int16 GfxText32::getStringWidth(const Common::String &text) {
+ return getTextWidth(text, 0, 10000);
+}
+
+int16 GfxText32::getTextCount(const Common::String &text, const uint index, const Common::Rect &textRect, const bool doScaling) {
+ const int16 scriptWidth = g_sci->_gfxFrameout->getCurrentBuffer().scriptWidth;
+ const int16 scriptHeight = g_sci->_gfxFrameout->getCurrentBuffer().scriptHeight;
+
+ Common::Rect scaledRect(textRect);
+ if (doScaling) {
+ mulinc(scaledRect, Ratio(_scaledWidth, scriptWidth), Ratio(_scaledHeight, scriptHeight));
}
- // Adjust the width/height if we're using an upscaled font
- // for the scripts
- if (_screen->fontIsUpscaled()) {
- rect.right = rect.right * _screen->getWidth() / _screen->getDisplayWidth();
- rect.bottom = rect.bottom * _screen->getHeight() / _screen->getDisplayHeight();
+ Common::String oldText = _text;
+ _text = text;
+
+ uint charIndex = index;
+ int16 maxWidth = scaledRect.width();
+ int16 lineCount = (scaledRect.height() - 2) / _font->getHeight();
+ while (lineCount--) {
+ getLongest(&charIndex, maxWidth);
}
- return rect.right;
+ _text = oldText;
+ return charIndex - index;
}
+int16 GfxText32::getTextCount(const Common::String &text, const uint index, const GuiResourceId fontId, const Common::Rect &textRect, const bool doScaling) {
+ setFont(fontId);
+ return getTextCount(text, index, textRect, doScaling);
+}
+
+
} // End of namespace Sci
diff --git a/engines/sci/graphics/text32.h b/engines/sci/graphics/text32.h
index 7ba7df50e4..5768ea0c59 100644
--- a/engines/sci/graphics/text32.h
+++ b/engines/sci/graphics/text32.h
@@ -23,34 +23,455 @@
#ifndef SCI_GRAPHICS_TEXT32_H
#define SCI_GRAPHICS_TEXT32_H
+#include "sci/graphics/celobj32.h"
+#include "sci/graphics/frameout.h"
+
namespace Sci {
+enum TextAlign {
+ kTextAlignLeft = 0,
+ kTextAlignCenter = 1,
+ kTextAlignRight = 2
+};
+
+enum BitmapFlags {
+ kBitmapRemap = 2
+};
+
+#define BITMAP_PROPERTY(size, property, offset)\
+inline uint##size get##property() const {\
+ return READ_SCI11ENDIAN_UINT##size(_bitmap + (offset));\
+}\
+inline void set##property(uint##size value) {\
+ WRITE_SCI11ENDIAN_UINT##size(_bitmap + (offset), (value));\
+}
+
/**
- * Text32 class, handles text calculation and displaying of text for SCI2, SCI21 and SCI3 games
+ * A convenience class for creating and modifying in-memory
+ * bitmaps.
*/
-class GfxText32 {
+class BitmapResource {
+ byte *_bitmap;
+ reg_t _object;
+
+ /**
+ * Gets the size of the bitmap header for the current
+ * engine version.
+ */
+ static inline uint16 getBitmapHeaderSize() {
+ // TODO: These values are accurate for each engine, but there may be no reason
+ // to not simply just always use size 40, since SCI2.1mid does not seem to
+ // actually store any data above byte 40, and SCI2 did not allow bitmaps with
+ // scaling resolutions other than the default (320x200). Perhaps SCI3 used
+ // the extra bytes, or there is some reason why they tried to align the header
+ // size with other headers like pic headers?
+// uint32 bitmapHeaderSize;
+// if (getSciVersion() >= SCI_VERSION_2_1_MIDDLE) {
+// bitmapHeaderSize = 46;
+// } else if (getSciVersion() == SCI_VERSION_2_1_EARLY) {
+// bitmapHeaderSize = 40;
+// } else {
+// bitmapHeaderSize = 36;
+// }
+// return bitmapHeaderSize;
+ return 46;
+ }
+
+ /**
+ * Gets the byte size of a bitmap with the given width
+ * and height.
+ */
+ static inline uint32 getBitmapSize(const uint16 width, const uint16 height) {
+ return width * height + getBitmapHeaderSize();
+ }
+
public:
- GfxText32(SegManager *segMan, GfxCache *fonts, GfxScreen *screen);
- ~GfxText32();
- reg_t createTextBitmap(reg_t textObject, uint16 maxWidth = 0, uint16 maxHeight = 0, reg_t prevHunk = NULL_REG);
- reg_t createScrollTextBitmap(Common::String text, reg_t textObject, uint16 maxWidth = 0, uint16 maxHeight = 0, reg_t prevHunk = NULL_REG);
- void drawTextBitmap(int16 x, int16 y, Common::Rect planeRect, reg_t textObject);
- void drawScrollTextBitmap(reg_t textObject, reg_t hunkId, uint16 x, uint16 y);
- void disposeTextBitmap(reg_t hunkId);
- int16 GetLongest(const char *text, int16 maxWidth, GfxFont *font);
+ /**
+ * Create a bitmap resource for an existing bitmap.
+ * Ownership of the bitmap is retained by the caller.
+ */
+ inline BitmapResource(reg_t bitmap) :
+ _bitmap(g_sci->getEngineState()->_segMan->getHunkPointer(bitmap)),
+ _object(bitmap) {
+ if (_bitmap == nullptr || getUncompressedDataOffset() != getBitmapHeaderSize()) {
+ error("Invalid Text bitmap %04x:%04x", PRINT_REG(bitmap));
+ }
+ }
- void kernelTextSize(const char *text, int16 font, int16 maxWidth, int16 *textWidth, int16 *textHeight);
+ /**
+ * Allocates and initialises a new bitmap in the given
+ * segment manager.
+ */
+ inline BitmapResource(SegManager *segMan, const int16 width, const int16 height, const uint8 skipColor, const int16 displaceX, const int16 displaceY, const int16 scaledWidth, const int16 scaledHeight, const uint32 hunkPaletteOffset, const bool remap) {
-private:
- reg_t createTextBitmapInternal(Common::String &text, reg_t textObject, uint16 maxWidth, uint16 maxHeight, reg_t hunkId);
- void drawTextBitmapInternal(int16 x, int16 y, Common::Rect planeRect, reg_t textObject, reg_t hunkId);
- int16 Size(Common::Rect &rect, const char *text, GuiResourceId fontId, int16 maxWidth);
- void Width(const char *text, int16 from, int16 len, GuiResourceId orgFontId, int16 &textWidth, int16 &textHeight, bool restoreFont);
- void StringWidth(const char *str, GuiResourceId orgFontId, int16 &textWidth, int16 &textHeight);
+ _object = segMan->allocateHunkEntry("Bitmap()", getBitmapSize(width, height));
+ _bitmap = segMan->getHunkPointer(_object);
+
+ const uint16 bitmapHeaderSize = getBitmapHeaderSize();
+
+ setWidth(width);
+ setHeight(height);
+ setDisplace(Common::Point(displaceX, displaceY));
+ setSkipColor(skipColor);
+ _bitmap[9] = 0;
+ WRITE_SCI11ENDIAN_UINT16(_bitmap + 10, 0);
+ setRemap(remap);
+ setDataSize(width * height);
+ WRITE_SCI11ENDIAN_UINT32(_bitmap + 16, 0);
+ setHunkPaletteOffset(hunkPaletteOffset);
+ setDataOffset(bitmapHeaderSize);
+ setUncompressedDataOffset(bitmapHeaderSize);
+ setControlOffset(0);
+ setScaledWidth(scaledWidth);
+ setScaledHeight(scaledHeight);
+ }
+
+ reg_t getObject() const {
+ return _object;
+ }
+
+ BITMAP_PROPERTY(16, Width, 0);
+ BITMAP_PROPERTY(16, Height, 2);
+
+ inline Common::Point getDisplace() const {
+ return Common::Point(
+ (int16)READ_SCI11ENDIAN_UINT16(_bitmap + 4),
+ (int16)READ_SCI11ENDIAN_UINT16(_bitmap + 6)
+ );
+ }
+
+ inline void setDisplace(const Common::Point &displace) {
+ WRITE_SCI11ENDIAN_UINT16(_bitmap + 4, (uint16)displace.x);
+ WRITE_SCI11ENDIAN_UINT16(_bitmap + 6, (uint16)displace.y);
+ }
+
+ inline uint8 getSkipColor() const {
+ return _bitmap[8];
+ }
+
+ inline void setSkipColor(const uint8 skipColor) {
+ _bitmap[8] = skipColor;
+ }
+
+ inline bool getRemap() const {
+ return READ_SCI11ENDIAN_UINT16(_bitmap + 10) & kBitmapRemap;
+ }
+
+ inline void setRemap(const bool remap) {
+ uint16 flags = READ_SCI11ENDIAN_UINT16(_bitmap + 10);
+ if (remap) {
+ flags |= kBitmapRemap;
+ } else {
+ flags &= ~kBitmapRemap;
+ }
+ WRITE_SCI11ENDIAN_UINT16(_bitmap + 10, flags);
+ }
+ BITMAP_PROPERTY(32, DataSize, 12);
+
+ inline uint32 getHunkPaletteOffset() const {
+ return READ_SCI11ENDIAN_UINT32(_bitmap + 20);
+ }
+
+ void setHunkPaletteOffset(uint32 hunkPaletteOffset) {
+ if (hunkPaletteOffset) {
+ hunkPaletteOffset += getBitmapHeaderSize();
+ }
+
+ WRITE_SCI11ENDIAN_UINT32(_bitmap + 20, hunkPaletteOffset);
+ }
+
+ BITMAP_PROPERTY(32, DataOffset, 24);
+
+ // NOTE: This property is used as a "magic number" for
+ // validating that a block of memory is a valid bitmap,
+ // and so is always set to the size of the header.
+ BITMAP_PROPERTY(32, UncompressedDataOffset, 28);
+
+ // NOTE: This property always seems to be zero
+ BITMAP_PROPERTY(32, ControlOffset, 32);
+
+ inline uint16 getScaledWidth() const {
+ if (getDataOffset() >= 40) {
+ return READ_SCI11ENDIAN_UINT16(_bitmap + 36);
+ }
+
+ // SCI2 bitmaps did not have scaling ability
+ return 320;
+ }
+
+ inline void setScaledWidth(uint16 scaledWidth) {
+ if (getDataOffset() >= 40) {
+ WRITE_SCI11ENDIAN_UINT16(_bitmap + 36, scaledWidth);
+ }
+ }
+
+ inline uint16 getScaledHeight() const {
+ if (getDataOffset() >= 40) {
+ return READ_SCI11ENDIAN_UINT16(_bitmap + 38);
+ }
+
+ // SCI2 bitmaps did not have scaling ability
+ return 200;
+ }
+
+ inline void setScaledHeight(uint16 scaledHeight) {
+ if (getDataOffset() >= 40) {
+ WRITE_SCI11ENDIAN_UINT16(_bitmap + 38, scaledHeight);
+ }
+ }
+
+ inline byte *getPixels() {
+ return _bitmap + getUncompressedDataOffset();
+ }
+};
+
+class GfxFont;
+
+/**
+ * This class handles text calculation and rendering for
+ * SCI32 games. The text calculation system in SCI32 is
+ * nearly the same as SCI16, which means this class behaves
+ * similarly. Notably, GfxText32 maintains drawing
+ * parameters across multiple calls.
+ */
+class GfxText32 {
+private:
SegManager *_segMan;
GfxCache *_cache;
- GfxScreen *_screen;
+
+ /**
+ * The resource ID of the default font used by the game.
+ *
+ * @todo Check all SCI32 games to learn what their
+ * default font is.
+ */
+ static int16 _defaultFontId;
+
+ /**
+ * The width and height of the currently active text
+ * bitmap, in text-system coordinates.
+ *
+ * @note These are unsigned in the actual engine.
+ */
+ int16 _width, _height;
+
+ /**
+ * The color used to draw text.
+ */
+ uint8 _foreColor;
+
+ /**
+ * The background color of the text box.
+ */
+ uint8 _backColor;
+
+ /**
+ * The transparent color of the text box. Used when
+ * compositing the bitmap onto the screen.
+ */
+ uint8 _skipColor;
+
+ /**
+ * The rect where the text is drawn within the bitmap.
+ * This rect is clipped to the dimensions of the bitmap.
+ */
+ Common::Rect _textRect;
+
+ /**
+ * The text being drawn to the currently active text
+ * bitmap.
+ */
+ Common::String _text;
+
+ /**
+ * The font being used to draw the text.
+ */
+ GuiResourceId _fontId;
+
+ /**
+ * The color of the text box border.
+ */
+ int16 _borderColor;
+
+ /**
+ * TODO: Document
+ */
+ bool _dimmed;
+
+ /**
+ * The text alignment for the drawn text.
+ */
+ TextAlign _alignment;
+
+ int16 _field_20;
+
+ /**
+ * TODO: Document
+ */
+ int16 _field_22;
+
+ int _field_2C, _field_30, _field_34, _field_38;
+
+ int16 _field_3C;
+
+ /**
+ * The position of the text draw cursor.
+ */
+ Common::Point _drawPosition;
+
+ void drawFrame(const Common::Rect &rect, const int16 size, const uint8 color, const bool doScaling);
+
+ void drawChar(const char charIndex);
+ void drawText(const uint index, uint length);
+
+ /**
+ * Gets the length of the longest run of text available
+ * within the currently loaded text, starting from the
+ * given `charIndex` and running for up to `maxWidth`
+ * pixels. Returns the number of characters that can be
+ * written, and mutates the value pointed to by
+ * `charIndex` to point to the index of the next
+ * character to render.
+ */
+ uint getLongest(uint *charIndex, const int16 maxWidth);
+
+ /**
+ * Gets the pixel width of a substring of the currently
+ * loaded text, without scaling.
+ */
+ int16 getTextWidth(const uint index, uint length) const;
+
+ inline Common::Rect scaleRect(const Common::Rect &rect) {
+ Common::Rect scaledRect(rect);
+ int16 scriptWidth = g_sci->_gfxFrameout->getCurrentBuffer().scriptWidth;
+ int16 scriptHeight = g_sci->_gfxFrameout->getCurrentBuffer().scriptHeight;
+ Ratio scaleX(_scaledWidth, scriptWidth);
+ Ratio scaleY(_scaledHeight, scriptHeight);
+ mulinc(scaledRect, scaleX, scaleY);
+ return scaledRect;
+ }
+
+public:
+ GfxText32(SegManager *segMan, GfxCache *fonts);
+
+ /**
+ * The memory handle of the currently active bitmap.
+ */
+ reg_t _bitmap;
+
+ /**
+ * The size of the x-dimension of the coordinate system
+ * used by the text renderer.
+ */
+ int16 _scaledWidth;
+
+ /**
+ * The size of the y-dimension of the coordinate system
+ * used by the text renderer.
+ */
+ int16 _scaledHeight;
+
+ /**
+ * The currently active font resource used to write text
+ * into the bitmap.
+ *
+ * @note SCI engine builds the font table directly
+ * inside of FontMgr; we use GfxFont instead.
+ */
+ GfxFont *_font;
+
+ /**
+ * Creates a plain font bitmap with a flat color
+ * background.
+ */
+ reg_t createFontBitmap(int16 width, int16 height, const Common::Rect &rect, const Common::String &text, const uint8 foreColor, const uint8 backColor, const uint8 skipColor, const GuiResourceId fontId, TextAlign alignment, const int16 borderColor, bool dimmed, const bool doScaling);
+
+ /**
+ * Creates a font bitmap with a view background.
+ */
+ reg_t createFontBitmap(const CelInfo32 &celInfo, const Common::Rect &rect, const Common::String &text, const int16 foreColor, const int16 backColor, const GuiResourceId fontId, const int16 skipColor, const int16 borderColor, const bool dimmed);
+
+ /**
+ * Creates a font bitmap with a title.
+ */
+ reg_t createTitledBitmap(const int16 width, const int16 height, const Common::Rect &textRect, const Common::String &text, const int16 foreColor, const int16 backColor, const int16 skipColor, const GuiResourceId fontId, const TextAlign alignment, const int16 borderColor, Common::String &title, const int16 titleForeColor, const int16 titleBackColor, const GuiResourceId titleFontId, const bool doScaling);
+
+ inline int scaleUpWidth(int value) const {
+ const int scriptWidth = g_sci->_gfxFrameout->getCurrentBuffer().scriptWidth;
+ return (value * scriptWidth + _scaledWidth - 1) / _scaledWidth;
+ }
+
+ inline int scaleUpHeight(int value) const {
+ const int scriptHeight = g_sci->_gfxFrameout->getCurrentBuffer().scriptHeight;
+ return (value * scriptHeight + _scaledHeight - 1) / _scaledHeight;
+ }
+
+ /**
+ * Draws the text to the bitmap.
+ */
+ void drawTextBox();
+
+ /**
+ * Draws the given text to the bitmap.
+ *
+ * @note The original engine holds a reference to a
+ * shared string which lets the text be updated from
+ * outside of the font manager. Instead, we give this
+ * extra signature to send the text to draw.
+ *
+ * TODO: Use shared string instead?
+ */
+ void drawTextBox(const Common::String &text);
+
+ /**
+ * Erases the given rect by filling with the background
+ * color.
+ */
+ void erase(const Common::Rect &rect, const bool doScaling);
+
+ void invertRect(const reg_t bitmap, const int16 bitmapStride, const Common::Rect &rect, const uint8 foreColor, const uint8 backColor, const bool doScaling);
+
+ /**
+ * Sets the font to be used for rendering and
+ * calculation of text dimensions.
+ */
+ void setFont(const GuiResourceId fontId);
+
+ /**
+ * Gets the width of a character.
+ */
+ uint16 getCharWidth(const char charIndex, const bool doScaling) const;
+
+ /**
+ * Retrieves the width and height of a block of text.
+ */
+ Common::Rect getTextSize(const Common::String &text, const int16 maxWidth, bool doScaling);
+
+ /**
+ * Gets the pixel width of a substring of the currently
+ * loaded text, with scaling.
+ */
+ int16 getTextWidth(const Common::String &text, const uint index, const uint length);
+
+ /**
+ * Retrieves the width of a line of text.
+ */
+ int16 getStringWidth(const Common::String &text);
+
+ /**
+ * Gets the number of characters of `text`, starting
+ * from `index`, that can be safely rendered into
+ * `textRect`.
+ */
+ int16 getTextCount(const Common::String &text, const uint index, const Common::Rect &textRect, const bool doScaling);
+
+ /**
+ * Gets the number of characters of `text`, starting
+ * from `index`, that can be safely rendered into
+ * `textRect` using the given font.
+ */
+ int16 getTextCount(const Common::String &text, const uint index, const GuiResourceId fontId, const Common::Rect &textRect, const bool doScaling);
};
} // End of namespace Sci
diff --git a/engines/sci/graphics/view.cpp b/engines/sci/graphics/view.cpp
index 2ee18b5c9a..1939e66179 100644
--- a/engines/sci/graphics/view.cpp
+++ b/engines/sci/graphics/view.cpp
@@ -25,6 +25,7 @@
#include "sci/engine/state.h"
#include "sci/graphics/screen.h"
#include "sci/graphics/palette.h"
+#include "sci/graphics/remap.h"
#include "sci/graphics/coordadjuster.h"
#include "sci/graphics/view.h"
@@ -833,19 +834,6 @@ void GfxView::draw(const Common::Rect &rect, const Common::Rect &clipRect, const
bitmap += (clipRect.top - rect.top) * celWidth + (clipRect.left - rect.left);
- // WORKAROUND: EcoQuest French and German draw the fish and anemone sprites
- // with priority 15 in scene 440. Afterwards, a dialog is shown on top of
- // these sprites with priority 15 as well. This is undefined behavior
- // actually, as the sprites and dialog share the same priority, so in our
- // implementation the sprites get drawn incorrectly on top of the dialog.
- // Perhaps this worked by mistake in SSCI because of subtle differences in
- // how sprites are drawn. We compensate for this by resetting the priority
- // of all sprites that have a priority of 15 in scene 440 to priority 14,
- // so that the speech bubble can be drawn correctly on top of them. Fixes
- // bug #3040625.
- if (g_sci->getGameId() == GID_ECOQUEST && g_sci->getEngineState()->currentRoomNumber() == 440 && priority == 15)
- priority = 14;
-
if (!_EGAmapping) {
for (y = 0; y < height; y++, bitmap += celWidth) {
for (x = 0; x < width; x++) {
@@ -855,12 +843,11 @@ void GfxView::draw(const Common::Rect &rect, const Common::Rect &clipRect, const
const int y2 = clipRectTranslated.top + y;
if (!upscaledHires) {
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);
- }
+ byte outputColor = palette->mapping[color];
+ // SCI16 remapping (QFG4 demo)
+ if (g_sci->_gfxRemap16 && g_sci->_gfxRemap16->isRemapped(outputColor))
+ outputColor = g_sci->_gfxRemap16->remapColor(outputColor, _screen->getVisual(x2, y2));
+ _screen->putPixel(x2, y2, drawMask, outputColor, priority, 0);
}
} else {
// UpscaledHires means view is hires and is supposed to
@@ -970,12 +957,11 @@ 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)) {
- 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);
- }
+ byte outputColor = palette->mapping[color];
+ // SCI16 remapping (QFG4 demo)
+ if (g_sci->_gfxRemap16 && g_sci->_gfxRemap16->isRemapped(outputColor))
+ outputColor = g_sci->_gfxRemap16->remapColor(outputColor, _screen->getVisual(x2, y2));
+ _screen->putPixel(x2, y2, drawMask, outputColor, priority, 0);
}
}
}
@@ -989,13 +975,4 @@ void GfxView::adjustBackUpscaledCoordinates(int16 &y, int16 &x) {
_screen->adjustBackUpscaledCoordinates(y, x, _sci2ScaleRes);
}
-byte GfxView::getColorAtCoordinate(int16 loopNo, int16 celNo, int16 x, int16 y) {
- const CelInfo *celInfo = getCelInfo(loopNo, celNo);
- const byte *bitmap = getBitmap(loopNo, celNo);
- const int16 celWidth = celInfo->width;
-
- bitmap += (celWidth * y);
- return bitmap[x];
-}
-
} // End of namespace Sci
diff --git a/engines/sci/graphics/view.h b/engines/sci/graphics/view.h
index d8803db208..91590208c1 100644
--- a/engines/sci/graphics/view.h
+++ b/engines/sci/graphics/view.h
@@ -85,8 +85,6 @@ public:
void adjustToUpscaledCoordinates(int16 &y, int16 &x);
void adjustBackUpscaledCoordinates(int16 &y, int16 &x);
- byte getColorAtCoordinate(int16 loopNo, int16 celNo, int16 x, int16 y);
-
private:
void initData(GuiResourceId resourceId);
void unpackCel(int16 loopNo, int16 celNo, byte *outPtr, uint32 pixelCount);
diff --git a/engines/sci/module.mk b/engines/sci/module.mk
index 08e5ea84d8..a02147e4d0 100644
--- a/engines/sci/module.mk
+++ b/engines/sci/module.mk
@@ -57,6 +57,7 @@ MODULE_OBJS := \
graphics/picture.o \
graphics/portrait.o \
graphics/ports.o \
+ graphics/remap.o \
graphics/screen.o \
graphics/text16.o \
graphics/transitions.o \
@@ -81,10 +82,13 @@ MODULE_OBJS := \
ifdef ENABLE_SCI32
MODULE_OBJS += \
engine/kgraphics32.o \
+ graphics/celobj32.o \
graphics/controls32.o \
graphics/frameout.o \
graphics/paint32.o \
+ graphics/plane32.o \
graphics/palette32.o \
+ graphics/screen_item32.o \
graphics/text32.o \
video/robot_decoder.o
endif
diff --git a/engines/sci/parser/vocabulary.cpp b/engines/sci/parser/vocabulary.cpp
index 828a57abeb..a09ba8f3ce 100644
--- a/engines/sci/parser/vocabulary.cpp
+++ b/engines/sci/parser/vocabulary.cpp
@@ -121,7 +121,7 @@ bool Vocabulary::loadParserWords() {
}
}
- unsigned int seeker;
+ uint32 seeker;
if (resourceType == kVocabularySCI1)
seeker = 255 * 2; // vocab.900 starts with 255 16-bit pointers which we don't use
else
@@ -202,7 +202,7 @@ bool Vocabulary::loadSuffixes() {
if (!resource)
return false; // No vocabulary found
- unsigned int seeker = 1;
+ uint32 seeker = 1;
while ((seeker < resource->size - 1) && (resource->data[seeker + 1] != 0xff)) {
suffix_t suffix;
@@ -288,7 +288,7 @@ bool Vocabulary::loadAltInputs() {
AltInput t;
t._input = data;
- unsigned int l = strlen(data);
+ uint32 l = strlen(data);
t._inputLength = l;
data += l + 1;
@@ -325,15 +325,15 @@ bool Vocabulary::checkAltInput(Common::String& text, uint16& cursorPos) {
return false;
bool ret = false;
- unsigned int loopCount = 0;
+ uint32 loopCount = 0;
bool changed;
do {
changed = false;
const char* t = text.c_str();
- unsigned int tlen = text.size();
+ uint32 tlen = text.size();
- for (unsigned int p = 0; p < tlen && !changed; ++p) {
+ for (uint32 p = 0; p < tlen && !changed; ++p) {
unsigned char s = t[p];
if (s >= _altInputs.size() || _altInputs[s].empty())
continue;
@@ -351,7 +351,7 @@ bool Vocabulary::checkAltInput(Common::String& text, uint16& cursorPos) {
cursorPos = p + strlen(i->_replacement);
}
- for (unsigned int j = 0; j < i->_inputLength; ++j)
+ for (uint32 j = 0; j < i->_inputLength; ++j)
text.deleteChar(p);
const char *r = i->_replacement;
while (*r)
diff --git a/engines/sci/parser/vocabulary.h b/engines/sci/parser/vocabulary.h
index f4adee6e55..59558ce18a 100644
--- a/engines/sci/parser/vocabulary.h
+++ b/engines/sci/parser/vocabulary.h
@@ -156,7 +156,7 @@ typedef Common::Array<synonym_t> SynonymList;
struct AltInput {
const char *_input;
const char *_replacement;
- unsigned int _inputLength;
+ uint32 _inputLength;
bool _prefix;
};
diff --git a/engines/sci/resource.cpp b/engines/sci/resource.cpp
index 54ef4b3363..6a5af1a6d6 100644
--- a/engines/sci/resource.cpp
+++ b/engines/sci/resource.cpp
@@ -307,7 +307,7 @@ bool Resource::loadPatch(Common::SeekableReadStream *file) {
error("Can't allocate %d bytes needed for loading %s", res->size + res->_headerSize, res->_id.toString().c_str());
}
- unsigned int really_read;
+ uint32 really_read;
if (res->_headerSize > 0) {
really_read = file->read(res->_header, res->_headerSize);
if (really_read != res->_headerSize)
@@ -565,12 +565,11 @@ Resource *ResourceManager::testResource(ResourceId id) {
}
int ResourceManager::addAppropriateSources() {
- Common::ArchiveMemberList files;
-
if (Common::File::exists("resource.map")) {
// SCI0-SCI2 file naming scheme
ResourceSource *map = addExternalMap("resource.map");
+ Common::ArchiveMemberList files;
SearchMan.listMatchingMembers(files, "resource.0??");
for (Common::ArchiveMemberList::const_iterator x = files.begin(); x != files.end(); ++x) {
@@ -587,20 +586,20 @@ int ResourceManager::addAppropriateSources() {
#endif
} else if (Common::MacResManager::exists("Data1")) {
// Mac SCI1.1+ file naming scheme
- SearchMan.listMatchingMembers(files, "Data?*");
+ Common::StringArray files;
+ Common::MacResManager::listFiles(files, "Data?");
- for (Common::ArchiveMemberList::const_iterator x = files.begin(); x != files.end(); ++x) {
- Common::String filename = (*x)->getName();
- addSource(new MacResourceForkResourceSource(filename, atoi(filename.c_str() + 4)));
+ for (Common::StringArray::const_iterator x = files.begin(); x != files.end(); ++x) {
+ addSource(new MacResourceForkResourceSource(*x, atoi(x->c_str() + 4)));
}
#ifdef ENABLE_SCI32
// There can also be a "Patches" resource fork with patches
- if (Common::File::exists("Patches"))
+ if (Common::MacResManager::exists("Patches"))
addSource(new MacResourceForkResourceSource("Patches", 100));
} else {
// SCI2.1-SCI3 file naming scheme
- Common::ArchiveMemberList mapFiles;
+ Common::ArchiveMemberList mapFiles, files;
SearchMan.listMatchingMembers(mapFiles, "resmap.0??");
SearchMan.listMatchingMembers(files, "ressci.0??");
@@ -865,6 +864,7 @@ ResourceManager::ResourceManager() {
}
void ResourceManager::init() {
+ _maxMemoryLRU = 256 * 1024; // 256KiB
_memoryLocked = 0;
_memoryLRU = 0;
_LRU.clear();
@@ -918,6 +918,14 @@ void ResourceManager::init() {
debugC(1, kDebugLevelResMan, "resMan: Detected %s", getSciVersionDesc(getSciVersion()));
+ // Resources in SCI32 games are significantly larger than SCI16
+ // games and can cause immediate exhaustion of the LRU resource
+ // cache, leading to constant decompression of picture resources
+ // and making the renderer very slow.
+ if (getSciVersion() >= SCI_VERSION_2) {
+ _maxMemoryLRU = 2048 * 1024; // 2MiB
+ }
+
switch (_viewType) {
case kViewEga:
debugC(1, kDebugLevelResMan, "resMan: Detected EGA graphic resources");
@@ -935,35 +943,14 @@ void ResourceManager::init() {
debugC(1, kDebugLevelResMan, "resMan: Detected SCI1.1 VGA graphic resources");
break;
default:
-#ifdef ENABLE_SCI32
- error("resMan: Couldn't determine view type");
-#else
- if (getSciVersion() >= SCI_VERSION_2) {
- // SCI support isn't built in, thus the view type won't be determined for
- // SCI2+ games. This will be handled further up, so throw no error here
- } else {
- error("resMan: Couldn't determine view type");
- }
-#endif
+ // Throw a warning, but do not error out here, because this is called from the
+ // fallback detector, and the user could be pointing to a folder with a non-SCI
+ // game, but with SCI-like file names (e.g. Pinball Creep)
+ warning("resMan: Couldn't determine view type");
+ break;
}
}
-void ResourceManager::initForDetection() {
- assert(!g_sci);
-
- _memoryLocked = 0;
- _memoryLRU = 0;
- _LRU.clear();
- _resMap.clear();
- _audioMapSCI1 = NULL;
-
- _mapVersion = detectMapVersion();
- _volVersion = detectVolVersion();
-
- scanNewSources();
- detectSciVersion();
-}
-
ResourceManager::~ResourceManager() {
// freeing resources
ResourceMap::iterator itr = _resMap.begin();
@@ -998,9 +985,9 @@ void ResourceManager::addToLRU(Resource *res) {
_LRU.push_front(res);
_memoryLRU += res->size;
#if SCI_VERBOSE_RESMAN
- debug("Adding %s.%03d (%d bytes) to lru control: %d bytes total",
- getResourceTypeName(res->type), res->number, res->size,
- mgr->_memoryLRU);
+ debug("Adding %s (%d bytes) to lru control: %d bytes total",
+ res->_id.toString().c_str(), res->size,
+ _memoryLRU);
#endif
res->_status = kResStatusEnqueued;
}
@@ -1023,13 +1010,13 @@ void ResourceManager::printLRU() {
}
void ResourceManager::freeOldResources() {
- while (MAX_MEMORY < _memoryLRU) {
+ while (_maxMemoryLRU < _memoryLRU) {
assert(!_LRU.empty());
Resource *goner = *_LRU.reverse_begin();
removeFromLRU(goner);
goner->unalloc();
#ifdef SCI_VERBOSE_RESMAN
- debug("resMan-debug: LRU: Freeing %s.%03d (%d bytes)", getResourceTypeName(goner->type), goner->number, goner->size);
+ debug("resMan-debug: LRU: Freeing %s (%d bytes)", goner->_id.toString().c_str(), goner->size);
#endif
}
}
@@ -2474,7 +2461,9 @@ bool ResourceManager::hasOldScriptHeader() {
Resource *res = findResource(ResourceId(kResourceTypeScript, 0), 0);
if (!res) {
- error("resMan: Failed to find script.000");
+ // Script 0 missing -> corrupted / non-SCI resource files.
+ // Don't error out here, because this might have been called
+ // from the fallback detector
return false;
}
@@ -2679,7 +2668,9 @@ Common::String ResourceManager::findSierraGameId() {
return "";
// Seek to the name selector of the first export
- byte *seeker = heap->data + READ_UINT16(heap->data + gameObjectOffset + nameSelector * 2);
+ byte *offsetPtr = heap->data + gameObjectOffset + nameSelector * 2;
+ uint16 offset = !isSci11Mac() ? READ_LE_UINT16(offsetPtr) : READ_BE_UINT16(offsetPtr);
+ byte *seeker = heap->data + offset;
Common::String sierraId;
sierraId += (const char *)seeker;
diff --git a/engines/sci/resource.h b/engines/sci/resource.h
index eb5b508254..ef474d97c2 100644
--- a/engines/sci/resource.h
+++ b/engines/sci/resource.h
@@ -315,11 +315,6 @@ public:
void init();
/**
- * Similar to the function above, only called from the fallback detector
- */
- void initForDetection();
-
- /**
* Adds all of the resource files for a game
*/
int addAppropriateSources();
@@ -426,9 +421,7 @@ protected:
// Note: maxMemory will not be interpreted as a hard limit, only as a restriction
// for resources which are not explicitly locked. However, a warning will be
// issued whenever this limit is exceeded.
- enum {
- MAX_MEMORY = 256 * 1024 // 256KB
- };
+ int _maxMemoryLRU;
ViewType _viewType; // Used to determine if the game has EGA or VGA graphics
Common::List<ResourceSource *> _sources;
diff --git a/engines/sci/resource_audio.cpp b/engines/sci/resource_audio.cpp
index 6869e6379e..5717a09121 100644
--- a/engines/sci/resource_audio.cpp
+++ b/engines/sci/resource_audio.cpp
@@ -139,7 +139,7 @@ bool Resource::loadFromAudioVolumeSCI1(Common::SeekableReadStream *file) {
error("Can't allocate %d bytes needed for loading %s", size, _id.toString().c_str());
}
- unsigned int really_read = file->read(data, size);
+ uint32 really_read = file->read(data, size);
if (really_read != size)
warning("Read %d bytes from %s but expected %d", really_read, _id.toString().c_str(), size);
@@ -688,6 +688,12 @@ SoundResource::SoundResource(uint32 resourceNr, ResourceManager *resMan, SciVers
channel->data = resource->data + dataOffset;
channel->size = READ_LE_UINT16(data + 4);
+
+ if (dataOffset + channel->size > resource->size) {
+ warning("Invalid size inside sound resource %d: track %d, channel %d", resourceNr, trackNr, channelNr);
+ channel->size = resource->size - dataOffset;
+ }
+
channel->curPos = 0;
channel->number = *channel->data;
diff --git a/engines/sci/sci.cpp b/engines/sci/sci.cpp
index 1232b6559b..e14d12b918 100644
--- a/engines/sci/sci.cpp
+++ b/engines/sci/sci.cpp
@@ -58,6 +58,7 @@
#include "sci/graphics/picture.h"
#include "sci/graphics/ports.h"
#include "sci/graphics/palette.h"
+#include "sci/graphics/remap.h"
#include "sci/graphics/screen.h"
#include "sci/graphics/text16.h"
#include "sci/graphics/transitions.h"
@@ -163,6 +164,7 @@ SciEngine::~SciEngine() {
delete _gfxText32;
delete _robotDecoder;
delete _gfxFrameout;
+ delete _gfxRemap32;
#endif
delete _gfxMenu;
delete _gfxControls16;
@@ -175,6 +177,7 @@ SciEngine::~SciEngine() {
delete _gfxPorts;
delete _gfxCache;
delete _gfxPalette16;
+ delete _gfxRemap16;
delete _gfxCursor;
delete _gfxScreen;
@@ -238,13 +241,7 @@ Common::Error SciEngine::run() {
// Only DOS+Windows
switch (_gameId) {
case GID_KQ6:
- if (isCD())
- _forceHiresGraphics = ConfMan.getBool("enable_high_resolution_graphics");
- break;
case GID_GK1:
- if ((isCD()) && (!isDemo()))
- _forceHiresGraphics = ConfMan.getBool("enable_high_resolution_graphics");
- break;
case GID_PQ4:
if (isCD())
_forceHiresGraphics = ConfMan.getBool("enable_high_resolution_graphics");
@@ -316,6 +313,7 @@ Common::Error SciEngine::run() {
if (directSaveSlotLoading >= 0) {
_gamestate->_delayedRestoreGame = true;
_gamestate->_delayedRestoreGameId = directSaveSlotLoading;
+ _gamestate->_delayedRestoreFromLauncher = true;
// Jones only initializes its menus when restarting/restoring, thus set
// the gameIsRestarting flag here before initializing. Fixes bug #6536.
@@ -529,7 +527,7 @@ void SciEngine::patchGameSaveRestore() {
byte kernelIdSave = 0;
switch (_gameId) {
- case GID_HOYLE1: // gets confused, although the game doesnt support saving/restoring at all
+ case GID_HOYLE1: // gets confused, although the game doesn't support saving/restoring at all
case GID_HOYLE2: // gets confused, see hoyle1
case GID_JONES: // gets confused, when we patch us in, the game is only able to save to 1 slot, so hooking is not required
case GID_MOTHERGOOSE: // mother goose EGA saves/restores directly and has no save/restore dialogs
@@ -576,17 +574,29 @@ void SciEngine::patchGameSaveRestore() {
}
}
+ const Object *patchObjectSave = nullptr;
+
+ if (getSciVersion() < SCI_VERSION_2) {
+ // Patch gameobject ::save for now for SCI0 - SCI1.1
+ // TODO: It seems this was never adjusted to superclass, but adjusting it now may cause
+ // issues with some game. Needs to get checked and then possibly changed.
+ patchObjectSave = gameObject;
+ } else {
+ // Patch superclass ::save for SCI32
+ patchObjectSave = gameSuperObject;
+ }
+
// Search for gameobject ::save, if there is one patch that one too
- uint16 gameObjectMethodCount = gameObject->getMethodCount();
- for (uint16 methodNr = 0; methodNr < gameObjectMethodCount; methodNr++) {
- uint16 selectorId = gameObject->getFuncSelector(methodNr);
+ uint16 patchObjectMethodCount = patchObjectSave->getMethodCount();
+ for (uint16 methodNr = 0; methodNr < patchObjectMethodCount; methodNr++) {
+ uint16 selectorId = patchObjectSave->getFuncSelector(methodNr);
Common::String methodName = _kernel->getSelectorName(selectorId);
if (methodName == "save") {
if (_gameId != GID_FAIRYTALES) { // Fairy Tales saves automatically without a dialog
if (kernelIdSave != kernelIdRestore)
- patchGameSaveRestoreCode(segMan, gameObject->getFunction(methodNr), kernelIdSave);
+ patchGameSaveRestoreCode(segMan, patchObjectSave->getFunction(methodNr), kernelIdSave);
else
- patchGameSaveRestoreCodeSci21(segMan, gameObject->getFunction(methodNr), kernelIdSave, false);
+ patchGameSaveRestoreCodeSci21(segMan, patchObjectSave->getFunction(methodNr), kernelIdSave, false);
}
break;
}
@@ -653,6 +663,7 @@ void SciEngine::initGraphics() {
_gfxPaint = 0;
_gfxPaint16 = 0;
_gfxPalette16 = 0;
+ _gfxRemap16 = 0;
_gfxPorts = 0;
_gfxText16 = 0;
_gfxTransitions = 0;
@@ -663,6 +674,7 @@ void SciEngine::initGraphics() {
_gfxFrameout = 0;
_gfxPaint32 = 0;
_gfxPalette32 = 0;
+ _gfxRemap32 = 0;
#endif
if (hasMacIconBar())
@@ -672,9 +684,12 @@ void SciEngine::initGraphics() {
if (getSciVersion() >= SCI_VERSION_2) {
_gfxPalette32 = new GfxPalette32(_resMan, _gfxScreen);
_gfxPalette16 = _gfxPalette32;
+ _gfxRemap32 = new GfxRemap32(_gfxPalette32);
} else {
#endif
_gfxPalette16 = new GfxPalette(_resMan, _gfxScreen);
+ if (getGameId() == GID_QFG4DEMO)
+ _gfxRemap16 = new GfxRemap(_gfxPalette16);
#ifdef ENABLE_SCI32
}
#endif
@@ -690,10 +705,11 @@ void SciEngine::initGraphics() {
_gfxCompare = new GfxCompare(_gamestate->_segMan, _gfxCache, _gfxScreen, _gfxCoordAdjuster);
_gfxPaint32 = new GfxPaint32(_resMan, _gfxCoordAdjuster, _gfxScreen, _gfxPalette32);
_gfxPaint = _gfxPaint32;
- _gfxText32 = new GfxText32(_gamestate->_segMan, _gfxCache, _gfxScreen);
- _gfxControls32 = new GfxControls32(_gamestate->_segMan, _gfxCache, _gfxText32);
_robotDecoder = new RobotDecoder(getPlatform() == Common::kPlatformMacintosh);
_gfxFrameout = new GfxFrameout(_gamestate->_segMan, _resMan, _gfxCoordAdjuster, _gfxCache, _gfxScreen, _gfxPalette32, _gfxPaint32);
+ _gfxText32 = new GfxText32(_gamestate->_segMan, _gfxCache);
+ _gfxControls32 = new GfxControls32(_gamestate->_segMan, _gfxCache, _gfxText32);
+ _gfxFrameout->run();
} else {
#endif
// SCI0-SCI1.1 graphic objects creation
@@ -704,7 +720,7 @@ void SciEngine::initGraphics() {
_gfxTransitions = new GfxTransitions(_gfxScreen, _gfxPalette16);
_gfxPaint16 = new GfxPaint16(_resMan, _gamestate->_segMan, _gfxCache, _gfxPorts, _gfxCoordAdjuster, _gfxScreen, _gfxPalette16, _gfxTransitions, _audio);
_gfxPaint = _gfxPaint16;
- _gfxAnimate = new GfxAnimate(_gamestate, _gfxCache, _gfxPorts, _gfxPaint16, _gfxScreen, _gfxPalette16, _gfxCursor, _gfxTransitions);
+ _gfxAnimate = new GfxAnimate(_gamestate, _scriptPatcher, _gfxCache, _gfxPorts, _gfxPaint16, _gfxScreen, _gfxPalette16, _gfxCursor, _gfxTransitions);
_gfxText16 = new GfxText16(_gfxCache, _gfxPorts, _gfxPaint16, _gfxScreen);
_gfxControls16 = new GfxControls16(_gamestate->_segMan, _gfxPorts, _gfxPaint16, _gfxText16, _gfxScreen);
_gfxMenu = new GfxMenu(_eventMan, _gamestate->_segMan, _gfxPorts, _gfxPaint16, _gfxText16, _gfxScreen, _gfxCursor);
@@ -820,7 +836,7 @@ Console *SciEngine::getSciDebugger() {
}
const char *SciEngine::getGameIdStr() const {
- return _gameDescription->gameid;
+ return _gameDescription->gameId;
}
Common::Language SciEngine::getLanguage() const {
@@ -897,12 +913,30 @@ int SciEngine::inQfGImportRoom() const {
void SciEngine::setLauncherLanguage() {
if (_gameDescription->flags & ADGF_ADDENGLISH) {
// If game is multilingual
- if (Common::parseLanguage(ConfMan.get("language")) == Common::EN_ANY) {
+ Common::Language chosenLanguage = Common::parseLanguage(ConfMan.get("language"));
+ uint16 languageToSet = 0;
+
+ switch (chosenLanguage) {
+ case Common::EN_ANY:
// and English was selected as language
- if (SELECTOR(printLang) != -1) // set text language to English
- writeSelectorValue(_gamestate->_segMan, _gameObjectAddress, SELECTOR(printLang), K_LANG_ENGLISH);
- if (SELECTOR(parseLang) != -1) // and set parser language to English as well
- writeSelectorValue(_gamestate->_segMan, _gameObjectAddress, SELECTOR(parseLang), K_LANG_ENGLISH);
+ languageToSet = K_LANG_ENGLISH;
+ break;
+ case Common::JA_JPN: {
+ // Set Japanese for FM-Towns games
+ // KQ5 on FM-Towns has no initial language set
+ if (g_sci->getPlatform() == Common::kPlatformFMTowns) {
+ languageToSet = K_LANG_JAPANESE;
+ }
+ }
+ default:
+ break;
+ }
+
+ if (languageToSet) {
+ if (SELECTOR(printLang) != -1) // set text language
+ writeSelectorValue(_gamestate->_segMan, _gameObjectAddress, SELECTOR(printLang), languageToSet);
+ if (SELECTOR(parseLang) != -1) // and set parser language as well
+ writeSelectorValue(_gamestate->_segMan, _gameObjectAddress, SELECTOR(parseLang), languageToSet);
}
}
}
diff --git a/engines/sci/sci.h b/engines/sci/sci.h
index 5c86d92355..7df3d38163 100644
--- a/engines/sci/sci.h
+++ b/engines/sci/sci.h
@@ -71,6 +71,8 @@ class GfxPaint16;
class GfxPaint32;
class GfxPalette;
class GfxPalette32;
+class GfxRemap;
+class GfxRemap32;
class GfxPorts;
class GfxScreen;
class GfxText16;
@@ -128,6 +130,7 @@ enum SciGameId {
GID_FAIRYTALES,
GID_FREDDYPHARKAS,
GID_FUNSEEKER,
+ GID_GK1DEMO, // We have a separate ID for GK1 demo, because it's actually a completely different game (SCI1.1 vs SCI2/SCI2.1)
GID_GK1,
GID_GK2,
GID_HOYLE1,
@@ -165,12 +168,14 @@ enum SciGameId {
GID_PQ2,
GID_PQ3,
GID_PQ4,
+ GID_PQ4DEMO, // We have a separate ID for PQ4 demo, because it's actually a completely different game (SCI1.1 vs SCI2/SCI2.1)
GID_PQSWAT,
GID_QFG1,
GID_QFG1VGA,
GID_QFG2,
GID_QFG3,
GID_QFG4,
+ GID_QFG4DEMO, // We have a separate ID for QFG4 demo, because it's actually a completely different game (SCI1.1 vs SCI2/SCI2.1)
GID_RAMA,
GID_SHIVERS,
//GID_SHIVERS2, // Not SCI
@@ -201,8 +206,8 @@ enum SciVersion {
SCI_VERSION_1_LATE, // Dr. Brain 1, EcoQuest 1, Longbow, PQ3, SQ1, LSL5, KQ5 CD
SCI_VERSION_1_1, // Dr. Brain 2, EcoQuest 1 CD, EcoQuest 2, KQ6, QFG3, SQ4CD, XMAS 1992 and many more
SCI_VERSION_2, // GK1, PQ4 floppy, QFG4 floppy
- SCI_VERSION_2_1_EARLY, // GK2 demo, KQ7, LSL6 hires, PQ4, QFG4 floppy
- SCI_VERSION_2_1_MIDDLE, // GK2, KQ7, MUMG Deluxe, Phantasmagoria 1, PQ4CD, PQ:SWAT, QFG4CD, Shivers 1, SQ6, Torin
+ SCI_VERSION_2_1_EARLY, // GK2 demo, KQ7 1.4/1.51, LSL6 hires, PQ4CD, QFG4 floppy
+ SCI_VERSION_2_1_MIDDLE, // GK2, KQ7 2.00b, MUMG Deluxe, Phantasmagoria 1, PQ:SWAT, QFG4CD, Shivers 1, SQ6, Torin
SCI_VERSION_2_1_LATE, // demos of LSL7, Lighthouse, RAMA
SCI_VERSION_3 // LSL7, Lighthouse, RAMA, Phantasmagoria 2
};
@@ -282,7 +287,7 @@ public:
inline EngineState *getEngineState() const { return _gamestate; }
inline Vocabulary *getVocabulary() const { return _vocabulary; }
inline EventManager *getEventManager() const { return _eventMan; }
- inline reg_t getGameObject() const { return _gameObjectAddress; }
+ inline reg_t getGameObject() const { return _gameObjectAddress; } // Gets the game object VM address
Common::RandomSource &getRNG() { return _rng; }
@@ -349,6 +354,8 @@ public:
GfxMenu *_gfxMenu; // Menu for 16-bit gfx
GfxPalette *_gfxPalette16;
GfxPalette32 *_gfxPalette32; // Palette for 32-bit gfx
+ GfxRemap *_gfxRemap16; // Remapping for the QFG4 demo
+ GfxRemap32 *_gfxRemap32; // Remapping for 32-bit gfx
GfxPaint *_gfxPaint;
GfxPaint16 *_gfxPaint16; // Painting in 16-bit gfx
GfxPaint32 *_gfxPaint32; // Painting in 32-bit gfx
diff --git a/engines/sci/sound/drivers/amigamac.cpp b/engines/sci/sound/drivers/amigamac.cpp
index 5ce49086ca..0f93b19e7c 100644
--- a/engines/sci/sound/drivers/amigamac.cpp
+++ b/engines/sci/sound/drivers/amigamac.cpp
@@ -497,7 +497,7 @@ MidiDriver_AmigaMac::InstrumentSample *MidiDriver_AmigaMac::readInstrumentSCI0(C
}
instrument->samples = (int8 *) malloc(size + 1);
- if (file.read(instrument->samples, size) < (unsigned int)size) {
+ if (file.read(instrument->samples, size) < (uint32)size) {
warning("Amiga/Mac driver: failed to read instrument samples");
free(instrument->samples);
delete instrument;
diff --git a/engines/sci/sound/soundcmd.cpp b/engines/sci/sound/soundcmd.cpp
index ee5903fda2..e7b25eb1fc 100644
--- a/engines/sci/sound/soundcmd.cpp
+++ b/engines/sci/sound/soundcmd.cpp
@@ -44,7 +44,7 @@ SoundCommandParser::SoundCommandParser(ResourceManager *resMan, SegManager *segM
// resource number, but it's totally unrelated to the menu music).
// The GK1 demo (very late SCI1.1) does the same thing
// TODO: Check the QFG4 demo
- _useDigitalSFX = (getSciVersion() >= SCI_VERSION_2 || g_sci->getGameId() == GID_GK1 || ConfMan.getBool("prefer_digitalsfx"));
+ _useDigitalSFX = (getSciVersion() >= SCI_VERSION_2 || g_sci->getGameId() == GID_GK1DEMO || ConfMan.getBool("prefer_digitalsfx"));
_music = new SciMusic(_soundVersion, _useDigitalSFX);
_music->init();
diff --git a/engines/sci/util.cpp b/engines/sci/util.cpp
index c72d3beb19..ccec41a1ab 100644
--- a/engines/sci/util.cpp
+++ b/engines/sci/util.cpp
@@ -69,4 +69,13 @@ void WRITE_SCI11ENDIAN_UINT16(void *ptr, uint16 val) {
WRITE_LE_UINT16(ptr, val);
}
+#ifdef ENABLE_SCI32
+void WRITE_SCI11ENDIAN_UINT32(void *ptr, uint32 val) {
+ if (g_sci->getPlatform() == Common::kPlatformMacintosh && getSciVersion() >= SCI_VERSION_1_1)
+ WRITE_BE_UINT32(ptr, val);
+ else
+ WRITE_LE_UINT32(ptr, val);
+}
+#endif
+
} // End of namespace Sci
diff --git a/engines/sci/util.h b/engines/sci/util.h
index 378030939c..b0fee5151e 100644
--- a/engines/sci/util.h
+++ b/engines/sci/util.h
@@ -37,6 +37,9 @@ void WRITE_SCIENDIAN_UINT16(void *ptr, uint16 val);
uint16 READ_SCI11ENDIAN_UINT16(const void *ptr);
uint32 READ_SCI11ENDIAN_UINT32(const void *ptr);
void WRITE_SCI11ENDIAN_UINT16(void *ptr, uint16 val);
+#ifdef ENABLE_SCI32
+void WRITE_SCI11ENDIAN_UINT32(void *ptr, uint32 val);
+#endif
// Wrappers for reading integer values in resources that are
// LE in SCI1.1 Mac, but BE in SCI32 Mac