diff options
Diffstat (limited to 'engines/sci')
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], ®, 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 |