diff options
| author | Le Philousophe | 2019-03-05 19:28:12 +0100 |
|---|---|---|
| committer | Eugene Sandulenko | 2019-06-01 22:43:48 +0200 |
| commit | 531aa8392e40458d52f22edef71abf3506ca63da (patch) | |
| tree | 5446a4eac74b59f3df4301cf4f0ed18f6ee5b586 /engines/cryomni3d/versailles | |
| parent | 97397bdaffed7f7bdf97ccde20bbcd41df4e63fb (diff) | |
| download | scummvm-rg350-531aa8392e40458d52f22edef71abf3506ca63da.tar.gz scummvm-rg350-531aa8392e40458d52f22edef71abf3506ca63da.tar.bz2 scummvm-rg350-531aa8392e40458d52f22edef71abf3506ca63da.zip | |
CRYOMNI3D: Add engine for Versailles 1685
Diffstat (limited to 'engines/cryomni3d/versailles')
| -rw-r--r-- | engines/cryomni3d/versailles/data.cpp | 1018 | ||||
| -rw-r--r-- | engines/cryomni3d/versailles/dialogs.cpp | 318 | ||||
| -rw-r--r-- | engines/cryomni3d/versailles/dialogs_manager.cpp | 379 | ||||
| -rw-r--r-- | engines/cryomni3d/versailles/dialogs_manager.h | 68 | ||||
| -rw-r--r-- | engines/cryomni3d/versailles/documentation.cpp | 2070 | ||||
| -rw-r--r-- | engines/cryomni3d/versailles/documentation.h | 143 | ||||
| -rw-r--r-- | engines/cryomni3d/versailles/engine.cpp | 1443 | ||||
| -rw-r--r-- | engines/cryomni3d/versailles/engine.h | 453 | ||||
| -rw-r--r-- | engines/cryomni3d/versailles/logic.cpp | 1036 | ||||
| -rw-r--r-- | engines/cryomni3d/versailles/menus.cpp | 1066 | ||||
| -rw-r--r-- | engines/cryomni3d/versailles/music.cpp | 281 | ||||
| -rw-r--r-- | engines/cryomni3d/versailles/saveload.cpp | 315 | ||||
| -rw-r--r-- | engines/cryomni3d/versailles/toolbar.cpp | 599 | ||||
| -rw-r--r-- | engines/cryomni3d/versailles/toolbar.h | 117 |
14 files changed, 9306 insertions, 0 deletions
diff --git a/engines/cryomni3d/versailles/data.cpp b/engines/cryomni3d/versailles/data.cpp new file mode 100644 index 0000000000..7439f4dae0 --- /dev/null +++ b/engines/cryomni3d/versailles/data.cpp @@ -0,0 +1,1018 @@ +/* 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 "cryomni3d/versailles/engine.h" + +namespace CryOmni3D { +namespace Versailles { + +const unsigned int CryOmni3DEngine_Versailles::kSpritesMapTable[] = { + /* 0 */ 242, 240, 243, 241, 256, 93, 97, 94, 160, 98, 178, 161, 179, 196, 197, 244, + /* 16 */ 142, 245, 143, 254, 95, 99, 113, 96, 100, 180, 114, 181, 73, 144, 74, 250, + /* 32 */ 202, 145, 170, 251, 203, 130, 206, 171, 49, 131, 207, 115, 116, 222, 75, 85, + /* 48 */ 76, 252, 204, 236, 86, 172, 253, 205, 237, 132, 81, 208, 173, 133, 82, 209, + /* 64 */ 24, 101, 25, 102, 87, 198, 88, 83, 258, 199, 84, 259, 257, 260, 26, 103, + /* 80 */ 28, 44, 27, 104, 29, 45, 200, 105, 201, 106, 162, 163, 32, 30, 46, 126, + /* 96 */ 33, 31, 47, 5, 127, 122, 219, 227, 123, 220, 107, 69, 108, 70, 164, 165, + /* 112 */ 89, 4, 90, 36, 34, 58, 128, 109, 37, 35, 255, 129, 110, 124, 125, 71, + /* 128 */ 40, 72, 41, 91, 92, 59, 228, 38, 7, 60, 111, 229, 39, 149, 121, 138, + /* 144 */ 112, 6, 139, 148, 42, 43, 232, 230, 233, 231, 140, 141, 134, 150, 135, 234, + /* 160 */ 151, 20, 226, 261, 235, 21, 262, 166, 246, 167, 136, 50, 247, 215, 152, 137, + /* 176 */ 51, 216, 153, 22, 117, 48, 23, 225, 118, 223, 182, 168, 248, 183, 169, 54, + /* 192 */ 52, 249, 217, 55, 53, 218, 8, 214, 119, 120, 186, 184, 154, 61, 187, 185, + /* 208 */ 155, 62, 56, 57, 188, 156, 65, 63, 210, 189, 157, 66, 64, 211, 19, 3, + /* 224 */ 80, 221, 1, 263, 78, 67, 174, 212, 68, 175, 213, 190, 191, 238, 0, 239, + /* 240 */ 224, 77, 146, 2, 147, 79, 158, 176, 159, 177, 194, 192, 195, 193, /*-1u, -1u*/ +}; +const unsigned int CryOmni3DEngine_Versailles::kSpritesMapTableSize = ARRAYSIZE(kSpritesMapTable); + +const LevelInitialState CryOmni3DEngine_Versailles::kLevelInitialStates[] = { + { 1, M_PI, 0. }, // Level 1 + { 9, M_PI, 0. }, // Level 2 + { 10, M_PI_2, 0. }, // Level 3 + { 10, 0., 0. }, // Level 4 + { 14, M_PI, 0. }, // Level 5 + { 8, 0., 0. }, // Level 6 + { 1, M_PI, 0. }, // Level 7 + { 4, M_PI, 0. } // Level 8 +}; + +const FakeTransitionActionPlace CryOmni3DEngine_Versailles::kFakeTransitions[] = { + {31141, 15}, + {31142, 16}, + {31143, 17}, + {32201, 18}, + {32202, 19}, + {32203, 20}, + {32204, 21}, + {35330, 36}, + {34172, 18}, + {0, 0} // Must be the last one +}; + +void CryOmni3DEngine_Versailles::setupMessages() { + _messages.resize(146); +#define SET_MESSAGE(id, str) _messages[id] = str + SET_MESSAGE(0, "Il est interdit d'ouvrir cette porte pour l'instant."); + SET_MESSAGE(1, "Cette porte est ferm" "\x8e" "e " "\x88" " clef."); + SET_MESSAGE(2, "Cette porte est ferm" "\x8e" "e."); + SET_MESSAGE(3, "Ce tiroir est vide."); + SET_MESSAGE(4, "Vous ne pouvez pas atteindre la b" "\x89" "che."); + SET_MESSAGE(5, "Il n'y a rien dans cet oranger"); + SET_MESSAGE(6, "Ceci n'est pas un oranger!"); + SET_MESSAGE(7, "Il fait trop sombre. "); + SET_MESSAGE(8, "Le coffre est ferm" "\x8e" ". "); + SET_MESSAGE(9, "Vous pouvez ouvrir la porte"); + SET_MESSAGE(10, "Il faudrait quelque chose pour atteindre la bombe."); + SET_MESSAGE(11, "Ce vase est vide."); + SET_MESSAGE(12, "Maintenant, vous pouvez y aller."); + SET_MESSAGE(13, "Vous niavez plus le temps de vous renseigner sur la Cour!"); + SET_MESSAGE(14, "Il est trop tard pour regarder les tableaux!"); + SET_MESSAGE(16, "Vous ne pouvez pas atteindre le papier."); + SET_MESSAGE(15, "Attendez ! Transmettez donc vos indices " "\x88" " l'huissier."); + SET_MESSAGE(17, "Vers l'apothicairerie"); + SET_MESSAGE( + 18, + "Attention : Vous allez pouvoir terminer ce niveau, mais vous n'avez pas effectu" "\x8e" + " toutes les actions necessaires pour la suite. " + "Il est conseill" "\x8e" " de SAUVEGARDER votre partie maintenant."); + SET_MESSAGE( + 19, + "Attention : Vous allez pouvoir terminer ce niveau, mais vous n'avez peut-" "\x89" "tre" + " pas effectu" "\x8e" " toutes les actions necessaires pour la suite. " + "Il est conseill" "\x8e" " de SAUVEGARDER votre partie maintenant."); + SET_MESSAGE(20, "Vous ne pouvez pas vous d" "\x8e" "placer en portant une " "\x8e" "chelle!"); + SET_MESSAGE(21, "Il n'y a plus rien ici"); + SET_MESSAGE(22, "Au revoir ..."); + SET_MESSAGE(23, "VERSAILLES,"); + SET_MESSAGE(24, "Complot " "\x88" " la Cour du Roi Soleil"); + SET_MESSAGE(27, " Commencer une nouvelle partie"); + SET_MESSAGE(26, " Reprendre la partie en cours"); + SET_MESSAGE(44, " Reprendre la visite en cours"); + SET_MESSAGE(28, " Charger une partie"); + SET_MESSAGE(46, " Charger une visite"); + SET_MESSAGE(29, " Sauver la partie"); + SET_MESSAGE(45, " Sauver la visite"); + SET_MESSAGE(25, "Consulter l'espace documentaire"); + SET_MESSAGE(42, "Visiter le ch" "\x89" "teau"); + SET_MESSAGE(48, " Omni3D : normal"); + SET_MESSAGE(51, " Omni3D : rapide"); + SET_MESSAGE(52, " Omni3D : tr" "\x8f" "s rapide"); + SET_MESSAGE(49, " Omni3D : lent"); + SET_MESSAGE(50, " Omni3D : tr" "\x8f" "s lent"); + SET_MESSAGE(30, " Afficher les sous-titres : OUI"); + SET_MESSAGE(31, " Afficher les sous-titres : NON"); + SET_MESSAGE(32, " Musique : OUI"); + SET_MESSAGE(33, " Musique : NON"); + SET_MESSAGE(35, " Toutes les musiques sur disque dur (92 Mo)"); + SET_MESSAGE(34, " Une seule musique sur disque dur (20 Mo)"); + SET_MESSAGE(36, " Aucune musique sur disque dur (lecture CD)"); + SET_MESSAGE(43, "Cr" "\x8e" "dits"); + SET_MESSAGE(39, "Volume"); + SET_MESSAGE(41, ""); + SET_MESSAGE(40, "Quitter le jeu"); + SET_MESSAGE(53, "Confirmer"); + SET_MESSAGE(54, "Annuler"); + SET_MESSAGE(55, "libre"); + SET_MESSAGE(56, "sans nom"); + SET_MESSAGE(57, "Attention : la partie en cours va " "\x89" "tre abandonn" "\x8e" "e."); + SET_MESSAGE(58, "Retour"); + SET_MESSAGE(59, "Le chateau"); + SET_MESSAGE(60, "Retour Menu Principal"); + SET_MESSAGE(61, "Sommaire Espace documentaire"); + SET_MESSAGE(62, "Plan du ch" "\x89" "teau et des jardins"); + SET_MESSAGE(63, "Plan des int" "\x8e" "rieurs du ch" "\x89" "teau"); + SET_MESSAGE(64, "Probl" "\x8f" "me d'" "\x8e" "criture sur dique dur : disque plein "); + SET_MESSAGE(66, "Veuillez ins" "\x8e" "rer le CD "); + SET_MESSAGE(67, "Veuillez ins" "\x8e" "rer le CD %d et presser une touche"); + SET_MESSAGE(68, "Les arts"); + SET_MESSAGE(69, "Le r" "\x8f" "gne"); + SET_MESSAGE(70, "La Cour"); + SET_MESSAGE(71, "Vie de Ch" "\x89" "teau"); + SET_MESSAGE(72, "Le ch" "\x89" "teau et les jardins"); + SET_MESSAGE(73, "Chronologie"); + SET_MESSAGE(74, "Bassin d'Apollon"); + SET_MESSAGE(75, "Le Ch" "\x89" "teau"); + SET_MESSAGE(76, "Colonnade"); + SET_MESSAGE(77, "Labyrinthe"); + SET_MESSAGE(78, "Latone"); + SET_MESSAGE(79, "Orangerie"); + SET_MESSAGE(80, "Parterre d'eau"); + SET_MESSAGE(81, "Tapis vert"); + SET_MESSAGE(86, "Grand Canal"); + SET_MESSAGE(87, "Parterre du Midi"); + SET_MESSAGE(88, "Parterre du nord"); + SET_MESSAGE(89, "Potager du Roi"); + SET_MESSAGE(90, "Salle de bal"); + SET_MESSAGE(91, "Bassin de Neptune"); + SET_MESSAGE(92, "Pi" "\x8f" "ce d'eau des suisses"); + SET_MESSAGE(82, "Grandes Ecuries"); + SET_MESSAGE(83, "Petites Ecuries"); + SET_MESSAGE(84, "Les jardins"); + SET_MESSAGE(85, "Avant cour"); + SET_MESSAGE(93, "Aiguilles (Inutile!)"); + SET_MESSAGE(94, "Ciseaux"); + SET_MESSAGE(95, "Papier"); + SET_MESSAGE(96, "Pamphlet sur les arts"); + SET_MESSAGE(97, "Petite clef 1"); + SET_MESSAGE(98, "Papier r" "\x8e" "v" "\x8e" "l" "\x8e" ""); + SET_MESSAGE(99, "Papier t" "\x89" "ch" "\x8e" ""); + SET_MESSAGE(100, "Papier du coffre"); + SET_MESSAGE(101, "Pamphlet sur la lign" "\x8e" "e royale"); + SET_MESSAGE(102, "Bougie allum" "\x8e" "e"); + SET_MESSAGE(103, "Bougie"); + SET_MESSAGE(104, "Clef "); + SET_MESSAGE(105, "Carton " "\x88" " dessin"); + SET_MESSAGE(106, "Carton " "\x88" " dessin"); + SET_MESSAGE(107, "Fausse esquisse"); + SET_MESSAGE(108, "Echelle"); + SET_MESSAGE(109, "Esquisse d" "\x8e" "truite"); + SET_MESSAGE(110, "pinceau"); + SET_MESSAGE(111, "pinceau Or"); + SET_MESSAGE(112, "pinceau Rouge"); + SET_MESSAGE(113, "Fusain"); + SET_MESSAGE(114, "Papier"); + SET_MESSAGE(115, "Pamphlet sur liarchitecture"); + SET_MESSAGE(116, "Petite clef 2"); + SET_MESSAGE(117, "Archer(inutile!)"); + SET_MESSAGE(118, "Partition"); + SET_MESSAGE(119, "Queue de billard"); + SET_MESSAGE(120, "Autorisation"); + SET_MESSAGE(121, "Reproduction des m" "\x8e" "dailles"); + SET_MESSAGE(122, "Tiroir " "\x88" " m" "\x8e" "dailles"); + SET_MESSAGE(123, "Clef de la petite porte diApollon"); + SET_MESSAGE(124, "Nourriture"); + SET_MESSAGE(125, "Pamphlet sur la religion"); + SET_MESSAGE(126, "Epigraphe"); + SET_MESSAGE(127, "Pamphlet sur le gouvernement"); + SET_MESSAGE(128, "Plume"); + SET_MESSAGE(129, "Pense-b" "\x89" "te"); + SET_MESSAGE(130, "Lunette"); + SET_MESSAGE(131, "Plan Vauban"); + SET_MESSAGE(132, "Plan Vauban"); + SET_MESSAGE(133, "Cordon"); + SET_MESSAGE(134, "Gravure"); + SET_MESSAGE(135, "Petite clef 3"); + SET_MESSAGE(136, "Petite clef 4"); + SET_MESSAGE(137, "M" "\x8e" "morandum"); + SET_MESSAGE(138, "Plans du chateau"); + SET_MESSAGE(139, "Plans du chateau"); + SET_MESSAGE(140, "Clef des combles"); + SET_MESSAGE(141, "Fables"); + SET_MESSAGE(142, "Plan du Labyrinthe"); + SET_MESSAGE(143, "Outil"); + SET_MESSAGE(144, "M" "\x8e" "dicament"); + SET_MESSAGE(145, "Eteignoir"); +#undef SET_MESSAGE +} + +void CryOmni3DEngine_Versailles::setupPaintingsTitles() { + _paintingsTitles.reserve(48); +#define SET_PAINTING_TITLE(str) _paintingsTitles.push_back(str) + SET_PAINTING_TITLE("\"Entr" "\x8e" "e des animaux dans l'arche\"\rGerolamo Bassano"); // 0: 41201 + SET_PAINTING_TITLE("\"Le repas d'Emma" "\x9f" "s\"\rJacopo Bassano"); // 1: 41202 + SET_PAINTING_TITLE("\"La Madeleine aux pieds de J\x8esus Christ\"\rSustris"); // 2: 41203 + SET_PAINTING_TITLE("\"La sortie de l'arche\"\rGerolamo Bassano"); // 3: 41204 + SET_PAINTING_TITLE("\"Le frappement du rocher\"\rJacopo Bassano"); // 4: 41205 + SET_PAINTING_TITLE("\"La Bataille d'Arbelles\"\rJoseph Parrocel"); // 5: 41301 + SET_PAINTING_TITLE("\"Alexandre Le Grand vainqueur de Darius " "\x88" + " la bataille d'Arbelles\"\rLe Bourguignon"); // 6: 41302 + SET_PAINTING_TITLE("\"Le Combat de Leuze\"\rJoseph Parrocel"); // 7: 42401 + SET_PAINTING_TITLE("\"Sainte C" "\x8e" + "cile avec un ange tenant une partition musicale\"\rDominiquin"); // 8: 42901 + SET_PAINTING_TITLE("\"Don Francisco du Moncada \"\rVan Dyck"); // 9: 42902 + SET_PAINTING_TITLE("\"Le Petit Saint Jean Baptiste\"\rLe Carrache"); // 10: 42903 + SET_PAINTING_TITLE("\"Saint Mathieu\"\rValentin"); // 11: 42904 + SET_PAINTING_TITLE("\"Le Denier de C" "\x8e" "sar \"\rValentin"); // 12: 42905 + SET_PAINTING_TITLE("\"Saint Luc\"\rValentin"); // 13: 42906 + SET_PAINTING_TITLE("\"Le mariage mystique de Sainte Catherine\"\r Alessandro Turchi"); // 14: 42907 + SET_PAINTING_TITLE("\"R" "\x8e" "union de buveurs\"\rNicolas Tournier"); // 15: 42908 + SET_PAINTING_TITLE("\"La diseuse de Bonne aventure \"\rValentin"); // 16: 42909 + SET_PAINTING_TITLE("\"le roi David jouant de la harpe \"\rDominiquin"); // 17: 42910 + SET_PAINTING_TITLE("\"Sainte Madeleine\"\rDominiquin"); // 18: 42911 + SET_PAINTING_TITLE("\"Autoportrait \"\rVan Dyck"); // 19: 42912 + SET_PAINTING_TITLE("\"Saint Jean l'" "\x8e" "vang" "\x8e" "liste\"\r Valentin"); // 20: 42913 + SET_PAINTING_TITLE("\"Agar secouru par un ange \"\rGiovanni Lanfranco"); // 21: 42914 + SET_PAINTING_TITLE("\"Saint Marc \"\rValentin"); // 22: 42915 + SET_PAINTING_TITLE("\"M" "\x8e" "l" "\x8e" "agre ayant " "\x88" + " ses pieds la hure du sanglier de Calydon\"\r Jacques Rousseau"); // 23: 43090 + SET_PAINTING_TITLE("\"Le Roi en costume romain\"\rJean Warin"); // 24: 43091 + SET_PAINTING_TITLE("\"attalante\"\rJacques Rousseau"); // 25: 43092 + SET_PAINTING_TITLE("\"En" "\x8e" "e portant Anchise\"\rSpada"); // 26: 43100 + SET_PAINTING_TITLE("\"David et Bethsab" "\x8e" "e\"\rV" "\x8e" "ron" "\x8f" "se"); // 27: 43101 + SET_PAINTING_TITLE("\"La fuite en Egypte\"\rGuido R" "\x8e" "ni "); // 28: 43102 + SET_PAINTING_TITLE("\"Louis XIV " "\x88" " cheval\"\rPierre Mignard"); // 29: 43103 + SET_PAINTING_TITLE("\"La magnificience royale & le progr" "\x8f" + "s des beaux arts\"\rHouasse"); // 30: 43104 + SET_PAINTING_TITLE("\"Le Sacrifice d'Iphig" "\x8e" "nie\"\rCharles de la Fosse"); // 31: 43130 + SET_PAINTING_TITLE("\"Buste de Louis XIV\"\rsculpt" "\x8e" + " par le Chevalier Bernin "); // 32: 43131 + SET_PAINTING_TITLE("\"Diane d" "\x8e" "couvrant son berger Endymion endormi dans les bras de Morph" + "\x8e" "e\"\rGabriel Blanchard"); // 33: 43132 + SET_PAINTING_TITLE("\"La vierge & Saint Pierre\"\rGuerchin"); // 34: 43140 + SET_PAINTING_TITLE("\"Les P" "\x8e" "lerins d'Emma" "\x9f" "s\"\rV" "\x8e" "ron" "\x8f" + "se"); // 35: 43141 + SET_PAINTING_TITLE("\"La sainte Famille\"\rV" "\x8e" "ron" "\x8f" "se"); // 36: 43142 + SET_PAINTING_TITLE("\"La famille de Darius aux pieds d'Alexandre\"\rCharles LeBrun"); // 37: 43143 + SET_PAINTING_TITLE("\"Saint Jean-Baptiste\"\rRapha" "\x91" "l"); // 38: 43144 + SET_PAINTING_TITLE("\"Marie de m" "\x8e" "dicis\"\rVan Dyck"); // 39: 43150 + SET_PAINTING_TITLE("\"Hercule luttant contre Achelous\"\rGuido R" "\x8e" "ni"); // 40: 43151 + SET_PAINTING_TITLE("\"Le Centaure Nessus porte Dejanire\"\rGuido R" "\x8e" "ni"); // 41: 43152 + SET_PAINTING_TITLE("\"Saint Franìois d'Assise r" "\x8e" "confort" "\x8e" " apr" "\x8f" + "s sa stigmatisation\"\rSeghers"); // 42: 43153 + SET_PAINTING_TITLE("\"Thomiris faisant tremper la t" "\x90" + "te de Cyrus dans le sang\"\rRubens"); // 43: 43154 + SET_PAINTING_TITLE("\"Hercule tuant l'Hydre\"\rGuido R" "\x8e" "ni"); // 44: 43155 + SET_PAINTING_TITLE("\"Hercule sur le b" "\x9e" "cher\"\rGuido R" "\x8e" "ni"); // 45: 43156 + SET_PAINTING_TITLE("\"Portrait du Prince Palatin & de son fr" "\x8f" + "re le Prince Robert\"\rVan Dyck"); // 46: 43157 + SET_PAINTING_TITLE("\"La descente de Croix \"\rCharles Lebrun"); // 47: 45260 +#undef SET_PAINTING_TITLE +} + +struct VideoSubSetting { + const char *videoName; + int16 textLeft; + int16 textTop; + int16 textRight; + int16 textBottom; + int16 drawLeft; + int16 drawTop; + int16 drawRight; + int16 drawBottom; +}; + +static const VideoSubSetting videoSubSettings[] = { + {"11D_LEB", 15, 11, 190, 479, 208, 129, 562, 479}, + {"11E_HUI", 330, 9, 620, 479, 111, 109, 321, 341}, + {"11E_MAN", 403, 12, 630, 479, 134, 89, 390, 405}, + {"11E_RAC", 10, 9, 241, 479, 271, 147, 628, 479}, + {"12E_HUI", 361, 16, 618, 479, 84, 107, 330, 479}, + {"13F_HUI", 373, 12, 633, 479, 96, 88, 341, 479}, + {"21B1_HUI", 355, 13, 625, 479, 96, 104, 337, 479}, + {"21F_BON", 324, 11, 628, 479, 84, 74, 307, 479}, + {"21F_BON2", 11, 13, 298, 479, 321, 99, 536, 424}, + {"21G_CON", 12, 13, 255, 479, 273, 156, 539, 479}, + {"21G_DAU", 358, 11, 631, 479, 82, 151, 346, 479}, + {"21G_HUI", 309, 17, 626, 479, 77, 85, 304, 479}, + {"21I_LEB", 343, 10, 628, 479, 38, 125, 330, 479}, + {"21Z_ALI", 380, 13, 627, 479, 184, 106, 369, 479}, + {"21Z_BOU", 365, 13, 629, 479, 95, 65, 341, 321}, + {"21Z_MON", 12, 11, 309, 479, 336, 101, 561, 406}, + {"21Z_PR", 10, 16, 352, 471, 375, 104, 567, 400}, + {"22G_DAU", 339, 13, 629, 479, 114, 152, 326, 479}, + {"23I_LEB", 341, 15, 627, 479, 67, 140, 325, 410}, + {"24Z_BON", 253, 23, 620, 479, 58, 166, 228, 439}, + {"31J_SUI", 9, 9, 183, 475, 195, 159, 428, 479}, + {"31L1_LUL", 367, 16, 628, 477, 136, 164, 359, 472}, + {"31M_SUI", 19, 16, 212, 479, 231, 193, 395, 479}, + {"31O_SUIA", 11, 12, 175, 479, 186, 118, 490, 479}, + {"31O_SUIP", 12, 9, 277, 466, 296, 183, 380, 349}, + {"31Q_SUI", 334, 15, 626, 479, 158, 169, 313, 308}, + {"31X_BO", 332, 11, 615, 479, 89, 78, 313, 296}, + {"31X_BON", 329, 12, 618, 456, 0, 171, 243, 479}, + {"31X_LOU", 12, 9, 267, 447, 280, 88, 639, 479}, + {"31X_SEI", 352, 12, 626, 479, 102, 98, 340, 479}, + {"32J_CRO", 418, 7, 618, 477, 103, 58, 402, 438}, + {"32M_MR", 13, 11, 175, 477, 184, 113, 476, 447}, + {"32Q_MON", 375, 17, 623, 479, 248, 161, 341, 259}, + {"32Q_RAC", 294, 11, 627, 479, 110, 152, 287, 479}, + {"32Q_RAC2", 374, 13, 625, 479, 0, 101, 366, 479}, + {"31O_SUIA", 11, 12, 175, 479, 186, 118, 490, 479}, + {"41C_HUI", 345, 17, 626, 479, 69, 147, 330, 479}, + {"41X2_CRO", 13, 13, 281, 479, 305, 113, 548, 427}, + {"42C_BON", 15, 13, 347, 479, 368, 173, 525, 410}, + {"43B1_MAI", 264, 15, 625, 479, 127, 154, 249, 296}, + {"43B1_SEI", 17, 14, 369, 479, 390, 142, 639, 479}, + {"43C_CON", 312, 11, 635, 479, 21, 137, 294, 476}, + {"43C_DUR", 11, 10, 295, 479, 311, 166, 639, 479}, + {"44C_BON", 17, 12, 331, 479, 358, 181, 531, 407}, + {"4_MAI", 325, 14, 630, 479, 35, 48, 308, 363}, + {"51L_LOU", 11, 11, 616, 161, 154, 165, 400, 479}, + {"51L_PRI", 26, 19, 601, 153, 130, 167, 311, 479}, + {"51M_LEB", 41, 29, 615, 188, 49, 200, 432, 479}, + {"51M_MAN", 23, 19, 618, 179, 211, 195, 449, 479}, + {"52A4_LAC", 12, 11, 258, 479, 273, 184, 465, 383}, + {"52L_BOU", 12, 12, 190, 479, 307, 56, 592, 332}, + {"52L_LOU", 8, 13, 604, 168, 135, 171, 413, 479}, + {"52L_PRI", 20, 17, 610, 167, 336, 182, 639, 479}, + {"53N_BON", 351, 13, 629, 479, 62, 119, 343, 418}, + {"54I_BON", 343, 14, 623, 479, 72, 117, 339, 440}, + {"61_BON", 10, 7, 311, 479, 336, 101, 581, 479}, + {"61_DUC", 10, 14, 344, 473, 376, 156, 639, 479}, + {"61_LEN", 13, 9, 269, 479, 285, 63, 590, 479}, + {"62_DUC", 18, 21, 317, 479, 388, 154, 614, 479}, +}; + +void CryOmni3DEngine_Versailles::setupDialogVariables() { +#define SET_DIAL_VARIABLE(id, var) _dialogsMan.setupVariable(id, var) + SET_DIAL_VARIABLE(0, "JOUEUR-PARLE-HUISSIER-PETIT-LEVER"); + SET_DIAL_VARIABLE(1, "HUBAS-PARLE-LEVER1"); + SET_DIAL_VARIABLE(2, "HUBAS-PARLE-LEVER2"); + SET_DIAL_VARIABLE(3, "LEBRUN-DIT-COLBERT"); + SET_DIAL_VARIABLE(4, "LEBRUN-PARLE-ESQUISSE"); + SET_DIAL_VARIABLE(5, "JOUEUR-PARLE-HUISSIER-GRAND-LEVER"); + SET_DIAL_VARIABLE(6, "BONTEMPS-PARLE-MAINTENON"); + SET_DIAL_VARIABLE(7, "BONTEMPS-PARLE-MAINTENON2"); + SET_DIAL_VARIABLE(8, "BONTEMPS-DEMANDE-INDICE"); + SET_DIAL_VARIABLE(9, "BONTEMPS-DIT-ENQUETE"); + SET_DIAL_VARIABLE(10, "JOUEUR-CONFIE-MESSAGE-HUISSIER"); + SET_DIAL_VARIABLE(11, "JOUEUR-PARLE-HUIMA1"); + SET_DIAL_VARIABLE(12, "MONSEIGNEUR-ATTEND-ESQUISSES"); + SET_DIAL_VARIABLE(13, "MONSEIGNEUR-PREVIENT-BONTEMPS"); + SET_DIAL_VARIABLE(14, "JOUEUR-MENT-MONSEIGNEUR"); + SET_DIAL_VARIABLE(15, "JOUEUR-ECOUTE-ALIAS"); + SET_DIAL_VARIABLE(16, "JOUEUR-PARLE-HUCON"); + SET_DIAL_VARIABLE(17, "BONTEMPS-ATTEND-OBJET-GALLERIE"); + SET_DIAL_VARIABLE(18, "SUISSE-APOLLON-PARLE-CLEF"); + SET_DIAL_VARIABLE(19, "SUISSE-CABINET-DEMANDE-AUTORISATION"); + SET_DIAL_VARIABLE(20, "SUISSE-VU-AUTORISATION"); + SET_DIAL_VARIABLE(21, "CROISSY-ACCEPTE-TEXTE"); + SET_DIAL_VARIABLE(22, "JOUEUR-POSSEDE-CLEF-PETITE-PORTE"); + SET_DIAL_VARIABLE(23, "SUISSE-REFUSE-CLEF"); + SET_DIAL_VARIABLE(24, "LULLY-ATTEND-MISSION-JOUEUR"); + SET_DIAL_VARIABLE(25, "LULLY-DONNE-MISSION1-JOUEUR"); + SET_DIAL_VARIABLE(26, "LULLY-DONNE-MISSION-JOUEUR"); + SET_DIAL_VARIABLE(27, "RACINE-REPOND-ETRANGERE"); + SET_DIAL_VARIABLE(28, "RACINE-REPOND-PEUPLES"); + SET_DIAL_VARIABLE(29, "LULLY-DONNE-MISSION2-JOUEUR"); + SET_DIAL_VARIABLE(30, "LULLY-DIT-CHAT-PENDU-JOUEUR"); + SET_DIAL_VARIABLE(31, "JOUEUR-DIT-PEUPLES-LULLY"); + SET_DIAL_VARIABLE(32, "LALANDE-PARLE-BONTEMPS-SCENE3"); + SET_DIAL_VARIABLE(33, "BONTEMPS-DONNE-AUTORISATION-CURIOSITES"); + SET_DIAL_VARIABLE(34, "BONTEMPS-ATTEND-PAMPHLET"); + SET_DIAL_VARIABLE(35, "BONTEMPS-VU-PAMPHLET-DECHIFFRE-LULLY"); + SET_DIAL_VARIABLE(36, "CROISSY-DIT-INEPTIES"); + SET_DIAL_VARIABLE(37, "CROISSY-ATTEND-PAMPHLET2"); + SET_DIAL_VARIABLE(38, "CROISSY-ATTEND-MEDAILLE"); + SET_DIAL_VARIABLE(39, "CROISSY-ATTEND-PAMPHLET2-2"); + SET_DIAL_VARIABLE(40, "JOUEUR-PARLE-CROISSY1"); + SET_DIAL_VARIABLE(41, "MONSIEUR-PARLE-LALANDE1"); + SET_DIAL_VARIABLE(42, "MONSIEUR-ATTEND-FUSAIN"); + SET_DIAL_VARIABLE(43, "MONSIEUR-DONNE-SOLUTION-MEDAILLES"); + SET_DIAL_VARIABLE(44, "HUISSIER-DIT-DINER"); + SET_DIAL_VARIABLE(45, "HUISSIER-DIT-PREVENIR-BONTEMPS"); + SET_DIAL_VARIABLE(46, "JOUEUR-POSSEDE-PAMPHLET-RELIGION"); + SET_DIAL_VARIABLE(47, "JOUEUR-PARLE-BONTEMPS-SCENE4"); + SET_DIAL_VARIABLE(48, "BONTEMPS-VU-PAPIER-CROISSY"); + SET_DIAL_VARIABLE(49, "BONTEMPS-ATTEND-OBJET-SCENE4"); + SET_DIAL_VARIABLE(50, "BONTEMPS-VU-PAMPHLET-GOUVERNEMENT"); + SET_DIAL_VARIABLE(51, "JOUEUR-PARLE-VAUBAN"); + SET_DIAL_VARIABLE(52, "JOUEUR-PARLE-CODE-LOUVOIS"); + SET_DIAL_VARIABLE(53, "LALANDE-ECOUTE-LOUVOIS"); + SET_DIAL_VARIABLE(54, "JOUEUR-PARLE-LACHAIZE"); + SET_DIAL_VARIABLE(55, "JOUEUR-PARLE-LACHAIZE2"); + SET_DIAL_VARIABLE(56, "LACHAIZE-ATTEND-TEXTE"); + SET_DIAL_VARIABLE(57, "LACHAIZE-VU-PAMPHLET-RELIGION"); + SET_DIAL_VARIABLE(58, "LACHAIZE-DIT-REFORME"); + SET_DIAL_VARIABLE(59, "LACHAIZE-PARLE-BOUILLON"); + SET_DIAL_VARIABLE(60, "BOUILLON-DIT-DRAGONNADES"); + SET_DIAL_VARIABLE(61, "JOUEUR-PARLE-BOUILLON"); + SET_DIAL_VARIABLE(62, "LACHAIZE-TROUVE-ECROUELLES"); + SET_DIAL_VARIABLE(63, "LACHAIZE-DIT-DRAGONNADES"); + SET_DIAL_VARIABLE(64, "LACHAIZE-DEMANDE-TEXTE"); + SET_DIAL_VARIABLE(65, "LACHAIZE-PARLE-ARCHITECTURE"); + SET_DIAL_VARIABLE(66, "JOUEUR-DIT-DRAGONNADES"); + SET_DIAL_VARIABLE(67, "BOUILLON-ATTEND-PAMPHLET"); + SET_DIAL_VARIABLE(68, "BONTEMPS-PARLE-LUSTRE"); + SET_DIAL_VARIABLE(69, "BONTEMPS-ATTEND-MEMORANDUM2"); + SET_DIAL_VARIABLE(70, "BONTEMPS-DIT-PROMENADE"); + SET_DIAL_VARIABLE(71, "BONTEMPS-ATTEND-MEMORANDUM"); + SET_DIAL_VARIABLE(72, "LENOTRE-DIT-CALME"); + SET_DIAL_VARIABLE(73, "MAINE-DIT-APOTHICAIRIE"); + SET_DIAL_VARIABLE(74, "JOUEUR-PARLE-BONTEMPS-SCENE6"); + SET_DIAL_VARIABLE(75, "{JOUEUR-ESSAYE-OUVRIR-PORTE-CHAMBRE}"); + SET_DIAL_VARIABLE(76, "{JOUEUR-TROUVE-TITRE-ET-PAMPHLET}"); + SET_DIAL_VARIABLE(77, "{JOUEUR-ESSAYE-OUVRIR-PORTE-SALON}"); + SET_DIAL_VARIABLE(78, "{JOUEUR-MONTRE-PAPIER-ECRIT-ENCRE-SYMPATHIQUE}"); + SET_DIAL_VARIABLE(79, "{JOUEUR-MONTRE-UN-PAMPHLET}"); + SET_DIAL_VARIABLE(80, "{JOUEUR-MONTRE-TOUT-AUTRE-OBJET}"); + SET_DIAL_VARIABLE(81, "{JOUEUR-MONTRE-PAMPHLET-ARTS}"); + SET_DIAL_VARIABLE(82, "{JOUEUR-A-MONTRE-ESQUISSES-NON-TRIEES-LEBRUN}"); + SET_DIAL_VARIABLE(83, "{JOUEUR-DONNE-ESQUISSES}"); + SET_DIAL_VARIABLE(84, "{JOUEUR-SE-DIRIGE-VERS-MONSEIGNEUR-AVEC-ESQUISSES}"); + SET_DIAL_VARIABLE(85, "{JOUEUR-PRESENTE-FAUX-CROQUIS3}"); + SET_DIAL_VARIABLE(86, "{JOUEUR-PRESENTE-FAUX-CROQUIS2}"); + SET_DIAL_VARIABLE(87, "{JOUEUR-PRESENTE-FAUX-CROQUIS}"); + SET_DIAL_VARIABLE(88, "{LE JOUEUR-PRESENTE-ESQUISSES-TRIEES}"); + SET_DIAL_VARIABLE(89, "{LE JOUEUR-PRESENTE-AUTRES-ESQUISSES-OU-ESQUISSE-NON-TRIEES}"); + SET_DIAL_VARIABLE(90, "{JOUEUR-PRESENTE-PAMPHLET-SUR-LEBRUN}"); + SET_DIAL_VARIABLE(91, "{JOUEUR-PRESENTE-TOUT-AUTRE-PAMPHLET-OU-LETTRE}"); + SET_DIAL_VARIABLE(92, "{JOUEUR-MONTRE-ESQUISSE-DETRUITE}"); + SET_DIAL_VARIABLE(93, "{JOUEUR-MONTRE-TITRE-FABLE-APPARU-SUR-ESQUISSE}"); + SET_DIAL_VARIABLE(94, "{JOUEUR-MONTRE-AUTORISATION-DE-BONTEMPS}"); + SET_DIAL_VARIABLE(95, "{LE JOUEUR-A-TENTE-OUVRIR-PETITE-PORTE}"); + SET_DIAL_VARIABLE(96, "{JOUEUR-POSSEDE-CLE}"); + SET_DIAL_VARIABLE(97, "{JOUEUR-PRESENTE-PAMPHLET-PARTITION}"); + SET_DIAL_VARIABLE(98, "{JOUEUR-MONTRE-PAMPHLET-DECHIFFRE-PAR-LULLY}"); + SET_DIAL_VARIABLE(99, "{JOUEUR-MONTRE-MEDAILLES-MONSIEUR}"); + SET_DIAL_VARIABLE(100, "{JOUEUR-MONTRE-PAMPHLET-ARCHITECTURE}"); + SET_DIAL_VARIABLE(101, "{JOUEUR-MONTRE-EPIGRAPHE-MEDAILLES}"); + SET_DIAL_VARIABLE(102, "{JOUEUR-MONTRE-TOUT-AUTRE-CHOSE}"); + SET_DIAL_VARIABLE(103, "{JOUEUR-POSSEDE-FUSAIN-MEDAILLES}"); + SET_DIAL_VARIABLE(104, "{JOUEUR-MONTRE-FUSAIN-MEDAILLES}"); + SET_DIAL_VARIABLE(105, "{JOUEUR-PRESENTE-OBJET-HUISSIER}"); + SET_DIAL_VARIABLE(106, "{JOUEUR-APPROCHE-MADAME-MAINTENON}"); + SET_DIAL_VARIABLE(107, "{JOUEUR-DONNE-REPAS}"); + SET_DIAL_VARIABLE(108, "{JOUEUR-TROUVE-PLANS-VAUBAN}"); + SET_DIAL_VARIABLE(109, "{JOUEUR-ALLER-BUREAU-LOUVOIS}"); + SET_DIAL_VARIABLE(110, "{JOUEUR-MONTRE-PAMPHLET-RELIGION}"); + SET_DIAL_VARIABLE(111, "{JOUEUR-MONTRE-PAMPHLET-GOUVERNEMENT}"); + SET_DIAL_VARIABLE(112, "{JOUEUR-MONTRE-PAPIER-CROISSY}"); + SET_DIAL_VARIABLE(113, "{JOUEUR-MONTRE-ECROUELLES}"); + SET_DIAL_VARIABLE(114, "{LACHAIZE-TIENT-TEXTE}"); + SET_DIAL_VARIABLE(115, "{JOUEUR-VU-PLANS-SALON-DIANE}"); + SET_DIAL_VARIABLE(116, "{JOUEUR-VU-MEMORANDUM-DANS-LUSTRE-DU-SALON-DE-LA-GUERRE}"); + SET_DIAL_VARIABLE(117, "{JOUEUR-VU-MEMORANDUM-DANS-LUSTRE-DU-SALON-APOLLON}"); + SET_DIAL_VARIABLE(118, "{JOUEUR-MONTRE-MEMORANDUM}"); + SET_DIAL_VARIABLE(119, "{JOUEUR-POSSEDE-CLEF-3-ET-4}"); + SET_DIAL_VARIABLE(120, "{JOUEUR-DONNE-SIROP-DE-ROSE}"); + SET_DIAL_VARIABLE(121, "{JOUEUR-DONNE-AUTRE-MEDICAMENT}"); + SET_DIAL_VARIABLE(122, "{DUC_MAIN_A_PARLE}"); + SET_DIAL_VARIABLE(123, "{LEVEL1_FINI}"); + SET_DIAL_VARIABLE(124, "{LEVEL2_FINI}"); + SET_DIAL_VARIABLE(125, "{LEVEL3_FINI}"); + SET_DIAL_VARIABLE(126, "{LEVEL4_FINI}"); + SET_DIAL_VARIABLE(127, "{LEVEL5_FINI}"); + SET_DIAL_VARIABLE(128, "{LEVEL6_FINI}"); + SET_DIAL_VARIABLE(129, "{LEVEL7_FINI}"); + SET_DIAL_VARIABLE(130, "{JOUEUR_POSSEDE_PAMPHLET_ARCHI}"); + SET_DIAL_VARIABLE(131, "{FAUSSE_ESQ_OK}"); + SET_DIAL_VARIABLE(132, "{CURRENT_GAME_TIME1}"); + SET_DIAL_VARIABLE(133, "{CURRENT_GAME_TIME2}"); + SET_DIAL_VARIABLE(134, "{CURRENT_GAME_TIME3}"); + SET_DIAL_VARIABLE(135, "{CURRENT_GAME_TIME4}"); + SET_DIAL_VARIABLE(136, "{CURRENT_GAME_TIME5}"); + SET_DIAL_VARIABLE(137, "{JOUEUR_POSSEDE_EPIGRAPHE}"); +#undef SET_DIAL_VARIABLE + for (unsigned int i = 0; i < ARRAYSIZE(videoSubSettings); i++) { + const VideoSubSetting &vss = videoSubSettings[i]; + _dialogsMan.registerSubtitlesSettings( + vss.videoName, + DialogsManager::SubtitlesSettings( + vss.textLeft, vss.textTop, vss.textRight, vss.textBottom, + vss.drawLeft, vss.drawTop, vss.drawRight, vss.drawBottom)); + } +} + +void CryOmni3DEngine_Versailles::initPlacesStates() { +#define SET_PLACE_STATE(id, init, filter, docImage) _placeStates[id] = PlaceState(init, filter, docImage) +#define FILTER_EVENT(level, place) &CryOmni3DEngine_Versailles::filterEventLevel ## level ## Place ## place +#define INIT_PLACE(level, place) &CryOmni3DEngine_Versailles::initPlaceLevel ## level ## Place ## place + if (_currentLevel == 1) { + _placeStates.resize(15); + SET_PLACE_STATE(1, nullptr, FILTER_EVENT(1, 1), "VS22"); + SET_PLACE_STATE(2, nullptr, FILTER_EVENT(1, 2), "VS20"); + SET_PLACE_STATE(3, INIT_PLACE(1, 3), FILTER_EVENT(1, 3), "VS19"); + SET_PLACE_STATE(4, nullptr, nullptr, nullptr); + SET_PLACE_STATE(5, nullptr, nullptr, nullptr); + SET_PLACE_STATE(6, nullptr, nullptr, nullptr); + SET_PLACE_STATE(7, nullptr, nullptr, nullptr); // Filter is a leftover + SET_PLACE_STATE(8, nullptr, nullptr, nullptr); + SET_PLACE_STATE(9, nullptr, nullptr, nullptr); + SET_PLACE_STATE(10, nullptr, nullptr, "VS31"); + SET_PLACE_STATE(11, nullptr, nullptr, "VS31"); + SET_PLACE_STATE(12, nullptr, nullptr, nullptr); + SET_PLACE_STATE(13, nullptr, nullptr, "VS31"); + SET_PLACE_STATE(14, nullptr, FILTER_EVENT(1, 14), nullptr); + } else if (_currentLevel == 2) { + _placeStates.resize(15); + SET_PLACE_STATE(1, nullptr, nullptr, "VS22"); + SET_PLACE_STATE(2, nullptr, nullptr, "VS20"); + SET_PLACE_STATE(3, nullptr, nullptr, "VS19"); + SET_PLACE_STATE(4, nullptr, nullptr, "VS18"); + SET_PLACE_STATE(5, nullptr, nullptr, nullptr); + SET_PLACE_STATE(6, nullptr, nullptr, "VS19"); + SET_PLACE_STATE(7, nullptr, nullptr, nullptr); + SET_PLACE_STATE(8, nullptr, nullptr, nullptr); + SET_PLACE_STATE(9, nullptr, nullptr, "VS23"); + SET_PLACE_STATE(10, nullptr, nullptr, "VS31"); + SET_PLACE_STATE(11, nullptr, nullptr, "VS31"); + SET_PLACE_STATE(12, nullptr, nullptr, "VS24"); + SET_PLACE_STATE(13, nullptr, nullptr, "VS31"); + SET_PLACE_STATE(14, nullptr, nullptr, nullptr); + } else if (_currentLevel == 3) { + _placeStates.resize(25); + SET_PLACE_STATE(1, nullptr, nullptr, "VS35"); + SET_PLACE_STATE(2, nullptr, nullptr, "VS40"); + SET_PLACE_STATE(3, nullptr, nullptr, "VS40"); + SET_PLACE_STATE(4, nullptr, nullptr, "VS36"); + SET_PLACE_STATE(5, nullptr, nullptr, "VS36"); + SET_PLACE_STATE(6, nullptr, nullptr, "VS30"); + SET_PLACE_STATE(7, nullptr, nullptr, "VS30"); + SET_PLACE_STATE(8, nullptr, nullptr, "VS30"); + SET_PLACE_STATE(9, nullptr, nullptr, "VS39"); + SET_PLACE_STATE(10, nullptr, nullptr, "VS28"); + SET_PLACE_STATE(11, nullptr, nullptr, "VS28"); + SET_PLACE_STATE(12, nullptr, nullptr, "VS30"); + SET_PLACE_STATE(13, nullptr, nullptr, "VS27"); + SET_PLACE_STATE(14, nullptr, nullptr, "VS26"); + SET_PLACE_STATE(15, nullptr, nullptr, "VS25"); + SET_PLACE_STATE(16, nullptr, nullptr, "VS24"); + SET_PLACE_STATE(17, nullptr, nullptr, "VS25"); + SET_PLACE_STATE(18, nullptr, nullptr, nullptr); + SET_PLACE_STATE(19, nullptr, nullptr, "VS26"); + SET_PLACE_STATE(20, nullptr, nullptr, nullptr); + SET_PLACE_STATE(21, nullptr, nullptr, "VS28"); + SET_PLACE_STATE(22, nullptr, nullptr, "VS26"); + SET_PLACE_STATE(23, nullptr, nullptr, nullptr); + SET_PLACE_STATE(24, nullptr, nullptr, "VS30"); + } else if (_currentLevel == 4) { + _placeStates.resize(18); + SET_PLACE_STATE(1, nullptr, nullptr, "VS35"); + SET_PLACE_STATE(2, nullptr, nullptr, "VS40"); + SET_PLACE_STATE(3, nullptr, nullptr, "VS40"); + SET_PLACE_STATE(4, nullptr, nullptr, "VS36"); + SET_PLACE_STATE(5, nullptr, nullptr, nullptr); + SET_PLACE_STATE(6, nullptr, nullptr, nullptr); + SET_PLACE_STATE(7, nullptr, nullptr, "VS17"); + SET_PLACE_STATE(8, nullptr, nullptr, "VS17"); + SET_PLACE_STATE(9, nullptr, nullptr, nullptr); + SET_PLACE_STATE(10, nullptr, nullptr, "VS18"); + SET_PLACE_STATE(11, nullptr, nullptr, "VS20"); + SET_PLACE_STATE(12, nullptr, nullptr, "VS31"); + SET_PLACE_STATE(13, nullptr, nullptr, "VS31"); + SET_PLACE_STATE(14, nullptr, nullptr, "VS31"); + SET_PLACE_STATE(15, nullptr, nullptr, "VS36"); + SET_PLACE_STATE(16, nullptr, nullptr, nullptr); + SET_PLACE_STATE(17, nullptr, nullptr, nullptr); + } else if (_currentLevel == 5) { + _placeStates.resize(35); + SET_PLACE_STATE(1, nullptr, nullptr, "VS35"); + SET_PLACE_STATE(2, nullptr, nullptr, "VS35"); + SET_PLACE_STATE(3, nullptr, nullptr, "VS36"); + SET_PLACE_STATE(4, nullptr, nullptr, "VS36"); + SET_PLACE_STATE(5, nullptr, nullptr, "VS36"); + SET_PLACE_STATE(6, nullptr, nullptr, "VS30"); + SET_PLACE_STATE(7, nullptr, nullptr, "VS30"); + SET_PLACE_STATE(8, nullptr, nullptr, "VS30"); + SET_PLACE_STATE(9, nullptr, nullptr, "VS39"); + SET_PLACE_STATE(10, nullptr, nullptr, "VS28"); + SET_PLACE_STATE(11, nullptr, nullptr, "VS16"); + SET_PLACE_STATE(12, nullptr, nullptr, "VS30"); + SET_PLACE_STATE(13, nullptr, nullptr, "VS27"); + SET_PLACE_STATE(14, nullptr, nullptr, "VS26"); + SET_PLACE_STATE(15, nullptr, nullptr, "VS25"); + SET_PLACE_STATE(16, nullptr, nullptr, "VS24"); + SET_PLACE_STATE(17, nullptr, nullptr, "VS25"); + SET_PLACE_STATE(18, nullptr, nullptr, nullptr); + SET_PLACE_STATE(19, nullptr, nullptr, nullptr); + SET_PLACE_STATE(20, nullptr, nullptr, nullptr); + SET_PLACE_STATE(21, nullptr, nullptr, "VS28"); + SET_PLACE_STATE(22, nullptr, nullptr, nullptr); + SET_PLACE_STATE(23, nullptr, nullptr, nullptr); + SET_PLACE_STATE(24, nullptr, nullptr, nullptr); + SET_PLACE_STATE(25, nullptr, nullptr, nullptr); + SET_PLACE_STATE(26, nullptr, nullptr, "VS16"); + SET_PLACE_STATE(27, nullptr, nullptr, "VS16"); + SET_PLACE_STATE(28, nullptr, nullptr, nullptr); + SET_PLACE_STATE(29, nullptr, nullptr, "VS24"); + SET_PLACE_STATE(30, nullptr, nullptr, nullptr); + SET_PLACE_STATE(31, nullptr, nullptr, nullptr); + SET_PLACE_STATE(32, nullptr, nullptr, nullptr); + SET_PLACE_STATE(33, nullptr, nullptr, nullptr); + SET_PLACE_STATE(34, nullptr, nullptr, nullptr); + } else if (_currentLevel == 6) { + _placeStates.resize(45); + SET_PLACE_STATE(1, nullptr, nullptr, "VS34"); + SET_PLACE_STATE(2, nullptr, nullptr, "VS32"); + SET_PLACE_STATE(3, nullptr, nullptr, "VS32"); + SET_PLACE_STATE(4, nullptr, nullptr, "VS32"); + SET_PLACE_STATE(5, nullptr, nullptr, "VS32"); + SET_PLACE_STATE(6, nullptr, nullptr, "VS32"); + SET_PLACE_STATE(7, nullptr, nullptr, "VS32"); + SET_PLACE_STATE(8, nullptr, nullptr, "VS32"); + SET_PLACE_STATE(9, nullptr, nullptr, "VS32"); + SET_PLACE_STATE(10, nullptr, nullptr, "VS22"); + SET_PLACE_STATE(11, nullptr, nullptr, "VS12"); + SET_PLACE_STATE(12, nullptr, nullptr, "VS32"); + SET_PLACE_STATE(13, nullptr, nullptr, "VS33"); + SET_PLACE_STATE(14, nullptr, nullptr, "VS33"); + SET_PLACE_STATE(15, nullptr, nullptr, "VS33"); + SET_PLACE_STATE(16, nullptr, nullptr, "VS33"); + SET_PLACE_STATE(17, nullptr, nullptr, "VS33"); + SET_PLACE_STATE(18, nullptr, nullptr, "VS33"); + SET_PLACE_STATE(19, nullptr, nullptr, "VS33"); + SET_PLACE_STATE(20, nullptr, nullptr, "VS33"); + SET_PLACE_STATE(21, nullptr, nullptr, "VS33"); + SET_PLACE_STATE(22, nullptr, nullptr, "VS33"); + SET_PLACE_STATE(23, nullptr, nullptr, "VS33"); + SET_PLACE_STATE(24, nullptr, nullptr, "VS33"); + SET_PLACE_STATE(25, nullptr, nullptr, "VS33"); + SET_PLACE_STATE(26, nullptr, nullptr, "VS33"); + SET_PLACE_STATE(27, nullptr, nullptr, "VS33"); + SET_PLACE_STATE(28, nullptr, nullptr, "VS33"); + SET_PLACE_STATE(29, nullptr, nullptr, "VS33"); + SET_PLACE_STATE(30, nullptr, nullptr, "VS33"); + SET_PLACE_STATE(31, nullptr, nullptr, "VS33"); + SET_PLACE_STATE(32, nullptr, nullptr, "VS33"); + SET_PLACE_STATE(33, nullptr, nullptr, "VS33"); + SET_PLACE_STATE(34, nullptr, nullptr, "VS33"); + SET_PLACE_STATE(35, nullptr, nullptr, "VS33"); + SET_PLACE_STATE(36, nullptr, nullptr, "VS33"); + SET_PLACE_STATE(37, nullptr, nullptr, "VS33"); + SET_PLACE_STATE(38, nullptr, nullptr, "VS33"); + SET_PLACE_STATE(39, nullptr, nullptr, "VS33"); + SET_PLACE_STATE(40, nullptr, nullptr, "VS33"); + SET_PLACE_STATE(41, nullptr, nullptr, "VS33"); + SET_PLACE_STATE(42, nullptr, nullptr, "VS33"); + SET_PLACE_STATE(43, nullptr, nullptr, "VS33"); + SET_PLACE_STATE(44, nullptr, nullptr, "VS33"); + } else if (_currentLevel == 7) { + _placeStates.resize(30); + SET_PLACE_STATE(1, nullptr, nullptr, nullptr); + SET_PLACE_STATE(2, nullptr, nullptr, nullptr); + SET_PLACE_STATE(3, nullptr, nullptr, nullptr); + SET_PLACE_STATE(4, nullptr, nullptr, nullptr); + SET_PLACE_STATE(5, nullptr, nullptr, nullptr); + SET_PLACE_STATE(6, nullptr, nullptr, nullptr); + SET_PLACE_STATE(7, nullptr, nullptr, nullptr); + SET_PLACE_STATE(8, nullptr, nullptr, nullptr); + SET_PLACE_STATE(9, nullptr, nullptr, nullptr); + SET_PLACE_STATE(10, nullptr, nullptr, "VS33"); + SET_PLACE_STATE(11, nullptr, nullptr, "VS33"); + SET_PLACE_STATE(12, nullptr, nullptr, nullptr); + SET_PLACE_STATE(13, nullptr, nullptr, "VS33"); + SET_PLACE_STATE(14, nullptr, nullptr, nullptr); + SET_PLACE_STATE(15, nullptr, nullptr, nullptr); + SET_PLACE_STATE(16, nullptr, nullptr, nullptr); + SET_PLACE_STATE(17, nullptr, nullptr, nullptr); + SET_PLACE_STATE(18, nullptr, nullptr, nullptr); + SET_PLACE_STATE(19, nullptr, nullptr, nullptr); + SET_PLACE_STATE(20, nullptr, nullptr, nullptr); + SET_PLACE_STATE(21, nullptr, nullptr, nullptr); + SET_PLACE_STATE(22, nullptr, nullptr, nullptr); + SET_PLACE_STATE(23, nullptr, nullptr, nullptr); + SET_PLACE_STATE(24, nullptr, nullptr, nullptr); + SET_PLACE_STATE(25, nullptr, nullptr, nullptr); + SET_PLACE_STATE(26, nullptr, nullptr, nullptr); + SET_PLACE_STATE(27, nullptr, nullptr, nullptr); + SET_PLACE_STATE(28, nullptr, nullptr, nullptr); + SET_PLACE_STATE(29, nullptr, nullptr, nullptr); + } else if (_currentLevel == 8) { + _placeStates.resize(50); + SET_PLACE_STATE(1, nullptr, nullptr, "VS35"); + SET_PLACE_STATE(2, nullptr, nullptr, "VS40"); + SET_PLACE_STATE(3, nullptr, nullptr, "VS40"); + SET_PLACE_STATE(4, nullptr, nullptr, "VS36"); + SET_PLACE_STATE(5, nullptr, nullptr, nullptr); + SET_PLACE_STATE(6, nullptr, nullptr, nullptr); + SET_PLACE_STATE(7, nullptr, nullptr, "VS30"); + SET_PLACE_STATE(8, nullptr, nullptr, "VS30"); + SET_PLACE_STATE(9, nullptr, nullptr, "VS39"); + SET_PLACE_STATE(10, nullptr, nullptr, "VS28"); + SET_PLACE_STATE(11, nullptr, nullptr, "VS16"); + SET_PLACE_STATE(12, nullptr, nullptr, "VS30"); + SET_PLACE_STATE(13, nullptr, nullptr, "VS27"); + SET_PLACE_STATE(14, nullptr, nullptr, "VS26"); + SET_PLACE_STATE(15, nullptr, nullptr, "VS25"); + SET_PLACE_STATE(16, nullptr, nullptr, "VS24"); + SET_PLACE_STATE(17, nullptr, nullptr, nullptr); + SET_PLACE_STATE(18, nullptr, nullptr, nullptr); + SET_PLACE_STATE(19, nullptr, nullptr, nullptr); + SET_PLACE_STATE(20, nullptr, nullptr, nullptr); + SET_PLACE_STATE(21, nullptr, nullptr, nullptr); + SET_PLACE_STATE(22, nullptr, nullptr, nullptr); + SET_PLACE_STATE(23, nullptr, nullptr, nullptr); + SET_PLACE_STATE(24, nullptr, nullptr, "VS30"); + SET_PLACE_STATE(25, nullptr, nullptr, nullptr); + SET_PLACE_STATE(26, nullptr, nullptr, nullptr); + SET_PLACE_STATE(27, nullptr, nullptr, nullptr); + SET_PLACE_STATE(28, nullptr, nullptr, nullptr); + SET_PLACE_STATE(29, nullptr, nullptr, nullptr); + SET_PLACE_STATE(30, nullptr, nullptr, nullptr); + SET_PLACE_STATE(31, nullptr, nullptr, nullptr); + SET_PLACE_STATE(32, nullptr, nullptr, nullptr); + SET_PLACE_STATE(33, nullptr, nullptr, "VS31"); + SET_PLACE_STATE(34, nullptr, nullptr, "VS31"); + SET_PLACE_STATE(35, nullptr, nullptr, "VS31"); + SET_PLACE_STATE(36, nullptr, nullptr, "VS23"); + SET_PLACE_STATE(37, nullptr, nullptr, "VS22"); + SET_PLACE_STATE(38, nullptr, nullptr, "VS20"); + SET_PLACE_STATE(39, nullptr, nullptr, nullptr); + SET_PLACE_STATE(40, nullptr, nullptr, "VS18"); + SET_PLACE_STATE(41, nullptr, nullptr, nullptr); + SET_PLACE_STATE(42, nullptr, nullptr, "VS17"); + SET_PLACE_STATE(43, nullptr, nullptr, "VS17"); + SET_PLACE_STATE(44, nullptr, nullptr, nullptr); + SET_PLACE_STATE(45, nullptr, nullptr, nullptr); + SET_PLACE_STATE(46, nullptr, nullptr, nullptr); + SET_PLACE_STATE(47, nullptr, nullptr, nullptr); + SET_PLACE_STATE(48, nullptr, nullptr, nullptr); + SET_PLACE_STATE(49, nullptr, nullptr, "VS19"); + } +#undef INIT_PLACE +#undef FILTER_EVENT +#undef SET_PLACE_STATE +} + +void CryOmni3DEngine_Versailles::setupLevelActionsMask() { + _actionMasks.clear(); +#define SET_MASK(placeId, placeState, oldActionId, newActionId) _actionMasks[PlaceStateActionKey(placeId, placeState, oldActionId)] = newActionId + if (_currentLevel == 1) { + SET_MASK(1, 0, 11015, 0); + SET_MASK(1, 0, 21015, 0); + SET_MASK(1, 1, 21011, 0); + SET_MASK(1, 1, 21012, 0); + SET_MASK(1, 1, 21013, 0); + SET_MASK(1, 1, 21014, 0); + // 2, 0 is empty + SET_MASK(2, 1, 51201, 0); + SET_MASK(2, 1, 21202, 0); + SET_MASK(2, 1, 21203, 0); + SET_MASK(2, 2, 51201, 0); + SET_MASK(2, 2, 21202, 0); + SET_MASK(2, 2, 21203, 0); + SET_MASK(2, 2, 11201, 1); + SET_MASK(2, 2, 21201, 1); + // 3, 0 is empty + SET_MASK(3, 1, 11301, 0); + SET_MASK(3, 1, 21301, 0); + // 14, 0 is empty + SET_MASK(14, 1, 31141, 0); + } else if (_currentLevel == 2) { + // 1, 0 is empty + SET_MASK(1, 1, 12101, 0); + SET_MASK(1, 1, 22101, 0); + SET_MASK(11, 0, 12111, 0); + SET_MASK(11, 0, 22111, 0); + // 11, 1 is empty + // 9, 0 is empty + SET_MASK(9, 1, 52903, 0); + SET_MASK(9, 1, 22903, 0); + SET_MASK(9, 1, 52902, 12902); + SET_MASK(9, 2, 52903, 0); + SET_MASK(9, 2, 22903, 0); + SET_MASK(9, 2, 52902, 0); + SET_MASK(9, 2, 22902, 1); + } else if (_currentLevel == 3) { + SET_MASK(13, 0, 13131, 0); + SET_MASK(13, 0, 23131, 0); + SET_MASK(13, 1, 13131, 0); + SET_MASK(13, 1, 23131, 0); + SET_MASK(13, 1, 33130, 0); + // 13, 2 is empty + SET_MASK(13, 3, 33130, 0); + // 14, 0 is empty + SET_MASK(14, 1, 23220, 0); + SET_MASK(15, 0, 13151, 43154); + SET_MASK(15, 0, 23151, 0); + // 15, 1 is empty + SET_MASK(17, 0, 13151, 0); + SET_MASK(17, 0, 23151, 0); + // 17, 1 is empty + // 16, 0 is empty + SET_MASK(16, 1, 43160, 0); + // 19, 0 is empty + SET_MASK(19, 1, 43190, 0); + SET_MASK(22, 0, 33220, 0); + SET_MASK(22, 1, 13220, 0); + SET_MASK(22, 1, 23220, 0); + SET_MASK(22, 2, 13220, 0); + SET_MASK(22, 2, 23220, 0); + SET_MASK(22, 2, 33220, 0); + } else if (_currentLevel == 4) { + // TODO: finish the boring job + error("TODO:"); + } else if (_currentLevel == 5) { + error("TODO:"); + } else if (_currentLevel == 6) { + error("TODO:"); + } else if (_currentLevel == 7) { + // 9, 0 is empty + SET_MASK(9, 1, 37090, 0); + } else if (_currentLevel == 8) { + // Nothing to mask + } else { + error("Invalid level"); + } +#undef SET_MASK +} + +void CryOmni3DEngine_Versailles::initWhoSpeaksWhere() { + _whoSpeaksWhere.clear(); +#define SET_WHO(placeId, actionId, dialog) _whoSpeaksWhere[PlaceActionKey(placeId, actionId)] = dialog + if (_currentLevel == 1) { + SET_WHO(1, 11015, "13F_HUI"); + SET_WHO(1, 12101, "21F_BON"); + SET_WHO(1, 52903, "21G_DAU"); + SET_WHO(1, 52902, "21G_DAU"); + SET_WHO(2, 11201, "11E_HUI"); + SET_WHO(2, 51201, "11E_RAC"); + SET_WHO(3, 11301, "11D_LEB"); + SET_WHO(5, 12501, "21B1_HUI"); + if (currentGameTime() >= 2) { + SET_WHO(2, 11201, "12E_HUI"); + } + } else if (_currentLevel == 2) { + SET_WHO(1, 12101, "21F_BON"); + SET_WHO(9, 52903, "21G_DAU"); + SET_WHO(9, 52902, "21G_DAU"); + SET_WHO(9, 12902, "22G_DAU"); + SET_WHO(9, 11201, "11E_HUI"); + SET_WHO(9, 12901, "21G_HUI"); + SET_WHO(5, 12501, "21B1_HUI"); + SET_WHO(10, 12130, "21Z_ALI"); + SET_WHO(10, 12130, "21Z_MON"); + SET_WHO(10, 12111, "24Z_BON"); + SET_WHO(11, 12130, "21Z_MON"); + SET_WHO(11, 12111, "24Z_BON"); + SET_WHO(13, 12130, "21Z_ALI"); + SET_WHO(13, 12130, "21Z_MON"); + SET_WHO(13, 12111, "24Z_BON"); + SET_WHO(12, 12121, "23I_LEB"); + SET_WHO(10, 52130, "21Z_ALI"); + SET_WHO(11, 52130, "21Z_ALI"); + SET_WHO(13, 52130, "21Z_ALI"); + SET_WHO(10, 52101, "21Z_MON"); + if (currentGameTime() >= 2) { + SET_WHO(9, 52902, "22G_DAU"); + } + } else if (_currentLevel == 3) { + SET_WHO(13, 13130, "31M_SUI"); + SET_WHO(13, 13131, "32M_MR"); + SET_WHO(10, 13100, "31O_SUIA"); + SET_WHO(10, 13101, "31O_SUIP"); + SET_WHO(22, 13220, "31L1_LUL"); + SET_WHO(6, 13060, "31Q_SUI"); + SET_WHO(15, 13150, "31J_SUI"); + SET_WHO(17, 13150, "31J_SUI"); + SET_WHO(3, 13030, "31X_BON"); + SET_WHO(24, 53240, "32Q_MON"); + SET_WHO(24, 13241, "32Q_RAC2"); + SET_WHO(4, 53041, "31X_SEI"); + SET_WHO(4, 53040, "31X_LOU"); + SET_WHO(15, 13151, "32J_CRO"); + SET_WHO(17, 13151, "32J_CRO"); + } else if (_currentLevel == 4) { + SET_WHO(10, 14104, "41C_HUI"); + SET_WHO(10, 14105, "42C_BON"); + SET_WHO(16, 14161, "41X2_CRO"); + SET_WHO(10, 54106, "43C_CON"); + SET_WHO(10, 54106, "43C_DUR"); + SET_WHO(9, 54091, "43B1_SEI"); + SET_WHO(9, 14091, "43B1_SEI"); + if (currentGameTime() >= 4) { + SET_WHO(9, 54091, "4_MAI"); + SET_WHO(9, 14091, "4_MAI"); + } + } else if (_currentLevel == 5) { + SET_WHO(27, 15270, "52A4_LAC"); + SET_WHO(9, 15090, "53N_BON"); + SET_WHO(13, 55130, "51M_MAN"); + SET_WHO(13, 55131, "51M_MAN"); + SET_WHO(14, 55140, "52L_LOU"); + SET_WHO(14, 55140, "52L_PRI"); + SET_WHO(14, 15142, "52L_BOU"); + SET_WHO(13, 13130, "53M_SUI"); + if (currentGameTime() >= 4) { + SET_WHO(9, 15090, "54I_BON"); + } + } else if (_currentLevel == 6) { + SET_WHO(9, 16090, "61_LEN"); + SET_WHO(19, 16190, "61_DUC"); + SET_WHO(14, 16140, "61_BON"); + if (_gameVariables[GameVariables::kDiscussedLabyrOrder] == 1) { + SET_WHO(19, 16190, "62_DUC"); + } + } +#undef SET_WHO +} + +void CryOmni3DEngine_Versailles::initDocPeopleRecord() { + _docPeopleRecord.clear(); +#define SET_INFO(actionId, record) _docPeopleRecord[actionId] = record + SET_INFO(22501, "VC25"); + SET_INFO(22401, "VC19"); + SET_INFO(22402, "VC24"); + SET_INFO(22403, "VC24"); + SET_INFO(22404, "VC24"); + SET_INFO(22405, "VC24"); + SET_INFO(22406, "VC24"); + SET_INFO(22407, "VC24"); + SET_INFO(22408, "VC24"); + SET_INFO(21201, "VC25"); + SET_INFO(21202, "VS12"); + SET_INFO(21203, "VA13"); + SET_INFO(21011, "VC13"); + SET_INFO(21012, "VC11"); + SET_INFO(21013, "VC10"); + SET_INFO(21014, "VC18"); + SET_INFO(22901, "VC25"); + SET_INFO(21015, "VC25"); + SET_INFO(22101, "VC18"); + SET_INFO(22903, "VC12"); + SET_INFO(22902, "VC10"); + SET_INFO(22131, "VC16"); + SET_INFO(22111, "VC18"); + SET_INFO(21301, "VA12"); + SET_INFO(22121, "VA12"); + SET_INFO(22103, "VC20"); + SET_INFO(22102, "VC15"); + SET_INFO(23100, "VC23"); + SET_INFO(23101, "VC23"); + SET_INFO(23130, "VC23"); + SET_INFO(23060, "VC23"); + SET_INFO(23150, "VC23"); + SET_INFO(23220, "VA11"); + SET_INFO(23131, "VC11"); + SET_INFO(23241, "VA13"); + SET_INFO(23151, "VR12"); + SET_INFO(23030, "VC18"); + SET_INFO(23040, "VR11"); + SET_INFO(23041, "VR13"); + SET_INFO(23240, "VC15"); + SET_INFO(24104, "VC25"); + SET_INFO(24105, "VC18"); + SET_INFO(24106, "VC12"); + SET_INFO(24107, "VC19"); + SET_INFO(24102, "VC21"); + SET_INFO(24103, "VC21"); + SET_INFO(24081, "VC21"); + SET_INFO(24101, "VC24"); + SET_INFO(24092, "VC14"); + SET_INFO(24091, "VR13"); + SET_INFO(24161, "VR12"); + SET_INFO(25270, "VC26"); + SET_INFO(25261, "VC26"); + //SET_INFO(25260, nullptr); // Don't put empty records + SET_INFO(25130, "VA12"); + SET_INFO(25131, "VS12"); + SET_INFO(25060, "VC23"); + SET_INFO(25061, "VC22"); + SET_INFO(25160, "VC23"); + SET_INFO(25140, "VR11"); + SET_INFO(25141, "VC16"); + SET_INFO(25142, "VC20"); + SET_INFO(25143, "VC15"); + SET_INFO(25145, "VC17"); + SET_INFO(25090, "VC18"); + SET_INFO(26190, "VC13"); + SET_INFO(24161, "VR12"); + SET_INFO(26090, "VS13"); + SET_INFO(26140, "VC18"); + SET_INFO(27111, "VC21"); +#undef SET_INFO +} + +} // End of namespace Versailles +} // End of namespace CryOmni3D diff --git a/engines/cryomni3d/versailles/dialogs.cpp b/engines/cryomni3d/versailles/dialogs.cpp new file mode 100644 index 0000000000..ff5a9ff38b --- /dev/null +++ b/engines/cryomni3d/versailles/dialogs.cpp @@ -0,0 +1,318 @@ +/* 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 "cryomni3d/versailles/engine.h" + +namespace CryOmni3D { +namespace Versailles { + +bool CryOmni3DEngine_Versailles::preprocessDialog(const Common::String &sequence) { + if (_inventory.inInventoryByNameId(96) && _inventory.inInventoryByNameId(98)) { + _dialogsMan["{JOUEUR-TROUVE-TITRE-ET-PAMPHLET}"] = 'Y'; + } + if (_inventory.inInventoryByNameId(126)) { + _dialogsMan["{JOUEUR_POSSEDE_EPIGRAPHE}"] = 'Y'; + } + + if (_currentLevel == 1 && _currentPlaceId == 3) { + playInGameVideo("11D_LEB1"); + } + + _dialogsMan["{JOUEUR-VU-MEMORANDUM-DANS-LUSTRE-DU-SALON-DE-LA-GUERRE}"] = 'N'; + if (_currentLevel == 5 && _gameVariables[GameVariables::kSeenMemorandum] && + !_inventory.inInventoryByNameId(140)) { + _dialogsMan["{JOUEUR-VU-MEMORANDUM-DANS-LUSTRE-DU-SALON-DE-LA-GUERRE}"] = 'Y'; + } + + if (_currentLevel == 1 && _currentPlaceId == 1 && currentGameTime() == 3 && + sequence.hasPrefix("13F_HUI") && _gameVariables[GameVariables::kWarnedIncomplete] == 0 && + _dialogsMan["{JOUEUR-TROUVE-TITRE-ET-PAMPHLET}"] == 'Y' && + (!_inventory.inInventoryByNameId(96) || !_inventory.inInventoryByNameId(98))) { + displayMessageBoxWarp(18); + _gameVariables[GameVariables::kWarnedIncomplete] = 1; + return 0; + } + if (_currentLevel == 2 && _currentPlaceId == 11 && currentGameTime() == 4 && + sequence.hasPrefix("24Z_BON") && _gameVariables[GameVariables::kWarnedIncomplete] == 0 && + _dialogsMan["{JOUEUR-MONTRE-TITRE-FABLE-APPARU-SUR-ESQUISSE}"] == 'Y' && + (!_inventory.inInventoryByNameId(101) || !_inventory.inInventoryByNameId(103))) { + displayMessageBoxWarp(18); + _gameVariables[GameVariables::kWarnedIncomplete] = 1; + return 0; + } + if (_currentLevel == 3 && _currentPlaceId == 10 && currentGameTime() == 3 && + sequence.hasPrefix("31O_SUIA") && _gameVariables[GameVariables::kWarnedIncomplete] == 0 && + _dialogsMan["CROISSY-ACCEPTE-TEXTE"] == 'Y' && + (!_inventory.inInventoryByNameId(121) || !_inventory.inInventoryByNameId(119) || + !_inventory.inInventoryByNameId(115) || _gameVariables[GameVariables::kGotMedaillesSolution] == 0)) { + displayMessageBoxWarp(18); + _gameVariables[GameVariables::kWarnedIncomplete] = 1; + return 0; + } + if (_currentLevel == 4 && _currentPlaceId == 10 && currentGameTime() == 3 && + sequence.hasPrefix("42C_BON") && _gameVariables[GameVariables::kWarnedIncomplete] == 0 && + _dialogsMan["{JOUEUR-MONTRE-PAMPHLET-RELIGION}"] == 'Y' && + (!_inventory.inInventoryByNameId(127) || _gameVariables[GameVariables::kUsedPlanVauban1] == 0 || + _gameVariables[GameVariables::kUsedPlanVauban2] == 0)) { + displayMessageBoxWarp(18); + _gameVariables[GameVariables::kWarnedIncomplete] = 1; + return 0; + } + if (_currentLevel == 5 && _currentPlaceId == 10 && currentGameTime() == 3 && + sequence.hasPrefix("42C_BON") && _gameVariables[GameVariables::kWarnedIncomplete] == 0 && + _dialogsMan["{JOUEUR-MONTRE-PAMPHLET-RELIGION}"] == 'Y' && + (!_inventory.inInventoryByNameId(127) || _gameVariables[GameVariables::kUsedPlanVauban1] == 0 || + _gameVariables[GameVariables::kUsedPlanVauban2] == 0)) { + displayMessageBoxWarp(18); + _gameVariables[GameVariables::kWarnedIncomplete] = 1; + return 0; + } + if (_currentLevel == 6 && _currentPlaceId == 14 && currentGameTime() == 2 && + sequence.hasPrefix("61_BON") && _gameVariables[GameVariables::kWarnedIncomplete] == 0) { + displayMessageBoxWarp(19); + _gameVariables[GameVariables::kWarnedIncomplete] = 1; + return 0; + } + return 1; +} + +void CryOmni3DEngine_Versailles::postprocessDialog(const Common::String &sequence) { + if (_currentLevel == 1) { + if (_dialogsMan["{LEVEL1_FINI}"] == 'Y') { + playTransitionEndLevel(1); + } + } else if (_currentLevel == 2) { + _dialogsMan["{JOUEUR-PRESENTE-FAUX-CROQUIS}"] = 'N'; + _dialogsMan["{JOUEUR-PRESENTE-FAUX-CROQUIS2}"] = 'N'; + _dialogsMan["{JOUEUR-PRESENTE-FAUX-CROQUIS3}"] = 'N'; + _dialogsMan["{LE JOUEUR-PRESENTE-AUTRES-ESQUISSES-OU-ESQUISSE-NON-TRIEES}"] = 'N'; + _dialogsMan["{LE JOUEUR-PRESENTE-ESQUISSES-TRIEES}"] = 'N'; + _dialogsMan["{JOUEUR-MONTRE-TITRE-FABLE-APPARU-SUR-ESQUISSE}"] = 'N'; + _dialogsMan["{JOUEUR-MONTRE-ESQUISSE-DETRUITE}"] = 'N'; + if (_dialogsMan["{LEVEL2_FINI}"] == 'Y') { + playTransitionEndLevel(2); + } + } else if (_currentLevel == 3) { + if (currentGameTime() == 1 && _dialogsMan["LULLY-DONNE-MISSION1-JOUEUR"] == 'Y') { + setGameTime(2, 3); + } + if (!_gameVariables[GameVariables::kGotMedaillesSolution] && + _dialogsMan["MONSIEUR-DONNE-SOLUTION-MEDAILLES"] == 'Y') { + playInGameVideo("32M_MR2"); + _gameVariables[GameVariables::kGotMedaillesSolution] = 1; + } + if (!_gameVariables[GameVariables::kCollectePartition] && + _dialogsMan["LULLY-DIT-CHAT-PENDU-JOUEUR"] == 'Y') { + _gameVariables[GameVariables::kCollectePartition] = 1; + collectObject(118); + setGameTime(3, 3); + } + if (currentGameTime() == 1 && _dialogsMan["CROISSY-ACCEPTE-TEXTE"] == 'Y') { + setGameTime(4, 3); + } + if (_dialogsMan["{LEVEL3_FINI}"] == 'Y') { + playTransitionEndLevel(3); + } + if (sequence == "32M_MR" && _dialogsMan["MONSIEUR-DONNE-SOLUTION-MEDAILLES"] == 'Y') { + _dialogsMan["{JOUEUR-MONTRE-MEDAILLES-MONSIEUR}"] = 'Y'; + } + _dialogsMan["{JOUEUR-MONTRE-PAMPHLET-ARCHITECTURE}"] = 'N'; + _dialogsMan["{JOUEUR-MONTRE-EPIGRAPHE-MEDAILLES}"] = 'N'; + _dialogsMan["{JOUEUR-MONTRE-TOUT-AUTRE-CHOSE}"] = 'N'; + } else if (_currentLevel == 4) { + if (_dialogsMan["{LEVEL4_FINI}"] == 'Y') { + playTransitionEndLevel(4); + } + } else if (_currentLevel == 5) { + if (sequence == "54I_BON" && _dialogsMan["BONTEMPS-DIT-PROMENADE"] == 'Y') { + collectObject(141); + playTransitionEndLevel(5); + } + if (sequence == "52A4_LAC" && _gameVariables[GameVariables::kStatePamphletReligion] != 3 && + _dialogsMan["LACHAIZE-DIT-REFORME"] == 'Y' && _dialogsMan["LACHAIZE-DIT-DRAGONNADES"] == 'Y' && + _dialogsMan["LACHAIZE-TROUVE-ECROUELLES"] == 'Y') { + _inventory.removeByNameId(125); + _gameVariables[GameVariables::kStatePamphletReligion] = 3; + collectObject(125); + _inventory.setSelectedObject(nullptr); + } + } +} + +void CryOmni3DEngine_Versailles::updateGameTimeDialVariables() { + _dialogsMan["{CURRENT_GAME_TIME1}"] = 'N'; + _dialogsMan["{CURRENT_GAME_TIME2}"] = 'N'; + _dialogsMan["{CURRENT_GAME_TIME3}"] = 'N'; + _dialogsMan["{CURRENT_GAME_TIME4}"] = 'N'; + _dialogsMan["{CURRENT_GAME_TIME5}"] = 'N'; + switch (currentGameTime()) { + case 1: + _dialogsMan["{CURRENT_GAME_TIME1}"] = 'Y'; + break; + case 2: + _dialogsMan["{CURRENT_GAME_TIME2}"] = 'Y'; + break; + case 3: + _dialogsMan["{CURRENT_GAME_TIME3}"] = 'Y'; + break; + case 4: + _dialogsMan["{CURRENT_GAME_TIME4}"] = 'Y'; + break; + case 5: + _dialogsMan["{CURRENT_GAME_TIME5}"] = 'Y'; + break; + default: + error("Invalid current game time %d", currentGameTime()); + } +} + +void CryOmni3DEngine_Versailles::setupDialogShows() { + _dialogsMan.registerShowCallback("(BONTEMPS-MONTRE-TROISIEME-TITRE-DE-FABLE)", + &CryOmni3DEngine_Versailles::dialogShowBontempsShowThird); + _dialogsMan.registerShowCallback("(HUISSIER DONNE PAMPHLET SUR LA FAMILLE ROYALE)", + &CryOmni3DEngine_Versailles::dialogShowHuissierShowPamphlet); + _dialogsMan.registerShowCallback("(MONSEIGNEUR TRIE LES ESQUISSES)", + &CryOmni3DEngine_Versailles::dialogShowMonseigneurSorts); + _dialogsMan.registerShowCallback("(ANIMATION LE BRUN REGARDE LES ESQUISSES)", + &CryOmni3DEngine_Versailles::dialogShowLeBrunWatches); + _dialogsMan.registerShowCallback("(OUVERTURE DES PORTES)", + &CryOmni3DEngine_Versailles::dialogShowDoorsOpen); + _dialogsMan.registerShowCallback("(GARDE SUISSE DONNE CLEF PETITE PORTE)", + &CryOmni3DEngine_Versailles::dialogShowSwissGuardGives); + _dialogsMan.registerShowCallback("(LULLY CORRIGE LA PARTITION.)", + &CryOmni3DEngine_Versailles::dialogShowLullyCorrects); + _dialogsMan.registerShowCallback("(BONTEMPS DONNE AUTORISATION)", + &CryOmni3DEngine_Versailles::dialogShowBontempsGivesAuth); + _dialogsMan.registerShowCallback("(CROISSY PART)", + &CryOmni3DEngine_Versailles::dialogShowCroissyLeave); + _dialogsMan.registerShowCallback("(MAINTENON-DONNE-PAMPHLET-RELIGION)", + &CryOmni3DEngine_Versailles::dialogShowMaintenonGives); + _dialogsMan.registerShowCallback("(LA CHAIZE REND TEXTE)", + &CryOmni3DEngine_Versailles::dialogShowLaChaizeGivesBack); + _dialogsMan.registerShowCallback("(LA CHAIZE " "\x83" "CRIT DRAGONNADES)", + &CryOmni3DEngine_Versailles::dialogShowLaChaizeWrites); + _dialogsMan.registerShowCallback("(LACHAIZE-DONNE-PAMPHLET-JOUEUR)", + &CryOmni3DEngine_Versailles::dialogShowLaChaizeGivesPamphlet); + _dialogsMan.registerShowCallback("(BONTEMPS-DONNE-CLEF-DES-COMBLES)", + &CryOmni3DEngine_Versailles::dialogShowBontempsGivesKey); + _dialogsMan.registerShowCallback("(LE DUC DU MAINE S'EN VA)", + &CryOmni3DEngine_Versailles::dialogShowDuMaineLeaves); + _dialogsMan.registerShowCallback("(SC" "\xe9" "NE DE TRANSITION)", + &CryOmni3DEngine_Versailles::dialogShowTransitionScene); + _dialogsMan.registerShowCallback("(FIN DU JEU)", &CryOmni3DEngine_Versailles::dialogShowEndOfGame); + _dialogsMan.registerShowCallback("(LEBRUN-DONNE-FAUSSES-ESQUISSES)", + &CryOmni3DEngine_Versailles::dialogShowLeBrunGives); + _dialogsMan.registerShowCallback("(LEBRUN_S_EN_VA)", + &CryOmni3DEngine_Versailles::dialogShowLeBrunLeave); +} + +void CryOmni3DEngine_Versailles::dialogShowBontempsShowThird() { + // Nothing to do +} + +void CryOmni3DEngine_Versailles::dialogShowHuissierShowPamphlet() { + collectObject(101); + _inventory.setSelectedObject(nullptr); +} + +void CryOmni3DEngine_Versailles::dialogShowMonseigneurSorts() { + _inventory.removeByNameId(105); + collectObject(106); + _gameVariables[GameVariables::kEsquissePainted] = 2; + _inventory.setSelectedObject(nullptr); + setGameTime(3, 2); + _dialogsMan["MONSEIGNEUR-ATTEND-ESQUISSES"] = 'N'; +} + +void CryOmni3DEngine_Versailles::dialogShowLeBrunWatches() { + // Nothing to do +} + +void CryOmni3DEngine_Versailles::dialogShowDoorsOpen() { + // Nothing to do +} + +void CryOmni3DEngine_Versailles::dialogShowSwissGuardGives() { + collectObject(123); + _dialogsMan["{JOUEUR-POSSEDE-CLE}"] = 'Y'; +} + +void CryOmni3DEngine_Versailles::dialogShowLullyCorrects() { + // Nothing to do +} + +void CryOmni3DEngine_Versailles::dialogShowBontempsGivesAuth() { + collectObject(120); +} + +void CryOmni3DEngine_Versailles::dialogShowCroissyLeave() { + // Nothing to do +} + +void CryOmni3DEngine_Versailles::dialogShowMaintenonGives() { + collectObject(125); + _inventory.setSelectedObject(nullptr); +} + +void CryOmni3DEngine_Versailles::dialogShowLaChaizeGivesBack() { + // Nothing to do +} + +void CryOmni3DEngine_Versailles::dialogShowLaChaizeWrites() { + // Nothing to do +} + +void CryOmni3DEngine_Versailles::dialogShowLaChaizeGivesPamphlet() { + // Nothing to do +} + +void CryOmni3DEngine_Versailles::dialogShowBontempsGivesKey() { + collectObject(140); + _inventory.setSelectedObject(nullptr); +} + +void CryOmni3DEngine_Versailles::dialogShowDuMaineLeaves() { + playInGameVideo("62S_DUC1"); + _inventory.removeByNameId(144); + _inventory.setSelectedObject(nullptr); + setPlaceState(19, 1); +} + +void CryOmni3DEngine_Versailles::dialogShowTransitionScene() { +} + +void CryOmni3DEngine_Versailles::dialogShowEndOfGame() { + playTransitionEndLevel(6); +} + +void CryOmni3DEngine_Versailles::dialogShowLeBrunGives() { + collectObject(107); + _inventory.setSelectedObject(nullptr); +} + +void CryOmni3DEngine_Versailles::dialogShowLeBrunLeave() { + playInGameVideo("11D_LEB3"); + setGameTime(2, 1); +} + +} // End of namespace Versailles +} // End of namespace CryOmni3D diff --git a/engines/cryomni3d/versailles/dialogs_manager.cpp b/engines/cryomni3d/versailles/dialogs_manager.cpp new file mode 100644 index 0000000000..4180a65d98 --- /dev/null +++ b/engines/cryomni3d/versailles/dialogs_manager.cpp @@ -0,0 +1,379 @@ +/* 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 "audio/decoders/wave.h" +#include "common/file.h" +#include "common/system.h" + +#include "cryomni3d/video/hnm_decoder.h" + +#include "cryomni3d/versailles/dialogs_manager.h" +#include "cryomni3d/versailles/engine.h" + +namespace CryOmni3D { +namespace Versailles { + +bool Versailles_DialogsManager::play(const Common::String &sequence) { + // Prepare with specific Versailles stuff + if (!_engine->preprocessDialog(sequence)) { + return false; + } + + _engine->musicSetQuiet(true); + + _engine->setCursor(181); + // No need to adjust hide cursor counter, there isn't any in ScummVM + bool cursorWasVisible = g_system->showMouse(true); + + bool slowStop = false; + bool didSth = DialogsManager::play(sequence, slowStop); + + g_system->showMouse(cursorWasVisible); + + if (didSth && slowStop) { + if (_engine->showSubtitles()) { + bool skip = false; + unsigned int end = g_system->getMillis() + 2000; + while (!g_engine->shouldQuit() && g_system->getMillis() < end && !skip) { + g_system->updateScreen(); + if (_engine->pollEvents() && + (_engine->checkKeysPressed(1, Common::KEYCODE_SPACE) || + _engine->getCurrentMouseButton() == 1)) { + skip = true; + } + } + } + } + _engine->postprocessDialog(sequence); + + _engine->musicSetQuiet(false); + + _lastImage.free(); + + _engine->waitMouseRelease(); + return didSth; +} + +void Versailles_DialogsManager::executeShow(const Common::String &show) { + Common::HashMap<Common::String, ShowCallback>::iterator showIt = _shows.find(show); + + if (showIt == _shows.end()) { + error("Missing show %s", show.c_str()); + } + + _lastImage.free(); + + ShowCallback cb = showIt->_value; + (_engine->*cb)(); +} + +void Versailles_DialogsManager::playDialog(const Common::String &video, const Common::String &sound, + const Common::String &text, const SubtitlesSettings &settings) { + Common::String videoFName(video); + Common::String soundFName(sound); + + videoFName += ".hnm"; + // Don't look for HNS file here + + while (soundFName.size() < 8) { + soundFName += '_'; + } + soundFName += ".wav"; + + Video::HNMDecoder *videoDecoder = new Video::HNMDecoder(true); + + if (!videoDecoder->loadFile(videoFName)) { + warning("Failed to open movie file %s/%s", video.c_str(), videoFName.c_str()); + delete videoDecoder; + return; + } + + Common::File *audioFile = new Common::File(); + if (!audioFile->open(soundFName)) { + warning("Failed to open sound file %s/%s", sound.c_str(), soundFName.c_str()); + delete videoDecoder; + delete audioFile; + return; + } + + Audio::SeekableAudioStream *audioDecoder = Audio::makeWAVStream(audioFile, DisposeAfterUse::YES); + // We lost ownership of the audioFile just set it to nullptr and don't use it + audioFile = nullptr; + + if (!audioDecoder) { + delete videoDecoder; + return; + } + + g_system->showMouse(false); + + uint16 width = videoDecoder->getWidth(); + uint16 height = videoDecoder->getHeight(); + + // Preload first frame to draw subtitles from it + const Graphics::Surface *firstFrame = videoDecoder->decodeNextFrame(); + assert(firstFrame != nullptr); + + if (videoDecoder->hasDirtyPalette()) { + const byte *palette = videoDecoder->getPalette(); + _engine->setupPalette(palette, 0, 256); + } + + FontManager &fontManager = _engine->_fontManager; + _lastImage.create(firstFrame->w, firstFrame->h, firstFrame->format); + _lastImage.blitFrom(*firstFrame); + + fontManager.setCurrentFont(7); + fontManager.setTransparentBackground(true); + fontManager.setForeColor(241); + fontManager.setLineHeight(22); + fontManager.setSpaceWidth(2); + fontManager.setCharSpacing(1); + + if (_engine->showSubtitles()) { + Common::Rect block = settings.textRect; + + unsigned int lines = fontManager.getLinesCount(text, block.width() - 8); + if (lines == 0) { + lines = 5; + } + unsigned int blockHeight = fontManager.lineHeight() * lines + 6; + block.setHeight(blockHeight); + + if (block.bottom >= 480) { + block.bottom = 470; + warning("Dialog text is really too long"); + } + + // Make only the block area translucent inplace + Graphics::Surface blockSurface = _lastImage.getSubArea(block); + _engine->makeTranslucent(blockSurface, blockSurface); + + fontManager.setSurface(&_lastImage); + block.grow(-4); + fontManager.setupBlock(block); + fontManager.displayBlockText(text); + } + + g_system->copyRectToScreen(_lastImage.getPixels(), _lastImage.pitch, 0, 0, width, height); + g_system->updateScreen(); + + const Common::Rect &drawRect = settings.drawRect; + + if (audioDecoder->getLength() == 0) { + // Empty wave file + delete audioDecoder; + + unsigned int duration = 100 * text.size(); + if (duration < 1000) { + duration = 1000; + } + + bool skipWait = false; + unsigned int end = g_system->getMillis() + duration; + while (!g_engine->shouldQuit() && g_system->getMillis() < end && !skipWait) { + if (_engine->pollEvents() && _engine->checkKeysPressed(1, Common::KEYCODE_SPACE)) { + skipWait = true; + } + } + } else { + // Let start the show! + videoDecoder->start(); + + Audio::SoundHandle audioHandle; + _engine->_mixer->playStream(Audio::Mixer::kSpeechSoundType, &audioHandle, audioDecoder); + // We lost ownership of the audioDecoder just set it to nullptr and don't use it + audioDecoder = nullptr; + + bool skipVideo = false; + while (!g_engine->shouldQuit() && _engine->_mixer->isSoundHandleActive(audioHandle) && !skipVideo) { + if (_engine->pollEvents() && _engine->checkKeysPressed(1, Common::KEYCODE_SPACE)) { + skipVideo = true; + } + + if (videoDecoder->needsUpdate()) { + const Graphics::Surface *frame = videoDecoder->decodeNextFrame(); + + if (frame) { + if (videoDecoder->hasDirtyPalette()) { + const byte *palette = videoDecoder->getPalette(); + _engine->setupPalette(palette, 0, 256); + } + + // Only refresh the moving part of the animation + const Graphics::Surface subFrame = frame->getSubArea(drawRect); + g_system->copyRectToScreen(subFrame.getPixels(), subFrame.pitch, drawRect.left, drawRect.top, + subFrame.w, subFrame.h); + } + } + g_system->updateScreen(); + } + _engine->_mixer->stopHandle(audioHandle); + } + + // It's intentional that _lastImage is set with the first video image + + delete videoDecoder; + g_system->showMouse(true); +} + +void Versailles_DialogsManager::displayMessage(const Common::String &text) { + _engine->displayMessageBoxWarp(text); +} + +unsigned int Versailles_DialogsManager::askPlayerQuestions(const Common::String &video, + const Common::StringArray &questions) { + if (_lastImage.empty()) { + loadFrame(video); + } + + if (questions.size() == 0 || questions.size() > 5) { + return -1; + } + + FontManager &fontManager = _engine->_fontManager; + fontManager.setCurrentFont(7); + fontManager.setTransparentBackground(true); + fontManager.setLineHeight(18); + fontManager.setSpaceWidth(2); + fontManager.setSurface(&_lastImage); + + int16 tops[5]; + int16 bottoms[5]; + int16 currentHeight = 0; + unsigned int questionId = 0; + for (Common::StringArray::const_iterator it = questions.begin(); it != questions.end(); + it++, questionId++) { + tops[questionId] = currentHeight; + unsigned int lines = fontManager.getLinesCount(*it, 598); + if (lines == 0) { + lines = 1; + } + currentHeight += 18 * lines; + bottoms[questionId] = currentHeight; + } + + int offsetY = 480 - (bottoms[questions.size() - 1] - tops[0]); + if (offsetY > 402) { + offsetY = 402; + } else if (offsetY < 2) { + offsetY = 2; + } + + for (questionId = 0; questionId < questions.size(); questionId++) { + tops[questionId] += offsetY; + bottoms[questionId] += offsetY; + } + + _engine->setCursor(181); + Graphics::Surface alphaSurface = _lastImage.getSubArea(Common::Rect(0, offsetY - 2, 640, 480)); + _engine->makeTranslucent(alphaSurface, alphaSurface); + + bool finished = false; + bool update = true; + unsigned int selectedQuestion = -1; + while (!finished) { + if (update) { + update = false; + questionId = 0; + for (Common::StringArray::const_iterator it = questions.begin(); it != questions.end(); + it++, questionId++) { + fontManager.setForeColor(selectedQuestion == questionId ? 241 : 245); + fontManager.setupBlock(Common::Rect(10, tops[questionId], 608, bottoms[questionId])); + fontManager.displayBlockText(*it); + } + g_system->copyRectToScreen(_lastImage.getPixels(), _lastImage.pitch, 0, 0, _lastImage.w, + _lastImage.h); + } + g_system->updateScreen(); + + if (_engine->pollEvents()) { + _engine->clearKeys(); + if (g_engine->shouldQuit()) { + finished = true; + selectedQuestion = -1; + break; + } + Common::Point mousePos = _engine->getMousePos(); + if (_engine->getDragStatus() == kDragStatus_Finished && selectedQuestion != -1u) { + finished = true; + } else if (mousePos.x >= 608 || mousePos.y < offsetY) { + if (selectedQuestion != -1u) { + selectedQuestion = -1; + update = true; + } + } else { + for (questionId = 0; questionId < questions.size(); questionId++) { + if (mousePos.y > tops[questionId] && mousePos.y < bottoms[questionId]) { + break; + } + } + if (questionId < questions.size()) { + if (selectedQuestion != questionId) { + selectedQuestion = questionId; + update = true; + } + } else { + selectedQuestion = -1; + update = true; + } + } + } + } + + return selectedQuestion; +} + +void Versailles_DialogsManager::loadFrame(const Common::String &video) { + Common::String videoFName(video); + int lastDotPos = videoFName.size() - 1; + for (; lastDotPos >= 0; --lastDotPos) { + if (videoFName[lastDotPos] == '.') { + break; + } + } + if (lastDotPos > -1) { + videoFName.erase(lastDotPos); + videoFName += ".hnm"; + } + + Video::HNMDecoder *videoDecoder = new Video::HNMDecoder(); + + if (!videoDecoder->loadFile(videoFName)) { + warning("Failed to open movie file %s/%s", video.c_str(), videoFName.c_str()); + delete videoDecoder; + return; + } + + if (videoDecoder->hasDirtyPalette()) { + const byte *palette = videoDecoder->getPalette(); + _engine->setupPalette(palette, 0, 256); + } + + // Preload first frame to draw subtitles from it + const Graphics::Surface *firstFrame = videoDecoder->decodeNextFrame(); + _lastImage.create(firstFrame->w, firstFrame->h, firstFrame->format); + _lastImage.blitFrom(*firstFrame); +} + +} // End of namespace Versailles +} // End of namespace CryOmni3D diff --git a/engines/cryomni3d/versailles/dialogs_manager.h b/engines/cryomni3d/versailles/dialogs_manager.h new file mode 100644 index 0000000000..3c5028ff2f --- /dev/null +++ b/engines/cryomni3d/versailles/dialogs_manager.h @@ -0,0 +1,68 @@ +/* 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 CRYOMNI3D_VERSAILLES_DIALOGS_MANAGER_H +#define CRYOMNI3D_VERSAILLES_DIALOGS_MANAGER_H + +#include "common/hashmap.h" +#include "common/hash-str.h" +#include "graphics/managed_surface.h" + +#include "cryomni3d/dialogs_manager.h" +#include "cryomni3d/font_manager.h" + +namespace CryOmni3D { +namespace Versailles { + +class CryOmni3DEngine_Versailles; + +class Versailles_DialogsManager : public DialogsManager { +public: + Versailles_DialogsManager(CryOmni3DEngine_Versailles *engine) : _engine(engine) { } + + // This overload will hide the base one and this is what we want + bool play(const Common::String &sequence); + + typedef void (CryOmni3DEngine_Versailles::*ShowCallback)(); + void registerShowCallback(const Common::String &showName, ShowCallback callback) { _shows[showName] = callback; } + +protected: + void executeShow(const Common::String &show) override; + void playDialog(const Common::String &video, const Common::String &sound, + const Common::String &text, const SubtitlesSettings &settings) override; + void displayMessage(const Common::String &text) override; + unsigned int askPlayerQuestions(const Common::String &video, + const Common::StringArray &questions) override; + +private: + CryOmni3DEngine_Versailles *_engine; + Common::HashMap<Common::String, ShowCallback> _shows; + + void loadFrame(const Common::String &video); + + Graphics::ManagedSurface _lastImage; +}; + +} // End of namespace Versailles +} // End of namespace CryOmni3D + +#endif diff --git a/engines/cryomni3d/versailles/documentation.cpp b/engines/cryomni3d/versailles/documentation.cpp new file mode 100644 index 0000000000..bbc924290c --- /dev/null +++ b/engines/cryomni3d/versailles/documentation.cpp @@ -0,0 +1,2070 @@ +/* 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 "common/file.h" +#include "common/system.h" +#include "graphics/managed_surface.h" +#include "image/bmp.h" + +#include "cryomni3d/font_manager.h" +#include "cryomni3d/mouse_boxes.h" +#include "cryomni3d/sprites.h" + +#include "cryomni3d/versailles/documentation.h" +#include "cryomni3d/versailles/engine.h" + +namespace CryOmni3D { +namespace Versailles { + +const char *Versailles_Documentation::kAllDocsFile = "tous_doc.txt"; +const char *Versailles_Documentation::kLinksDocsFile = "lien_doc.txt"; +const Versailles_Documentation::TimelineEntry Versailles_Documentation::kTimelineEntries[] = { + { "1638", 340, 15 }, + { "1643", 470, 30 }, + { "1648", 380, 45 }, + { "1649", 500, 60 }, + { "1650", 420, 75 }, + { "1651", 520, 90 }, + { "1652", 450, 105 }, + { "1654", 540, 120 }, + { "1658", 470, 135 }, + { "1659", 550, 150 }, + { "1660", 480, 165 }, + { "1661", 560, 180 }, + { "1662", 490, 195 }, + { "1663", 560, 210 }, + { "1664", 490, 225 }, + { "1665", 560, 240 }, + { "1666", 490, 255 }, + { "1667", 560, 270 }, + { "1668", 490, 285 }, + { "1670", 550, 300 }, + { "1671", 480, 315 }, + { "1672", 540, 330 }, + { "1673", 470, 345 }, + { "1674", 530, 360 }, + { "1675", 460, 375 }, + { "1678", 510, 390 }, + { "1680", 430, 405 }, + { "1681", 490, 420 }, + { "1682", 400, 435 }, + { "1683", 450, 450 }, + { "1684", 156, 444 }, + { "1685", 81, 439 }, + { "1686", 140, 422 }, + { "1687", 73, 413 }, + { "1689", 128, 401 }, + { "1697", 62, 389 }, + { "1700", 121, 378 }, + { "1701", 62, 366 }, + { "1702", 121, 355 }, + { "1709", 62, 344 }, + { "1710", 121, 333 }, + { "1712", 67, 322 }, + { "1715", 128, 311 }, +}; + +void Versailles_Documentation::init(const Sprites *sprites, FontManager *fontManager, + const Common::StringArray *messages, CryOmni3DEngine *engine) { + _sprites = sprites; + _fontManager = fontManager; + _messages = messages; + _engine = engine; + + // Build list of records + Common::File allDocsFile; + + if (!allDocsFile.open(kAllDocsFile)) { + error("Can't open %s", kAllDocsFile); + } + + unsigned int allDocsSize = allDocsFile.size(); + char *allDocs = new char[allDocsSize + 1]; + char *end = allDocs + allDocsSize; + allDocsFile.read(allDocs, allDocsSize); + allDocs[allDocsSize] = '\0'; + allDocsFile.close(); + + const char *patterns[] = { "FICH=", nullptr }; + RecordInfo record; + + char *currentPos = allDocs; + char *lastRecordName; + bool first = true; + + while (true) { + currentPos = getDocPartAddress(currentPos, end, patterns); + if (!currentPos) { + break; + } + currentPos -= 5; + if (first) { + record.position = currentPos - allDocs; + record.id = 0; + + lastRecordName = currentPos + 5; + + first = false; + } else { + record.size = (currentPos - allDocs) - record.position; + //debug("Found record %s", lastRecordName); + _records[lastRecordName] = record; + _recordsOrdered.push_back(lastRecordName); + + record.id++; + record.position = currentPos - allDocs; + + lastRecordName = currentPos + 5; + } + // Next line + currentPos += strlen(currentPos) + 1; + } + record.size = allDocsSize - record.position; + _records[lastRecordName] = record; + _recordsOrdered.push_back(lastRecordName); + + delete[] allDocs; +} + +void Versailles_Documentation::handleDocArea() { + g_system->showMouse(false); + + // Load all links lazily and free them at the end to not waste memory + // Maybe it's not really useful + getLinks("ALL00", _allLinks); + + bool end = false; + while (!end) { + Common::String selectedRecord = docAreaHandleSummary(); + if (selectedRecord == "") { + end = true; + } else if (selectedRecord == "VT00") { + selectedRecord = docAreaHandleTimeline(); + if (selectedRecord != "") { + if (docAreaHandleRecords(selectedRecord) == 2) { + end = true; + } + } + } else { + if (docAreaHandleRecords(selectedRecord) == 2) { + end = true; + } + } + } + + _allLinks.clear(); + + g_system->showMouse(true); +} + +void Versailles_Documentation::handleDocInGame(const Common::String &record) { + _visitTrace.clear(); + _currentRecord = record; + + Graphics::ManagedSurface docSurface; + Common::String nextRecord; + MouseBoxes boxes(3); + + g_system->showMouse(false); + bool end = false; + while (!end) { + inGamePrepareRecord(docSurface, boxes); + unsigned int action = inGameHandleRecord(docSurface, boxes, nextRecord); + switch (action) { + case 0: + // Back + if (!_visitTrace.empty()) { + _currentRecord = _visitTrace.back(); + _visitTrace.pop_back(); + break; + } + // No previous record, like a quit + // fall through + case 1: + // Quit + end = true; + break; + case 2: + // Follow hyperlink keeping trace + _visitTrace.push_back(_currentRecord); + _currentRecord = nextRecord; + break; + default: + error("Invalid case %d when displaying doc record", action); + } + } + g_system->showMouse(true); +} + +Common::String Versailles_Documentation::docAreaHandleSummary() { + struct Category { + const char *record; + const char *bmp; + Common::Point imgPos; + Common::Rect linesPos; + const Common::String *title; + Graphics::Surface highlightedImg; + + Category(const char *record_, const char *bmp_, const Common::Point &imgPos_, + const Common::Rect &linesPos_, const Common::String *title_) : + record(record_), bmp(bmp_), imgPos(imgPos_), linesPos(linesPos_), title(title_) { } + } categories[8] = { + Category( + "VA00", + "VA.bmp", + Common::Point(142, 402), + Common::Rect(174, 372, 284, 372), + &(*_messages)[68]), + Category( + "VR00", + "VR.bmp", + Common::Point(82, 187), + Common::Rect(89, 187, 217, 212), + &(*_messages)[69]), + Category( + "VC00", + "VC.bmp", + Common::Point(176, 105), + Common::Rect(177, 107, 292, 130), + &(*_messages)[70]), + Category( + "VT00", + "VH.bmp", //sic + Common::Point(283, 467), + Common::Rect(311, 451, 466, 451), + &(*_messages)[73]), + Category( + "VV00", + "VV.bmp", + Common::Point(68, 305), + Common::Rect(94, 292, 300, 292), + &(*_messages)[71]), + Category( + "VS00", + "VS.bmp", + Common::Point(321, 70), + Common::Rect(322, 71, 540, 94), + &(*_messages)[72]), + Category( + nullptr, + nullptr, + Common::Point(256, 212), + Common::Rect(), + nullptr), + Category( + nullptr, + nullptr, + Common::Point(600, 450), + Common::Rect(), + nullptr) + }; + Image::BitmapDecoder bmpDecoder; + Common::File file; + + for (unsigned int i = 0; i < ARRAYSIZE(categories); i++) { + if (!categories[i].bmp) { + // No BMP to load + continue; + } + if (!file.open(categories[i].bmp)) { + error("Failed to open BMP file: %s", categories[i].bmp); + } + if (!bmpDecoder.loadStream(file)) { + error("Failed to load BMP file: %s", categories[i].bmp); + } + categories[i].highlightedImg.copyFrom(*bmpDecoder.getSurface()); + bmpDecoder.destroy(); + file.close(); + } + + Image::ImageDecoder *imageDecoder = _engine->loadHLZ("SOM1.HLZ"); + if (!imageDecoder) { + return ""; + } + const Graphics::Surface *bgFrame = imageDecoder->getSurface(); + + Graphics::ManagedSurface docSurface; + docSurface.create(bgFrame->w, bgFrame->h, bgFrame->format); + docSurface.blitFrom(*bgFrame); + + _fontManager->setCurrentFont(0); + _fontManager->setTransparentBackground(true); + _fontManager->setLineHeight(14); + _fontManager->setSpaceWidth(0); + _fontManager->setCharSpacing(1); + _fontManager->setSurface(&docSurface); + + MouseBoxes boxes(8); + boxes.setupBox(0, 104, 335, 177, 408); + boxes.setupBox(1, 46, 122, 119, 195); + boxes.setupBox(2, 140, 40, 213, 113); + boxes.setupBox(3, 247, 402, 320, 475); + boxes.setupBox(4, 32, 240, 105, 313); + boxes.setupBox(5, 285, 5, 358, 78); + // No box for 6 + boxes.setupBox(7, 0, 480 - _sprites->getCursor(225).getHeight(), 640, 480); + + _engine->setupPalette(imageDecoder->getPalette(), imageDecoder->getPaletteStartIndex(), + imageDecoder->getPaletteColorCount()); + + _engine->setCursor(181); + g_system->showMouse(true); + + bool redraw = true; + unsigned int hoveredBox = -1; + unsigned int selectedBox = -1; + + while (selectedBox == -1u) { + if (redraw) { + // Draw without worrying of already modified areas, that's handled when recomputing hoveredBox + for (unsigned int i = 0; i < ARRAYSIZE(categories); i++) { + unsigned int foreColor = 243; + if (i == hoveredBox) { + foreColor = 241; + if (categories[hoveredBox].highlightedImg.getPixels() != nullptr) { + docSurface.transBlitFrom(categories[i].highlightedImg, categories[i].imgPos - Common::Point(36, + 65)); + } + } + _fontManager->setForeColor(foreColor); + if (categories[i].title) { + unsigned int x = categories[i].linesPos.right - _fontManager->getStrWidth(*categories[i].title); + unsigned int y = categories[i].linesPos.bottom - _fontManager->getFontMaxHeight() - 5; + _fontManager->displayStr(x, y, *categories[i].title); + + // Draw line to text + docSurface.vLine(categories[i].linesPos.left, categories[i].linesPos.top, + categories[i].linesPos.bottom, foreColor); + docSurface.hLine(categories[i].linesPos.left, categories[i].linesPos.bottom, + categories[i].linesPos.right - 1, foreColor); // minus 1 because hLine draws inclusive + } + } + docSurface.transBlitFrom(_sprites->getSurface(225), boxes.getBoxOrigin(7), + _sprites->getKeyColor(225)); + + g_system->copyRectToScreen(docSurface.getPixels(), docSurface.pitch, 0, 0, docSurface.w, + docSurface.h); + + redraw = false; + } + g_system->updateScreen(); + + if (_engine->pollEvents()) { + if (!_engine->getCurrentMouseButton()) { + // Don't change highlighted icon when clicking + Common::Point mouse = _engine->getMousePos(); + bool foundBox = false; + for (unsigned int i = 0; i < ARRAYSIZE(categories); i++) { + if (boxes.hitTest(i, mouse)) { + foundBox = true; + if (i != hoveredBox) { + hoveredBox = i; + redraw = true; + } + } + } + if (!foundBox && hoveredBox != -1u) { + if (categories[hoveredBox].highlightedImg.getPixels() != nullptr) { + // Restore original icon + const Common::Point &imgPos = categories[hoveredBox].imgPos; + docSurface.blitFrom(*bgFrame, Common::Rect( + imgPos.x - 36, imgPos.y - 65, imgPos.x + 37, imgPos.y + 8), + Common::Point(imgPos.x - 36, imgPos.y - 65)); + } + hoveredBox = -1; + redraw = true; + } + } + if (_engine->getDragStatus() == kDragStatus_Finished) { + if (hoveredBox != -1u) { + selectedBox = hoveredBox; + } + } + if (_engine->checkKeysPressed(1, Common::KEYCODE_ESCAPE)) { + selectedBox = 7; + } + } + if (g_engine->shouldQuit()) { + selectedBox = 7; + } + } + + g_system->showMouse(false); + + delete imageDecoder; + + if (selectedBox == 7) { + return ""; + } else { + return categories[selectedBox].record; + } +} + +Common::String Versailles_Documentation::docAreaHandleTimeline() { + Image::ImageDecoder *imageDecoder = _engine->loadHLZ("chrono1.HLZ"); + if (!imageDecoder) { + return ""; + } + const Graphics::Surface *bgFrame = imageDecoder->getSurface(); + + Graphics::ManagedSurface docSurface; + docSurface.create(bgFrame->w, bgFrame->h, bgFrame->format); + docSurface.blitFrom(*bgFrame); + + _fontManager->setCurrentFont(1); + _fontManager->setTransparentBackground(true); + _fontManager->setLineHeight(14); + _fontManager->setSpaceWidth(0); + _fontManager->setCharSpacing(1); + _fontManager->setSurface(&docSurface); + + _engine->setupPalette(imageDecoder->getPalette(), imageDecoder->getPaletteStartIndex(), + imageDecoder->getPaletteColorCount()); + + _fontManager->displayStr(78, 10, (*_messages)[73]); + docSurface.hLine(0, 39, 171, 241); // minus 1 because hLine draws inclusive + + _fontManager->setCurrentFont(0); + + MouseBoxes boxes(ARRAYSIZE(kTimelineEntries) + 1); + for (unsigned int box_id = 0; box_id < ARRAYSIZE(kTimelineEntries); box_id++) { + boxes.setupBox(box_id, kTimelineEntries[box_id].x, kTimelineEntries[box_id].y, + kTimelineEntries[box_id].x + 30, kTimelineEntries[box_id].y + 20); + } + const unsigned int leaveBoxId = ARRAYSIZE(kTimelineEntries); + boxes.setupBox(leaveBoxId, 639 - _sprites->getCursor(105).getWidth(), + 479 - _sprites->getCursor(105).getHeight(), 640, 480); + + _engine->setCursor(181); + g_system->showMouse(true); + + bool redraw = true; + unsigned int hoveredBox = -1; + unsigned int selectedBox = -1; + + while (selectedBox == -1u) { + if (redraw) { + // Draw without worrying of already modified areas, that's handled when recomputing hoveredBox + for (unsigned int i = 0; i < ARRAYSIZE(kTimelineEntries); i++) { + _fontManager->setForeColor(i == hoveredBox ? 241 : 243); + _fontManager->displayStr(kTimelineEntries[i].x, kTimelineEntries[i].y, kTimelineEntries[i].year); + } + docSurface.transBlitFrom(_sprites->getSurface(105), boxes.getBoxOrigin(leaveBoxId), + _sprites->getKeyColor(105)); + + g_system->copyRectToScreen(docSurface.getPixels(), docSurface.pitch, 0, 0, + docSurface.w, docSurface.h); + redraw = false; + } + g_system->updateScreen(); + + if (_engine->pollEvents()) { + Common::Point mouse = _engine->getMousePos(); + if (!_engine->getCurrentMouseButton()) { + // Don't change highlighted date when clicking + bool foundBox = false; + for (unsigned int i = 0; i < ARRAYSIZE(kTimelineEntries); i++) { + if (boxes.hitTest(i, mouse)) { + foundBox = true; + if (i != hoveredBox) { + hoveredBox = i; + redraw = true; + } + } + } + if (!foundBox && hoveredBox != -1u) { + hoveredBox = -1; + redraw = true; + } + } + if (_engine->getDragStatus() == kDragStatus_Finished) { + if (hoveredBox != -1u) { + selectedBox = hoveredBox; + } + if (boxes.hitTest(leaveBoxId, mouse)) { + selectedBox = leaveBoxId; + } + } + if (_engine->checkKeysPressed(1, Common::KEYCODE_ESCAPE)) { + selectedBox = leaveBoxId; + } + } + if (g_engine->shouldQuit()) { + selectedBox = leaveBoxId; + } + } + + g_system->showMouse(false); + + delete imageDecoder; + + if (selectedBox == leaveBoxId) { + return ""; + } else { + Common::String ret = "VT"; + ret += kTimelineEntries[selectedBox].year; + return ret; + } +} + +unsigned int Versailles_Documentation::docAreaHandleRecords(const Common::String &record) { + unsigned int action = -1; + + _currentRecord = record; + _visitTrace.clear(); + + Graphics::ManagedSurface docSurface; + Common::String nextRecord; + MouseBoxes boxes(10 + ARRAYSIZE(kTimelineEntries)); + + while (true) { + if (action == -1u) { + _currentRecord.toUppercase(); + + //debug("Displaying %s", _currentRecord.c_str()); + docAreaPrepareNavigation(); + docAreaPrepareRecord(docSurface, boxes); + action = docAreaHandleRecord(docSurface, boxes, nextRecord); + } + + switch (action) { + case 0: + action = -1; + // Back + if (!_visitTrace.empty()) { + _currentRecord = _visitTrace.back(); + _visitTrace.pop_back(); + break; + } + // No previous record, like a back to root + // fall through + case 1: + // Back to root + return 1; + case 2: + action = -1; + // Follow hyperlink keeping trace + _visitTrace.push_back(_currentRecord); + _currentRecord = nextRecord; + break; + case 3: + action = -1; + // Follow hyperlink losing trace + _visitTrace.clear(); + _currentRecord = nextRecord; + break; + case 6: + // Quit + return 2; + case 7: + action = -1; + // General map + _visitTrace.clear(); + nextRecord = docAreaHandleGeneralMap(); + if (nextRecord == "") { + // Go back to current record + break; + } else if (nextRecord != "VS00") { + _currentRecord = nextRecord; + break; + } + // castle has been selected, display its map + // fall through + case 8: + action = -1; + // Castle map + _visitTrace.clear(); + nextRecord = docAreaHandleCastleMap(); + if (nextRecord == "") { + // Go back to current record + break; + } else if (nextRecord != "planG") { + _currentRecord = nextRecord; + break; + } else { + // We can't go up to previous case, so let's do a round + action = 7; + break; + } + case 9: + action = -1; + // Start of category + _currentRecord = _categoryStartRecord; + break; + default: + error("Invalid case %d when displaying doc record", action); + } + } + error("shouldn't be there"); +} + +void Versailles_Documentation::docAreaPrepareNavigation() { + _currentInTimeline = false; + _currentMapLayout = false; + _currentHasMap = false; + _currentLinks.clear(); + + if (_currentRecord.hasPrefix("VA")) { + _categoryStartRecord = "VA00"; + _categoryEndRecord = "VA15"; + _categoryTitle = (*_messages)[68]; + } else if (_currentRecord.hasPrefix("VC")) { + _categoryStartRecord = "VC00"; + _categoryEndRecord = "VC26"; + _categoryTitle = (*_messages)[70]; + } else if (_currentRecord.hasPrefix("VR")) { + _categoryStartRecord = "VR00"; + _categoryEndRecord = "VR14"; + _categoryTitle = (*_messages)[69]; + } else if (_currentRecord.hasPrefix("VS")) { + _categoryStartRecord = "VS00"; + _categoryEndRecord = "VS37"; + _categoryTitle = (*_messages)[72]; + unsigned int id = atoi(_currentRecord.c_str() + 2); + if (id >= 16 && id <= 40) { + _currentMapLayout = true; + } + if ((id >= 16 && id <= 31) || + (id >= 35 && id <= 39)) { + _currentHasMap = true; + } + } else if (_currentRecord.hasPrefix("VT")) { + _categoryStartRecord = "VT00"; + _categoryEndRecord = "VT1715"; + _categoryTitle = (*_messages)[73]; + _currentInTimeline = true; + } else if (_currentRecord.hasPrefix("VV")) { + _categoryStartRecord = "VV00"; + _categoryEndRecord = "VV15"; + _categoryTitle = (*_messages)[71]; + } + getLinks(_currentRecord, _currentLinks); +} + +void Versailles_Documentation::docAreaPrepareRecord(Graphics::ManagedSurface &surface, + MouseBoxes &boxes) { + boxes.reset(); + + setupRecordBoxes(true, boxes); + + Common::String title, subtitle, caption; + Common::StringArray hyperlinks; + Common::String text = getRecordData(_currentRecord, title, subtitle, caption, hyperlinks); + + drawRecordData(surface, text, title, subtitle, caption); + + if (_currentInTimeline) { + surface.hLine(0, 39, 171, 241); // minus 1 because hLine draws inclusive + _fontManager->setCurrentFont(0); + _fontManager->setTransparentBackground(true); + _fontManager->setLineHeight(14); + _fontManager->setSpaceWidth(0); + _fontManager->setCharSpacing(1); + _fontManager->setSurface(&surface); + _fontManager->setForeColor(243); + for (unsigned int box_id = 10; box_id < ARRAYSIZE(kTimelineEntries) + 10; box_id++) { + boxes.display(box_id, *_fontManager); + } + } + + drawRecordBoxes(surface, true, boxes); +} + +unsigned int Versailles_Documentation::docAreaHandleRecord(Graphics::ManagedSurface &surface, + MouseBoxes &boxes, Common::String &nextRecord) { + // Hovering is only handled for timeline entries + _engine->setCursor(181); + g_system->showMouse(true); + + bool first = true; + bool redraw = true; + unsigned int hoveredBox = -1; + unsigned int action = -1; + + while (action == -1u) { + if (redraw) { + g_system->copyRectToScreen(surface.getPixels(), surface.pitch, 0, 0, surface.w, surface.h); + redraw = false; + } + g_system->updateScreen(); + + if (_engine->pollEvents() || first) { + first = false; + if (g_engine->shouldQuit()) { + // Fake the quit + action = 6; + } + Common::Point mouse = _engine->getMousePos(); + if (_currentInTimeline) { + bool foundBox = false; + for (unsigned int i = 10; i < 10 + ARRAYSIZE(kTimelineEntries); i++) { + if (boxes.hitTest(i, mouse)) { + foundBox = true; + if (i != hoveredBox) { + _fontManager->setCurrentFont(0); + _fontManager->setTransparentBackground(true); + _fontManager->setSurface(&surface); + if (hoveredBox != -1u) { + // Restore the previous entry hovered + _fontManager->setForeColor(243); + boxes.display(hoveredBox, *_fontManager); + } + hoveredBox = i; + _fontManager->setForeColor(241); + boxes.display(hoveredBox, *_fontManager); + redraw = true; + } + } + } + if (!foundBox && hoveredBox != -1u) { + // Restore the previous entry hovered + _fontManager->setForeColor(243); + boxes.display(hoveredBox, *_fontManager); + hoveredBox = -1; + redraw = true; + } + } else if (_currentHasMap) { // Mutually exclusive with timeline + // No clash is possible for hoveredBox between timeline and map + if (boxes.hitTest(8, mouse)) { + if (hoveredBox != 8) { + _engine->setCursor(145); + hoveredBox = 8; + } + } else { + if (hoveredBox == 8) { + _engine->setCursor(181); + hoveredBox = -1; + } + } + } + if (_engine->getDragStatus() == kDragStatus_Pressed) { + if (boxes.hitTest(2, mouse) && _currentLinks.size()) { + Common::StringArray items; + for (Common::Array<LinkInfo>::const_iterator it = _currentLinks.begin(); it != _currentLinks.end(); + it++) { + items.push_back(it->title); + } + Common::Rect iconRect = boxes.getBoxRect(2); + unsigned int selectedItem = handlePopupMenu(surface, Common::Point(iconRect.right, iconRect.top), + true, 20, items); + if (selectedItem != -1u) { + nextRecord = _currentLinks[selectedItem].record; + action = 2; + } + } else if (boxes.hitTest(3, mouse)) { + Common::StringArray items; + for (Common::Array<LinkInfo>::const_iterator it = _allLinks.begin(); it != _allLinks.end(); it++) { + items.push_back(it->title); + } + Common::Rect iconRect = boxes.getBoxRect(3); + unsigned int selectedItem = handlePopupMenu(surface, Common::Point(iconRect.right, iconRect.top), + true, 20, items); + if (selectedItem != -1u) { + nextRecord = _allLinks[selectedItem].record; + action = 3; + } + } + } else if (_engine->getDragStatus() == kDragStatus_Finished) { + if (boxes.hitTest(0, mouse)) { + // Back in history + action = 0; + } else if (boxes.hitTest(1, mouse)) { + // Handle summary menu + Common::StringArray items; + items.push_back((*_messages)[61]); + items.push_back((*_messages)[62]); + unsigned int selectedItem = handlePopupMenu(surface, boxes.getBoxOrigin(1), false, 20, items); + if (selectedItem == 0) { + action = 1; + } else if (selectedItem == 1) { + action = 7; + } + } else if (boxes.hitTest(4, mouse)) { + // Next + action = 4; + } else if (boxes.hitTest(5, mouse)) { + // Previous + action = 5; + } else if (boxes.hitTest(6, mouse)) { + // Handle quit menu + Common::StringArray items; + items.push_back((*_messages)[60]); + unsigned int selectedItem = handlePopupMenu(surface, boxes.getBoxOrigin(6), false, 20, items); + if (selectedItem == 0) { + action = 6; + } + } else if (_currentHasMap && boxes.hitTest(8, mouse)) { + // Map + action = 8; + } else if (boxes.hitTest(9, mouse)) { + // Category name + action = 9; + } else if (_currentInTimeline && hoveredBox != -1u) { + // Clicked on a timeline entry + nextRecord = "VT"; + nextRecord += kTimelineEntries[hoveredBox - 10].year; + // Fake a global jump + action = 3; + } + } + if (action == 4 || action == 5) { + if (action == 4 && _currentRecord == _categoryEndRecord) { + action = -1; + continue; + } + if (action == 5 && _currentRecord == _categoryStartRecord) { + action = -1; + continue; + } + Common::HashMap<Common::String, RecordInfo>::iterator hmIt = _records.find(_currentRecord); + if (hmIt == _records.end()) { + // Shouldn't happen + action = -1; + continue; + } + unsigned int recordId = hmIt->_value.id; + if (action == 4) { + recordId++; + } else if (action == 5) { + recordId--; + } + assert(recordId < _recordsOrdered.size()); + nextRecord = _recordsOrdered[recordId]; + // Fake a global jump + action = 3; + } + } + } + + g_system->showMouse(false); + _engine->setCursor(181); + return action; +} + +Common::String Versailles_Documentation::docAreaHandleGeneralMap() { + struct Area { + Common::Rect areaPos; + const char *record; + const char *bmp; + unsigned int messageId; + const Common::String *message; + Common::Point messagePos; + Graphics::Surface highlightedImg; + + Area(const Common::Point &areaPos_, const char *bmp_, unsigned int messageId_, + const char *record_ = nullptr) : + areaPos(areaPos_.x, areaPos_.y, areaPos_.x, areaPos_.y), record(record_), bmp(bmp_), + messageId(messageId_), message(nullptr) { } + Area(const Common::Rect &areaPos_, unsigned int messageId_, const char *record_ = nullptr) : + areaPos(areaPos_), record(record_), bmp(nullptr), messageId(messageId_), message(nullptr) { } + } areas[] = { + Area(Common::Point(174, 181), "APL.bmp", 74), + Area(Common::Point(422, 129), "CHAT.bmp", 75, "VS00"), + Area(Common::Point(193, 204), "COLN.bmp", 76, "VS02"), + Area(Common::Point(327, 269), "LABY.bmp", 77, "VS33"), + Area(Common::Point(327, 170), "LATN.bmp", 78), + Area(Common::Point(396, 271), "ORG.bmp", 79, "VS32"), + Area(Common::Point(385, 203), "PART.bmp", 80, "VS06"), + Area(Common::Point(212, 193), "TAP.bmp", 81), + Area(Common::Rect(0, 194, 154, 211), 86, "VS09"), + Area(Common::Rect(396, 229, 450, 268), 87), + Area(Common::Rect(394, 133, 450, 177), 88), + Area(Common::Rect(489, 376, 592, 479), 89, "VS07"), + Area(Common::Rect(327, 233, 386, 266), 90), + Area(Common::Rect(395, 18, 451, 60), 91), + Area(Common::Rect(383, 381, 477, 479), 92) + }; + + _fontManager->setCurrentFont(0); + _fontManager->setTransparentBackground(true); + _fontManager->setLineHeight(14); + _fontManager->setSpaceWidth(0); + _fontManager->setCharSpacing(1); + + MouseBoxes boxes(ARRAYSIZE(areas) + 1); + + Image::BitmapDecoder bmpDecoder; + Common::File file; + + for (unsigned int i = 0; i < ARRAYSIZE(areas); i++) { + if (areas[i].bmp) { + if (!file.open(areas[i].bmp)) { + error("Failed to open BMP file: %s", areas[i].bmp); + } + if (!bmpDecoder.loadStream(file)) { + error("Failed to load BMP file: %s", areas[i].bmp); + } + areas[i].highlightedImg.copyFrom(*bmpDecoder.getSurface()); + bmpDecoder.destroy(); + file.close(); + areas[i].areaPos.setWidth(areas[i].highlightedImg.w); + areas[i].areaPos.setHeight(areas[i].highlightedImg.h); + } + areas[i].message = &(*_messages)[areas[i].messageId]; + unsigned int lineWidth = _fontManager->getStrWidth(*areas[i].message); + areas[i].messagePos.x = (areas[i].areaPos.left + areas[i].areaPos.right) / 2 - lineWidth / 2; + areas[i].messagePos.y = areas[i].areaPos.top - 40; + if (areas[i].messagePos.x < 8) { + areas[i].messagePos.x = 8; + } else if (areas[i].messagePos.x + lineWidth > 627) { + areas[i].messagePos.x = 627 - lineWidth; + } + if (areas[i].messagePos.y < 5) { + areas[i].messagePos.y = 5; + } + const Common::Rect &areaPos = areas[i].areaPos; + boxes.setupBox(i, areaPos.left, areaPos.top, areaPos.right, areaPos.bottom); + } + boxes.setupBox(ARRAYSIZE(areas), 639 - _sprites->getCursor(105).getWidth(), + 479 - _sprites->getCursor(105).getHeight(), 640, 480); + + Image::ImageDecoder *imageDecoder = _engine->loadHLZ("PLANGR.HLZ"); + if (!imageDecoder) { + return ""; + } + const Graphics::Surface *bgFrame = imageDecoder->getSurface(); + + Graphics::ManagedSurface mapSurface; + mapSurface.create(bgFrame->w, bgFrame->h, bgFrame->format); + mapSurface.blitFrom(*bgFrame); + + _fontManager->setSurface(&mapSurface); + + _engine->setupPalette(imageDecoder->getPalette(), imageDecoder->getPaletteStartIndex(), + imageDecoder->getPaletteColorCount()); + + _engine->setCursor(181); + g_system->showMouse(true); + + bool redraw = true; + unsigned int hoveredBox = -1; + unsigned int selectedBox = -1; + + while (selectedBox == -1u) { + if (redraw) { + // Draw without worrying of already modified areas, that's handled when recomputing hoveredBox + if (hoveredBox != -1u) { + if (areas[hoveredBox].highlightedImg.getPixels() != nullptr) { + mapSurface.transBlitFrom(areas[hoveredBox].highlightedImg, + Common::Point(areas[hoveredBox].areaPos.left, areas[hoveredBox].areaPos.top)); + } else { + unsigned int middleX = (areas[hoveredBox].areaPos.left + areas[hoveredBox].areaPos.right) / 2; + unsigned int middleY = (areas[hoveredBox].areaPos.top + areas[hoveredBox].areaPos.bottom) / 2; + unsigned int spriteX = middleX - _sprites->getCursor(163).getWidth() / 2; + unsigned int spriteY = middleY - _sprites->getCursor(163).getHeight() / 2; + mapSurface.transBlitFrom(_sprites->getSurface(163), Common::Point(spriteX, spriteY), + _sprites->getKeyColor(163)); + } + _fontManager->setForeColor(areas[hoveredBox].record == nullptr ? 243 : 240); + Graphics::Surface subSurface = mapSurface.getSubArea(Common::Rect(areas[hoveredBox].messagePos.x - + 3, + areas[hoveredBox].messagePos.y - 3, + areas[hoveredBox].messagePos.x + _fontManager->getStrWidth(*areas[hoveredBox].message) + 3, + areas[hoveredBox].messagePos.y + _fontManager->getFontMaxHeight() + 8)); + _engine->makeTranslucent(subSurface, subSurface); + _fontManager->displayStr(areas[hoveredBox].messagePos.x, areas[hoveredBox].messagePos.y, + *areas[hoveredBox].message); + } + mapSurface.transBlitFrom(_sprites->getSurface(105), boxes.getBoxOrigin(ARRAYSIZE(areas)), + _sprites->getKeyColor(105)); + /* + // For debugging only + for(unsigned int i = 0; i < ARRAYSIZE(areas); i++) { + mapSurface.frameRect(areas[i].areaPos, 0); + } + */ + + g_system->copyRectToScreen(mapSurface.getPixels(), mapSurface.pitch, 0, 0, mapSurface.w, + mapSurface.h); + + redraw = false; + } + g_system->updateScreen(); + + if (_engine->pollEvents()) { + Common::Point mouse = _engine->getMousePos(); + if (!_engine->getCurrentMouseButton()) { + // Don't change highlighted icon when clicking + bool foundBox = false; + unsigned int oldHoveredBox = hoveredBox; + for (unsigned int i = 0; i < ARRAYSIZE(areas); i++) { + if (boxes.hitTest(i, mouse)) { + if (i != hoveredBox) { + hoveredBox = i; + redraw = true; + } + foundBox = true; + break; + } + } + if (!foundBox && hoveredBox != -1u) { + hoveredBox = -1; + redraw = true; + } + if (hoveredBox != oldHoveredBox && oldHoveredBox != -1u) { + // Restore original area + mapSurface.blitFrom(*bgFrame, areas[oldHoveredBox].areaPos, + Common::Point(areas[oldHoveredBox].areaPos.left, areas[oldHoveredBox].areaPos.top)); + Common::Rect textRect(areas[oldHoveredBox].messagePos.x - 3, + areas[oldHoveredBox].messagePos.y - 3, + areas[oldHoveredBox].messagePos.x + _fontManager->getStrWidth(*areas[oldHoveredBox].message) + 3, + areas[oldHoveredBox].messagePos.y + _fontManager->getFontMaxHeight() + 8); + mapSurface.blitFrom(*bgFrame, textRect, + Common::Point(textRect.left, textRect.top)); + } + } + if (_engine->getDragStatus() == kDragStatus_Finished) { + if (hoveredBox != -1u && areas[hoveredBox].record) { + selectedBox = hoveredBox; + } else if (boxes.hitTest(ARRAYSIZE(areas), mouse)) { + selectedBox = ARRAYSIZE(areas); + } + } + if (_engine->checkKeysPressed(1, Common::KEYCODE_ESCAPE)) { + selectedBox = ARRAYSIZE(areas); + } + if (g_engine->shouldQuit()) { + selectedBox = ARRAYSIZE(areas); + } + } + } + + g_system->showMouse(false); + + delete imageDecoder; + + if (selectedBox == ARRAYSIZE(areas)) { + return ""; + } else { + return areas[selectedBox].record; + } +} + +Common::String Versailles_Documentation::docAreaHandleCastleMap() { + struct Area { + Common::Rect areaPos; + bool fillArea; + const char *record; + unsigned int messageId; + Common::String message; + Common::Point messagePos; + Common::Rect areaPos1; + Common::Rect areaPos2; + + Area(const Common::Rect &areaPos_, const char *record_, bool fillArea_ = true, + unsigned int messageId_ = -1) : + areaPos(areaPos_), record(record_), fillArea(fillArea_), messageId(messageId_) { } + Area(const Common::Rect &areaPos_, const Common::Rect &areaPos1_, + const Common::Rect &areaPos2_, const char *record_, bool fillArea_ = true, + unsigned int messageId_ = -1) : + areaPos(areaPos_), areaPos1(areaPos1_), areaPos2(areaPos2_), + record(record_), fillArea(fillArea_), messageId(messageId_) { } + } areas[] = { + /* 0 */ + Area(Common::Rect(212, 134, 239, 164), "VS16"), + Area(Common::Rect(74, 160, 89, 173), "VS24"), + Area(Common::Rect(93, 160, 109, 173), "VS25"), + Area(Common::Rect(130, 160, 154, 173), "VS26"), + Area(Common::Rect(158, 160, 171, 173), "VS27"), + Area(Common::Rect(199, 160, 209, 171), "VS28"), + Area(Common::Rect(74, 177, 89, 291), "VS31"), + Area(Common::Rect(158, 178, 195, 193), "VS30"), + Area(Common::Rect(199, 175, 209, 188), "VS29"), + Area(Common::Rect(112, 220, 160, 249), "VS35"), + /* 10 */ + Area(Common::Rect(93, 227, 106, 240), "VS23"), + Area(Common::Rect(93, 244, 106, 257), "VS22"), + Area(Common::Rect(93, 261, 106, 274), "VS20"), + Area(Common::Rect(110, 255, 126, 269), "VS19"), + Area(Common::Rect(133, 255, 155, 271), "VS18"), + Area(Common::Rect(93, 285, 99, 295), "VS21"), + Area(Common::Rect(152, 279, 173, 288), "VS17"), + Area(Common::Rect(336, 113, 359, 136), Common::Rect(359, 116, 448, 134), Common::Rect(449, 113, 473, 136), "VS36"), + Area(Common::Rect(336, 328, 359, 351), Common::Rect(359, 331, 448, 348), Common::Rect(449, 328, 473, 351), "VS36"), + Area(Common::Rect(563, 0, 624, 139), "planG", false, 82), + /* 20 */ + Area(Common::Rect(563, 300, 624, 462), "planG", false, 83), + Area(Common::Rect(0, 0, 205, 152), "planG", false, 84), + Area(Common::Rect(0, 318, 205, 465), "planG", false, 84), + Area(Common::Rect(160, 210, 329, 267), "VS40", false), + Area(Common::Rect(330, 158, 561, 315), "planG", false, 85), + }; + + _fontManager->setCurrentFont(0); + _fontManager->setTransparentBackground(true); + _fontManager->setLineHeight(14); + _fontManager->setSpaceWidth(0); + _fontManager->setCharSpacing(1); + + MouseBoxes boxes(ARRAYSIZE(areas) + 1); + + for (unsigned int i = 0; i < ARRAYSIZE(areas); i++) { + if (areas[i].messageId != -1u) { + areas[i].message = (*_messages)[areas[i].messageId]; + } else { + areas[i].message = getRecordTitle(areas[i].record); + } + unsigned int lineWidth = _fontManager->getStrWidth(areas[i].message); + unsigned int right; + if (areas[i].areaPos2.right) { + right = areas[i].areaPos2.right; + } else { + right = areas[i].areaPos.right; + } + areas[i].messagePos.x = (areas[i].areaPos.left + right) / 2 - lineWidth / 2; + if (areas[i].fillArea) { + areas[i].messagePos.y = areas[i].areaPos.top - 30; + } else { + areas[i].messagePos.y = (areas[i].areaPos.top + areas[i].areaPos.bottom) / 2 - 50; + } + if (areas[i].messagePos.x < 5) { + areas[i].messagePos.x = 5; + } else if (areas[i].messagePos.x + lineWidth > 630) { + areas[i].messagePos.x = 630 - lineWidth; + } + if (areas[i].messagePos.y < 2) { + areas[i].messagePos.y = 2; + } + Common::Rect areaPos = areas[i].areaPos; + if (areas[i].areaPos2.right) { + areaPos.right = areas[i].areaPos2.right; + areaPos.bottom = areas[i].areaPos2.bottom; + } + boxes.setupBox(i, areaPos.left, areaPos.top, areaPos.right, areaPos.bottom); + } + boxes.setupBox(ARRAYSIZE(areas), 639 - _sprites->getCursor(105).getWidth(), + 479 - _sprites->getCursor(105).getHeight(), 640, 480); + + Image::ImageDecoder *imageDecoder = _engine->loadHLZ("PLAN.HLZ"); + if (!imageDecoder) { + return ""; + } + const Graphics::Surface *bgFrame = imageDecoder->getSurface(); + + Graphics::ManagedSurface mapSurface; + mapSurface.create(bgFrame->w, bgFrame->h, bgFrame->format); + mapSurface.blitFrom(*bgFrame); + + _fontManager->setSurface(&mapSurface); + + _engine->setupPalette(imageDecoder->getPalette(), imageDecoder->getPaletteStartIndex(), + imageDecoder->getPaletteColorCount()); + + _engine->setCursor(181); + g_system->showMouse(true); + + bool redraw = true; + unsigned int hoveredBox = -1; + unsigned int selectedBox = -1; + + while (selectedBox == -1u) { + if (redraw) { + // Draw without worrying of already modified areas, that's handled when recomputing hoveredBox + if (hoveredBox != -1u) { + if (areas[hoveredBox].fillArea) { + Common::Rect rect(areas[hoveredBox].areaPos); + rect.bottom += 1; // fillRect needs to fill including the limit + rect.right += 1; + mapSurface.fillRect(rect, 243); + if (areas[hoveredBox].areaPos1.right) { + rect = Common::Rect(areas[hoveredBox].areaPos1); + rect.bottom += 1; // fillRect needs to fill including the limit + rect.right += 1; + mapSurface.fillRect(rect, 243); + } + if (areas[hoveredBox].areaPos2.right) { + rect = Common::Rect(areas[hoveredBox].areaPos2); + rect.bottom += 1; // fillRect needs to fill including the limit + rect.right += 1; + mapSurface.fillRect(rect, 243); + } + } else { + unsigned int middleX = (areas[hoveredBox].areaPos.left + areas[hoveredBox].areaPos.right) / 2; + unsigned int middleY = (areas[hoveredBox].areaPos.top + areas[hoveredBox].areaPos.bottom) / 2; + unsigned int spriteX = middleX - _sprites->getCursor(163).getWidth() / 2; + unsigned int spriteY = middleY - _sprites->getCursor(163).getHeight() / 2; + mapSurface.transBlitFrom(_sprites->getSurface(163), Common::Point(spriteX, spriteY), + _sprites->getKeyColor(163)); + } + Common::Rect textRect(areas[hoveredBox].messagePos.x - 4, + areas[hoveredBox].messagePos.y, + areas[hoveredBox].messagePos.x + _fontManager->getStrWidth(areas[hoveredBox].message) + 5, + areas[hoveredBox].messagePos.y + _fontManager->getFontMaxHeight() + 5); + mapSurface.fillRect(textRect, 247); + _fontManager->setForeColor(strcmp(areas[hoveredBox].record, "planG") == 0 ? 243 : 241); + _fontManager->displayStr(areas[hoveredBox].messagePos.x, areas[hoveredBox].messagePos.y, + areas[hoveredBox].message); + } + mapSurface.transBlitFrom(_sprites->getSurface(105), boxes.getBoxOrigin(ARRAYSIZE(areas)), + _sprites->getKeyColor(105)); + /* + // For debugging only + for(unsigned int i = 0; i < ARRAYSIZE(areas); i++) { + mapSurface.frameRect(areas[i].areaPos, 0); + if (areas[i].areaPos1.right) { + mapSurface.frameRect(areas[i].areaPos1, 0); + } + if (areas[i].areaPos2.right) { + mapSurface.frameRect(areas[i].areaPos2, 0); + } + } + */ + + g_system->copyRectToScreen(mapSurface.getPixels(), mapSurface.pitch, 0, 0, + mapSurface.w, mapSurface.h); + + redraw = false; + } + g_system->updateScreen(); + + if (_engine->pollEvents()) { + Common::Point mouse = _engine->getMousePos(); + if (!_engine->getCurrentMouseButton()) { + // Don't change highlighted icon when clicking + bool foundBox = false; + unsigned int oldHoveredBox = hoveredBox; + for (unsigned int i = 0; i < ARRAYSIZE(areas); i++) { + if (boxes.hitTest(i, mouse)) { + if (i != hoveredBox) { + hoveredBox = i; + redraw = true; + } + foundBox = true; + break; + } + } + if (!foundBox && hoveredBox != -1u) { + hoveredBox = -1; + redraw = true; + } + if (hoveredBox != oldHoveredBox && oldHoveredBox != -1u) { + // Restore original area + Common::Rect areaPos = areas[oldHoveredBox].areaPos; + if (areas[oldHoveredBox].areaPos2.right) { + areaPos.right = areas[oldHoveredBox].areaPos2.right; + areaPos.bottom = areas[oldHoveredBox].areaPos2.bottom; + } + areaPos.right += 1; + areaPos.bottom += 1; + mapSurface.blitFrom(*bgFrame, areaPos, + Common::Point(areaPos.left, areaPos.top)); + Common::Rect textRect(areas[oldHoveredBox].messagePos.x - 4, + areas[oldHoveredBox].messagePos.y, + areas[oldHoveredBox].messagePos.x + _fontManager->getStrWidth(areas[oldHoveredBox].message) + 5, + areas[oldHoveredBox].messagePos.y + _fontManager->getFontMaxHeight() + 5); + mapSurface.blitFrom(*bgFrame, textRect, + Common::Point(textRect.left, textRect.top)); + } + } + if (_engine->getDragStatus() == kDragStatus_Finished) { + if (hoveredBox != -1u && areas[hoveredBox].record) { + selectedBox = hoveredBox; + } else if (boxes.hitTest(ARRAYSIZE(areas), mouse)) { + selectedBox = ARRAYSIZE(areas); + } + } + if (_engine->checkKeysPressed(1, Common::KEYCODE_ESCAPE)) { + selectedBox = ARRAYSIZE(areas); + } + if (g_engine->shouldQuit()) { + selectedBox = ARRAYSIZE(areas); + } + } + } + + g_system->showMouse(false); + + delete imageDecoder; + + if (selectedBox == ARRAYSIZE(areas)) { + return ""; + } else { + return areas[selectedBox].record; + } +} + +void Versailles_Documentation::inGamePrepareRecord(Graphics::ManagedSurface &surface, + MouseBoxes &boxes) { + _categoryStartRecord = ""; + _categoryEndRecord = ""; + _categoryTitle = ""; + _currentLinks.clear(); + _currentInTimeline = false; + _currentMapLayout = false; + _currentHasMap = false; + + if (_currentRecord.hasPrefix("VS")) { + unsigned int id = atoi(_currentRecord.c_str() + 2); + if (id >= 16 && id <= 40) { + _currentMapLayout = true; + } + } else if (_currentRecord.hasPrefix("VT")) { + error("There shouldn't be the timeline in game"); + } + + boxes.reset(); + + setupRecordBoxes(false, boxes); + + Common::String title, subtitle, caption; + Common::StringArray hyperlinks; + Common::String text = getRecordData(_currentRecord, title, subtitle, caption, hyperlinks); + convertHyperlinks(hyperlinks, _currentLinks); + + drawRecordData(surface, text, title, subtitle, caption); + drawRecordBoxes(surface, false, boxes); +} + +unsigned int Versailles_Documentation::inGameHandleRecord(Graphics::ManagedSurface &surface, + MouseBoxes &boxes, Common::String &nextRecord) { + _engine->setCursor(181); + g_system->showMouse(true); + + unsigned int action = -1; + + g_system->copyRectToScreen(surface.getPixels(), surface.pitch, 0, 0, surface.w, surface.h); + + while (action == -1u) { + g_system->updateScreen(); + + if (_engine->pollEvents()) { + if (g_engine->shouldQuit()) { + // Fake the quit + action = 6; + } + Common::Point mouse = _engine->getMousePos(); + if (_engine->getDragStatus() == kDragStatus_Pressed) { + if (boxes.hitTest(2, mouse) && _currentLinks.size()) { + Common::StringArray items; + for (Common::Array<LinkInfo>::const_iterator it = _currentLinks.begin(); it != _currentLinks.end(); + it++) { + items.push_back(it->title); + } + Common::Rect iconRect = boxes.getBoxRect(2); + unsigned int selectedItem = handlePopupMenu(surface, Common::Point(iconRect.right, iconRect.top), + true, 20, items); + if (selectedItem != -1u) { + nextRecord = _currentLinks[selectedItem].record; + action = 2; + } + } + } else if (_engine->getDragStatus() == kDragStatus_Finished) { + if (boxes.hitTest(0, mouse)) { + // Back in history + action = 0; + } else if (boxes.hitTest(1, mouse)) { + // Quit + action = 1; + } + } + } + } + + g_system->showMouse(false); + _engine->setCursor(181); + return action; +} + +void Versailles_Documentation::drawRecordData(Graphics::ManagedSurface &surface, + const Common::String &text, const Common::String &title, + const Common::String &subtitle, const Common::String &caption) { + bool displayMap = false; + unsigned char foreColor = 247; + Common::String background; + Common::Rect blockTitle; + Common::Rect blockHLine; + Common::Rect blockSubTitle; + Common::Rect blockCaption; + Common::Rect blockContent1; + Common::Rect blockContent2; + + if (_currentMapLayout) { + blockTitle = Common::Rect(30, 8, 361, 38); + blockHLine = Common::Rect(60, 35, 286, 35); + blockSubTitle = Common::Rect(60, 40, 361, 70); + blockCaption = Common::Rect(378, 293, 630, 344); + blockContent1 = Common::Rect(60, 60, 272, 295); + blockContent2 = Common::Rect(60, 295, 383, 437); + } else if (_currentInTimeline) { + blockTitle = Common::Rect(78, 10, 170, 33); + //blockHLine = Common::Rect(); + blockSubTitle = Common::Rect(60, 40, 361, 70); + blockCaption = Common::Rect(378, 293, 630, 344); + blockContent1 = Common::Rect(47, 70, 420, 306); + blockContent2 = Common::Rect(174, 306, 414, 411); + } else if (_currentRecord == "VC02" || + _currentRecord == "VC03" || + _currentRecord == "VV01") { + blockTitle = Common::Rect(30, 8, 361, 38); + blockHLine = Common::Rect(60, 35, 378, 35); + blockSubTitle = Common::Rect(60, 40, 361, 70); + blockCaption = Common::Rect(378, 293, 630, 360); + blockContent1 = Common::Rect(60, 80, 351, 355); + blockContent2 = Common::Rect(60, 355, 605, 437); + } else if (_currentRecord == "VV13" || + _currentRecord == "VV08") { + blockTitle = Common::Rect(30, 8, 361, 38); + blockHLine = Common::Rect(60, 35, 286, 35); + blockSubTitle = Common::Rect(60, 40, 361, 70); + blockCaption = Common::Rect(378, 422, 630, 480); + blockContent1 = Common::Rect(60, 60, 378, 285); + blockContent2 = Common::Rect(60, 285, 378, 437); + } else { + blockTitle = Common::Rect(30, 8, 361, 38); + blockHLine = Common::Rect(60, 35, 378, 35); + blockSubTitle = Common::Rect(60, 40, 361, 70); + blockCaption = Common::Rect(378, 293, 630, 360); + blockContent1 = Common::Rect(60, 80, 351, 345); + blockContent2 = Common::Rect(60, 345, 605, 437); + } + if (_currentInTimeline) { + background = "CHRONO1"; + foreColor = 241; + } else { + background = _currentRecord; + } + background += ".HLZ"; + Common::File backgroundFl; + if (!backgroundFl.open(background)) { + background = displayMap ? "pas_fonP.hlz" : "pas_fond.hlz"; + } else { + backgroundFl.close(); + } + + Image::ImageDecoder *imageDecoder = _engine->loadHLZ(background); + const Graphics::Surface *bgFrame = imageDecoder->getSurface(); + + _engine->setupPalette(imageDecoder->getPalette(), imageDecoder->getPaletteStartIndex(), + imageDecoder->getPaletteColorCount()); + + surface.create(bgFrame->w, bgFrame->h, bgFrame->format); + surface.blitFrom(*bgFrame); + + /*Common::String title, subtitle, caption; + Common::StringArray hyperlinks; + + Common::String text = getRecordData(_currentRecord, title, subtitle, caption, hyperlinks);*/ + + unsigned int lineHeight = 21; + _fontManager->setCurrentFont(4); + _fontManager->setTransparentBackground(true); + _fontManager->setSpaceWidth(1); + _fontManager->setCharSpacing(1); + _fontManager->setForeColor(foreColor); + _fontManager->setSurface(&surface); + + /* + surface.frameRect(blockContent1, foreColor); + surface.frameRect(blockContent2, foreColor); + surface.frameRect(blockTitle, foreColor); + surface.frameRect(blockSubTitle, foreColor); + surface.frameRect(blockCaption, foreColor); + */ + + Graphics::ManagedSurface backupSurface; + backupSurface.copyFrom(surface); + + // This loop tries to adapt the interline space to make all the text fit in the blocks + while (true) { + _fontManager->setLineHeight(lineHeight); + _fontManager->setupBlock(blockContent1); + if (!_fontManager->displayBlockText(text)) { + // All text was drawn + break; + } + + // Setup second zone + blockContent2.top = _fontManager->blockTextLastPos().y + lineHeight; + _fontManager->setupBlock(blockContent2); + + if (!_fontManager->displayBlockText(text, _fontManager->blockTextRemaining())) { + // All text was drawn + break; + } + + // Not all text could be drawn: shrink everything, restore image and do it again + lineHeight--; + surface.copyFrom(backupSurface); + } + + _fontManager->setForeColor(foreColor); + _fontManager->setCurrentFont(0); + _fontManager->setTransparentBackground(true); + _fontManager->setLineHeight(20); + _fontManager->setCharSpacing(0); + _fontManager->setSpaceWidth(2); + + //debug("Title: %s", title.c_str()); + _fontManager->setupBlock(blockTitle); + _fontManager->displayBlockText(title); + + _fontManager->setCurrentFont(6); + _fontManager->setLineHeight(14); + _fontManager->setSpaceWidth(1); + + //debug("Subtitle: %s", subtitle.c_str()); + _fontManager->setupBlock(blockSubTitle); + _fontManager->displayBlockText(subtitle); + + if (!_currentInTimeline) { + surface.hLine(blockHLine.left, blockHLine.top, blockHLine.right - 1, + foreColor); // minus 1 because hLine draws inclusive + } + + _fontManager->setSpaceWidth(0); + + _fontManager->setupBlock(blockCaption); + _fontManager->displayBlockText(caption); +} + +void Versailles_Documentation::setupRecordBoxes(bool inDocArea, MouseBoxes &boxes) { + // Layout of bar in doc area is Quit | Back | | Previous | Category | Next | | Trace | Hyperlinks | All records + // Layout of bar in game is ==> Trace | Hyperlinks | Quit + unsigned int allRecordsX = 640 - _sprites->getCursor(19).getWidth(); + unsigned int hyperlinksX = allRecordsX - _sprites->getCursor(242).getWidth() - 10; + unsigned int traceX = hyperlinksX - _sprites->getCursor(105).getWidth() - 10; + + if (_visitTrace.size()) { + boxes.setupBox(0, traceX, 480 - _sprites->getCursor(105).getHeight() - 3, + traceX + _sprites->getCursor(105).getWidth(), 480); + } + if (inDocArea) { + unsigned int backX = _sprites->getCursor(225).getWidth() + 10; //Right to quit button + + _fontManager->setCurrentFont(0); + _fontManager->setTransparentBackground(true); + _fontManager->setSpaceWidth(0); + _fontManager->setCharSpacing(1); + unsigned int categoryHalfWidth = _fontManager->getStrWidth(_categoryTitle) / 2; + unsigned nextX = 320 + categoryHalfWidth + 20; + unsigned prevX = 320 - categoryHalfWidth - 20 - _sprites->getCursor(76).getWidth(); + + boxes.setupBox(3, allRecordsX, 480 - _sprites->getCursor(19).getHeight(), + allRecordsX + _sprites->getCursor(19).getWidth(), 480); + boxes.setupBox(1, backX, 480 - _sprites->getCursor(227).getHeight(), + backX + _sprites->getCursor(227).getWidth(), 480); + boxes.setupBox(9, 320 - categoryHalfWidth - 5, 480 - _sprites->getCursor(227).getHeight(), + 320 + categoryHalfWidth + 5, 480); + boxes.setupBox(4, nextX, 476 - _sprites->getCursor(72).getHeight(), + nextX + _sprites->getCursor(72).getWidth(), 476); + boxes.setupBox(5, prevX, 476 - _sprites->getCursor(76).getHeight(), + prevX + _sprites->getCursor(76).getWidth(), 476); + // Quit button + boxes.setupBox(6, 0, 480 - _sprites->getCursor(225).getHeight(), + _sprites->getCursor(225).getWidth(), 480); + // Map + boxes.setupBox(8, 403, 305, 622, 428); + if (_currentInTimeline) { + for (unsigned int box_id = 0; box_id < ARRAYSIZE(kTimelineEntries); box_id++) { + boxes.setupBox(10 + box_id, kTimelineEntries[box_id].x, kTimelineEntries[box_id].y, + kTimelineEntries[box_id].x + 30, kTimelineEntries[box_id].y + 15, kTimelineEntries[box_id].year); + } + } + } else { + unsigned int quitInGameX = 640 - _sprites->getCursor(105).getWidth(); + boxes.setupBox(1, quitInGameX, 480 - _sprites->getCursor(105).getHeight(), + quitInGameX + _sprites->getCursor(105).getWidth(), 480); + } + boxes.setupBox(2, hyperlinksX, 480 - _sprites->getCursor(242).getHeight(), + hyperlinksX + _sprites->getCursor(242).getWidth(), 480); +} + +void Versailles_Documentation::drawRecordBoxes(Graphics::ManagedSurface &surface, bool inDocArea, + MouseBoxes &boxes) { + if (_visitTrace.size()) { + surface.transBlitFrom(_sprites->getSurface(105), boxes.getBoxOrigin(0), _sprites->getKeyColor(105)); + } + if (inDocArea) { + surface.transBlitFrom(_sprites->getSurface(19), boxes.getBoxOrigin(3), _sprites->getKeyColor(19)); + surface.transBlitFrom(_sprites->getSurface(227), boxes.getBoxOrigin(1), _sprites->getKeyColor(227)); + + surface.fillRect(boxes.getBoxRect(9), 243); + + _fontManager->setCurrentFont(0); + _fontManager->setTransparentBackground(true); + _fontManager->setSpaceWidth(0); + _fontManager->setCharSpacing(1); + _fontManager->setForeColor(240); + Common::Point catPos = boxes.getBoxOrigin(9); + catPos += Common::Point(5, 3); + _fontManager->displayStr(catPos.x, catPos.y, _categoryTitle); + + if (_currentRecord == _categoryEndRecord) { + surface.transBlitFrom(_sprites->getSurface(75), boxes.getBoxOrigin(4), _sprites->getKeyColor(75)); + } else { + surface.transBlitFrom(_sprites->getSurface(72), boxes.getBoxOrigin(4), _sprites->getKeyColor(72)); + } + if (_currentRecord == _categoryStartRecord) { + surface.transBlitFrom(_sprites->getSurface(77), boxes.getBoxOrigin(5), _sprites->getKeyColor(77)); + } else { + surface.transBlitFrom(_sprites->getSurface(76), boxes.getBoxOrigin(5), _sprites->getKeyColor(76)); + } + surface.transBlitFrom(_sprites->getSurface(225), boxes.getBoxOrigin(6), _sprites->getKeyColor(225)); + } else { + surface.transBlitFrom(_sprites->getSurface(105), boxes.getBoxOrigin(1), _sprites->getKeyColor(105)); + } + if (_currentLinks.size()) { + surface.transBlitFrom(_sprites->getSurface(242), boxes.getBoxOrigin(2), _sprites->getKeyColor(242)); + } else { + surface.transBlitFrom(_sprites->getSurface(244), boxes.getBoxOrigin(2), _sprites->getKeyColor(244)); + } +} + +unsigned int Versailles_Documentation::handlePopupMenu(const Graphics::ManagedSurface + &originalSurface, + const Common::Point &anchor, bool rightAligned, unsigned int itemHeight, + const Common::StringArray &items) { + + unsigned int maxTextWidth = 0; + + _fontManager->setCurrentFont(4); + _fontManager->setTransparentBackground(true); + _fontManager->setCharSpacing(1); + + for (Common::StringArray::const_iterator it = items.begin(); it != items.end(); it++) { + unsigned int width = _fontManager->getStrWidth(*it); + if (width > maxTextWidth) { + maxTextWidth = width; + } + } + + unsigned int width = maxTextWidth + 2 * kPopupMenuMargin; + unsigned int height = itemHeight * items.size() + 2 * kPopupMenuMargin; + + unsigned int hiddenItems = 0; + int top = anchor.y - height; + while (top < 0) { + hiddenItems++; + top += itemHeight; + } + unsigned shownItems = items.size() - hiddenItems; + + Common::Rect popupRect; + if (rightAligned) { + popupRect = Common::Rect(anchor.x - width, top, anchor.x, anchor.y); + } else { + popupRect = Common::Rect(anchor.x, top, anchor.x + width, anchor.y); + } + + Graphics::ManagedSurface surface; + surface.copyFrom(originalSurface); + + MouseBoxes boxes(shownItems); + for (unsigned int i = 0; i < shownItems; i++) { + boxes.setupBox(i, popupRect.left + kPopupMenuMargin, + popupRect.top + kPopupMenuMargin + i * itemHeight, + popupRect.right - kPopupMenuMargin, + popupRect.top + kPopupMenuMargin + (i + 1) * itemHeight); + } + + _fontManager->setSurface(&surface); + + bool fullRedraw = true; + bool redraw = true; + unsigned int hoveredBox = -1; + unsigned int action = -1; + unsigned int lastShownItem = items.size() - 1; + unsigned int firstShownItem = lastShownItem - shownItems + 1; + + unsigned int slowScrollNextEvent = g_system->getMillis() + 250; + + Common::Point mouse; + + while (action == -1u) { + if (redraw) { + if (fullRedraw) { + surface.fillRect(popupRect, 247); + fullRedraw = false; + } + for (unsigned int i = 0; i < shownItems; i++) { + if (i == 0 && firstShownItem != 0) { + // There are items before the first one: display an arrow + surface.transBlitFrom(_sprites->getSurface(162), + Common::Point(popupRect.left + kPopupMenuMargin, + popupRect.top + kPopupMenuMargin + i * itemHeight + 3), + _sprites->getKeyColor(162)); + } else if (i == shownItems - 1 && lastShownItem != items.size() - 1) { + // There are items after the last one: display an arrow + surface.transBlitFrom(_sprites->getSurface(185), + Common::Point(popupRect.left + kPopupMenuMargin, + popupRect.top + kPopupMenuMargin + i * itemHeight + 3), + _sprites->getKeyColor(185)); + } else { + // Display the item text + _fontManager->setForeColor(i == hoveredBox ? 241 : 243); + _fontManager->displayStr(popupRect.left + kPopupMenuMargin, + popupRect.top + kPopupMenuMargin + i * itemHeight + 3, items[firstShownItem + i]); + } + } + g_system->copyRectToScreen(surface.getPixels(), surface.pitch, 0, 0, surface.w, surface.h); + redraw = false; + } + g_system->updateScreen(); + + if (_engine->pollEvents()) { + if (g_engine->shouldQuit()) { + // Fake the quit + break; + } + mouse = _engine->getMousePos(); + + unsigned int newHovered = -1; + for (unsigned int i = 0; i < shownItems; i++) { + if (boxes.hitTest(i, mouse)) { + newHovered = i; + break; + } + } + + if (newHovered != hoveredBox) { + hoveredBox = newHovered; + redraw = true; + } + } + + DragStatus dragStatus = _engine->getDragStatus(); + + if (hoveredBox == -1u) { + if (dragStatus == kDragStatus_Pressed) { + break; + } else { + continue; + } + } + + // From there we only act if there is something hovered + if (hoveredBox == 0 && firstShownItem > 0) { + // Scroll up fast + firstShownItem--; + lastShownItem--; + slowScrollNextEvent = g_system->getMillis() + 250; + fullRedraw = true; + redraw = true; + } else if (hoveredBox == 1 && firstShownItem > 0) { + // Scroll up slow + if (g_system->getMillis() > slowScrollNextEvent) { + firstShownItem--; + lastShownItem--; + slowScrollNextEvent = g_system->getMillis() + 250; + fullRedraw = true; + redraw = true; + } + } else if (hoveredBox == shownItems - 2 && lastShownItem < items.size() - 1) { + // Scroll down slow + if (g_system->getMillis() > slowScrollNextEvent) { + firstShownItem++; + lastShownItem++; + slowScrollNextEvent = g_system->getMillis() + 250; + fullRedraw = true; + redraw = true; + } + } else if (hoveredBox == shownItems - 1 && lastShownItem < items.size() - 1) { + // Scroll down fast + firstShownItem++; + lastShownItem++; + slowScrollNextEvent = g_system->getMillis() + 250; + fullRedraw = true; + redraw = true; + } else if (dragStatus == kDragStatus_Finished) { + action = hoveredBox + firstShownItem; + continue; + } + } + + // Restore original surface + g_system->copyRectToScreen(originalSurface.getPixels(), originalSurface.pitch, 0, 0, + originalSurface.w, originalSurface.h); + g_system->updateScreen(); + + _engine->waitMouseRelease(); + + return action; +} + +/* Below is documentation files parsing */ + +char *Versailles_Documentation::getDocPartAddress(char *start, char *end, const char *patterns[]) { + if (!start) { + return nullptr; + } + char *foundPos = nullptr; + const char *pattern; + unsigned int patternLen; + for (const char **patternP = patterns; *patternP && !foundPos; patternP++) { + pattern = *patternP; + patternLen = strlen(pattern); + /*debug("Matching %.10s", pattern);*/ + for (char *p = start; p < end - patternLen - 1; p++) { + /*if (p == start || *p == '\r' || *p == '\0') { + debug("Line %.10s", p == start ? start : p+1); + }*/ + if (p == start && !strncmp(p, pattern, patternLen)) { + foundPos = p; + break; + } else if ((*p == '\r' || *p == '\0') && !strncmp(p + 1, pattern, patternLen)) { + foundPos = p + 1; + break; + } + } + } + if (!foundPos) { + return nullptr; + } + /*debug("Matched %.10s", foundPos);*/ + foundPos += patternLen; + char *eol = foundPos; + for (; *eol != '\r' && *eol != '\0'; eol++) {} + *eol = '\0'; + return foundPos; +} + +static bool hasEqualInLine(const char *text, const char *end) { + for (; text < end && *text && *text != '\r' && *text != '='; text++) { } + return text < end && *text == '='; +} + +static const char *nextLine(const char *text, const char *end) { + for (; text < end && *text && *text != '\r'; text++) { } + return text < end ? text + 1 : end; +} + +const char *Versailles_Documentation::getDocTextAddress(char *start, char *end) { + if (!start) { + return nullptr; + } + const char *foundPos = nullptr; + const char *p = start; + while (p < end) { + if (hasEqualInLine(p, end)) { + p = nextLine(p, end); + if (p < end && !hasEqualInLine(p, end)) { + // Only return the text that is after the last = + foundPos = p; + } + } else { + p = nextLine(p, end); + } + } + return foundPos; +} + +const char *Versailles_Documentation::getRecordCaption(char *start, char *end) { + const char *patterns[] = { "LEGENDE=", "LEGENDE =", nullptr }; + const char *ret = getDocPartAddress(start, end, patterns); + return ret; +} + +const char *Versailles_Documentation::getRecordTitle(char *start, char *end) { + const char *patterns[] = { "TITRE=", "TITRE =", nullptr }; + const char *ret = getDocPartAddress(start, end, patterns); + return ret; +} + +const char *Versailles_Documentation::getRecordSubtitle(char *start, char *end) { + const char *patterns[] = { "SOUS-TITRE=", "SOUS_TITRE=", "SOUS-TITRE =", "SOUS_TITRE =", "SOUS TITRE=", nullptr }; + char *ret = getDocPartAddress(start, end, patterns); + if (!ret) { + return nullptr; + } + + unsigned int ln = strlen(ret); + char *p = ret + ln + 1; // Got to end of line and check next line + for (; p < end && *p && *p != '\r' && *p != '=' ; p++) { } + if (*p == '=') { + // Next line has a =, so it's not multiline + return ret; + } + + if (*p == '\r') { + *p = '\0'; + } + ret[ln] = '\r'; + + return ret; +} + +void Versailles_Documentation::getRecordHyperlinks(char *start, char *end, + Common::StringArray &hyperlinks) { + const char *const hyperlinksPatterns[] = { "SAVOIR-PLUS 1=", "SAVOIR-PLUS 2=", "SAVOIR-PLUS 3=" }; + + hyperlinks.clear(); + for (unsigned int hyperlinkId = 0; hyperlinkId < ARRAYSIZE(hyperlinksPatterns); hyperlinkId++) { + const char *patterns[] = { hyperlinksPatterns[hyperlinkId], nullptr }; + const char *ret = getDocPartAddress(start, end, patterns); + if (ret) { + hyperlinks.push_back(ret); + } + } +} + +Common::String Versailles_Documentation::getRecordTitle(const Common::String &record) { + Common::HashMap<Common::String, RecordInfo>::iterator it = _records.find(record); + if (it == _records.end()) { + return ""; + } + + const RecordInfo &recordInfo = it->_value; + Common::File allDocsFile; + + if (!allDocsFile.open(kAllDocsFile)) { + error("Can't open %s", kAllDocsFile); + } + allDocsFile.seek(recordInfo.position); + + char *recordData = new char[recordInfo.size + 1]; + allDocsFile.read(recordData, recordInfo.size); + recordData[recordInfo.size] = '\0'; + char *recordDataEnd = recordData + recordInfo.size + 1; + + Common::String title = getRecordTitle(recordData, recordDataEnd); + + delete[] recordData; + + return title; +} + +Common::String Versailles_Documentation::getRecordData(const Common::String &record, + Common::String &title, Common::String &subtitle, Common::String &caption, + Common::StringArray &hyperlinks) { + Common::HashMap<Common::String, RecordInfo>::iterator it = _records.find(record); + if (it == _records.end()) { + warning("Can't find %s record data", record.c_str()); + return ""; + } + + const RecordInfo &recordInfo = it->_value; + Common::File allDocsFile; + + if (!allDocsFile.open(kAllDocsFile)) { + error("Can't open %s", kAllDocsFile); + } + allDocsFile.seek(recordInfo.position); + + char *recordData = new char[recordInfo.size + 1]; + allDocsFile.read(recordData, recordInfo.size); + recordData[recordInfo.size] = '\0'; + char *recordDataEnd = recordData + recordInfo.size + 1; + + const char *titleP = getRecordTitle(recordData, recordDataEnd); + /*debug("Title: %s", titleP);*/ + title = titleP ? titleP : ""; + const char *subtitleP = getRecordSubtitle(recordData, recordDataEnd); + /*debug("SubTitle: %s", subtitleP);*/ + subtitle = subtitleP ? subtitleP : ""; + const char *captionP = getRecordCaption(recordData, recordDataEnd); + /*debug("Caption: %s", captionP);*/ + caption = captionP ? captionP : ""; + getRecordHyperlinks(recordData, recordDataEnd, hyperlinks); + + Common::String text(getDocTextAddress(recordData, recordDataEnd)); + + delete[] recordData; + + return text; +} + +void Versailles_Documentation::convertHyperlinks(const Common::StringArray &hyperlinks, + Common::Array<LinkInfo> &links) { + for (Common::StringArray::const_iterator it = hyperlinks.begin(); it != hyperlinks.end(); it++) { + LinkInfo link; + link.record = *it; + link.record.toUppercase(); + link.title = getRecordTitle(link.record); + links.push_back(link); + } +} + +void Versailles_Documentation::loadLinksFile() { + if (_linksData) { + return; + } + + Common::File linksFile; + if (!linksFile.open(kLinksDocsFile)) { + error("Can't open links file: %s", kLinksDocsFile); + } + + _linksSize = linksFile.size(); + _linksData = new char[_linksSize + 1]; + + linksFile.read(_linksData, _linksSize); + _linksData[_linksSize] = '\0'; +} + +void Versailles_Documentation::getLinks(const Common::String &record, + Common::Array<LinkInfo> &links) { + loadLinksFile(); + + links.clear(); + + Common::String pattern = "\r"; + pattern += record; + + const char *recordStart = strstr(_linksData, pattern.c_str()); + if (!recordStart) { + return; + } + + const char *p = recordStart + pattern.size(); // Go beyond the record name + for (; *p != '\r' && *p != '\0'; p++) { } // Goto next line + if (!*p) { + return; + } + p++; + + bool finished = false; + while (!finished) { + if (!scumm_strnicmp(p, "REM=", 4)) { + // Comment: goto next line + for (; *p != '\r' && *p != '\0'; p++) { } + } else if (!scumm_strnicmp(p, "LIEN=", 5)) { + // Link: read it + const char *linkStart = p + 5; + const char *linkEnd = linkStart; + for (; *linkEnd != '\r' && *linkEnd != ' ' && *linkEnd != '\0'; linkEnd++) { } + LinkInfo link; + link.record = Common::String(linkStart, linkEnd); + link.record.toUppercase(); + link.title = getRecordTitle(link.record); + links.push_back(link); + // Advance to end of link and finish the line + p = linkEnd; + for (; *p != '\r' && *p != '\0'; p++) { } + //debug("Link %s/%s", link.record.c_str(), link.title.c_str()); + } else { + // Something else: we expect a blank line to continue, else we are on a new record + for (; *p != '\r' && *p != '\0'; p++) { + if (*p != ' ' && *p != '\t' && *p != '\n') { + finished = true; + break; + } + } + } + if (*p == '\0') { + break; + } + p++; + } +} + +} // End of namespace Versailles +} // End of namespace CryOmni3D diff --git a/engines/cryomni3d/versailles/documentation.h b/engines/cryomni3d/versailles/documentation.h new file mode 100644 index 0000000000..0a581d9822 --- /dev/null +++ b/engines/cryomni3d/versailles/documentation.h @@ -0,0 +1,143 @@ +/* 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 CRYOMNI3D_VERSAILLES_DOCUMENTATION_H +#define CRYOMNI3D_VERSAILLES_DOCUMENTATION_H + +#include "common/hashmap.h" +#include "common/hash-str.h" +#include "common/rect.h" +#include "common/str-array.h" +#include "graphics/managed_surface.h" + +namespace CryOmni3D { +class FontManager; +class MouseBoxes; +class Sprites; + +class CryOmni3DEngine; + +namespace Versailles { +class Versailles_Documentation { +public: + Versailles_Documentation() : _engine(nullptr), _fontManager(nullptr), _messages(nullptr), + _linksData(nullptr), _linksSize(0) { } + ~Versailles_Documentation() { delete _linksData; } + + void init(const Sprites *sprites, FontManager *fontManager, const Common::StringArray *messages, + CryOmni3DEngine *engine); + void handleDocArea(); + void handleDocInGame(const Common::String &record); + +private: + Common::String docAreaHandleSummary(); + Common::String docAreaHandleTimeline(); + Common::String docAreaHandleGeneralMap(); + Common::String docAreaHandleCastleMap(); + unsigned int docAreaHandleRecords(const Common::String &record); + + void docAreaPrepareNavigation(); + void docAreaPrepareRecord(Graphics::ManagedSurface &surface, MouseBoxes &boxes); + unsigned int docAreaHandleRecord(Graphics::ManagedSurface &surface, MouseBoxes &boxes, + Common::String &nextRecord); + + void inGamePrepareRecord(Graphics::ManagedSurface &surface, MouseBoxes &boxes); + unsigned int inGameHandleRecord(Graphics::ManagedSurface &surface, MouseBoxes &boxes, + Common::String &nextRecord); + + void setupRecordBoxes(bool inDocArea, MouseBoxes &boxes); + void setupTimelineBoxes(MouseBoxes &boxes); + void drawRecordData(Graphics::ManagedSurface &surface, + const Common::String &text, const Common::String &title, + const Common::String &subtitle, const Common::String &caption); + void drawRecordBoxes(Graphics::ManagedSurface &surface, bool inDocArea, MouseBoxes &boxes); + + unsigned int handlePopupMenu(const Graphics::ManagedSurface &surface, + const Common::Point &anchor, bool rightAligned, unsigned int itemHeight, + const Common::StringArray &items); + + struct RecordInfo { + unsigned int id; + unsigned int position; + unsigned int size; + }; + + struct LinkInfo { + Common::String record; + Common::String title; + }; + + struct TimelineEntry { + char year[8]; + unsigned int x; + unsigned int y; + }; + static const TimelineEntry kTimelineEntries[]; + + static char *getDocPartAddress(char *start, char *end, const char *patterns[]); + static const char *getDocTextAddress(char *start, char *end); + static const char *getRecordTitle(char *start, char *end); + static const char *getRecordSubtitle(char *start, char *end); + static const char *getRecordCaption(char *start, char *end); + static void getRecordHyperlinks(char *start, char *end, Common::StringArray &hyperlinks); + + Common::String getRecordTitle(const Common::String &record); + Common::String getRecordData(const Common::String &record, Common::String &title, + Common::String &subtitle, Common::String &caption, + Common::StringArray &hyperlinks); + void convertHyperlinks(const Common::StringArray &hyperlinks, Common::Array<LinkInfo> &links); + + void loadLinksFile(); + void getLinks(const Common::String &record, Common::Array<LinkInfo> &links); + + static const char *kAllDocsFile; + static const char *kLinksDocsFile; + + static const unsigned int kPopupMenuMargin = 5; + + CryOmni3DEngine *_engine; + FontManager *_fontManager; + const Sprites *_sprites; + const Common::StringArray *_messages; + + Common::StringArray _recordsOrdered; + Common::HashMap<Common::String, RecordInfo> _records; + char *_linksData; + unsigned int _linksSize; + + Common::Array<LinkInfo> _allLinks; + + Common::StringArray _visitTrace; + Common::String _currentRecord; + Common::String _categoryStartRecord; + Common::String _categoryEndRecord; + Common::String _categoryTitle; + Common::Array<LinkInfo> _currentLinks; + bool _currentInTimeline; + bool _currentMapLayout; + bool _currentHasMap; +}; + +} // End of namespace Versailles +} // End of namespace CryOmni3D + +#endif diff --git a/engines/cryomni3d/versailles/engine.cpp b/engines/cryomni3d/versailles/engine.cpp new file mode 100644 index 0000000000..b25efc7719 --- /dev/null +++ b/engines/cryomni3d/versailles/engine.cpp @@ -0,0 +1,1443 @@ +/* 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 "common/config-manager.h" +#include "common/system.h" +#include "common/error.h" +#include "common/file.h" +#include "common/rect.h" +#include "engines/util.h" +#include "image/bmp.h" + +#include "cryomni3d/font_manager.h" +#include "cryomni3d/dialogs_manager.h" +#include "cryomni3d/fixed_image.h" +#include "cryomni3d/omni3d.h" + +#include "cryomni3d/versailles/engine.h" + +#define DEBUG_FAST_START 1 + +namespace CryOmni3D { +namespace Versailles { + +const FixedImageConfiguration CryOmni3DEngine_Versailles::kFixedImageConfiguration = { + 45, 223, 243, 238, 226, 198, 136, 145, 99, 113, + 470 +}; + +CryOmni3DEngine_Versailles::CryOmni3DEngine_Versailles(OSystem *syst, + const CryOmni3DGameDescription *gamedesc) : CryOmni3DEngine(syst, gamedesc), + _mainPalette(nullptr), _cursorPalette(nullptr), _transparentPaletteMap(nullptr), + _currentPlace(nullptr), _currentWarpImage(nullptr), _fixedImage(nullptr), + _transitionAnimateWarp(true), _forceRedrawWarp(false), _forcePaletteUpdate(false), + _fadedPalette(false), _loadedSave(-1), _dialogsMan(this), + _musicVolumeFactor(1.), _musicCurrentFile(nullptr) { +} + +CryOmni3DEngine_Versailles::~CryOmni3DEngine_Versailles() { + delete _currentWarpImage; + delete[] _mainPalette; + delete[] _cursorPalette; + delete[] _transparentPaletteMap; + + delete _fixedImage; +} + +Common::Error CryOmni3DEngine_Versailles::run() { + CryOmni3DEngine::run(); + + const Common::FSNode gameDataDir(ConfMan.get("path")); + SearchMan.addSubDirectoryMatching(gameDataDir, "datas_v/sc_trans", 1); + SearchMan.addSubDirectoryMatching(gameDataDir, "datas_v/menu", 1); + SearchMan.addSubDirectoryMatching(gameDataDir, "datas_v/basedoc/fonds", 1); + SearchMan.addSubDirectoryMatching(gameDataDir, "datas_v/fonts", 1); + SearchMan.addSubDirectoryMatching(gameDataDir, "datas_v/spr8col", 1); + SearchMan.addSubDirectoryMatching(gameDataDir, "datas_v/spr8col/bmpok", 1); + SearchMan.addSubDirectoryMatching(gameDataDir, "datas_v/wam", 1); + SearchMan.addSubDirectoryMatching(gameDataDir, "datas_v/objets", 1); + SearchMan.addSubDirectoryMatching(gameDataDir, "datas_v/gto", 1); + SearchMan.addSubDirectoryMatching(gameDataDir, "datas_v/dial/flc_dial", 1); + SearchMan.addSubDirectoryMatching(gameDataDir, "datas_v/dial/voix", 1); + SearchMan.addSubDirectoryMatching(gameDataDir, "datas_v/textes", 1); + SearchMan.addSubDirectoryMatching(gameDataDir, "datas_v/music", 1); + SearchMan.addSubDirectoryMatching(gameDataDir, "datas_v/sound", 1); + + setupMessages(); + + _dialogsMan.init(138, _messages[22]); + _gameVariables.resize(GameVariables::kMax); + _omni3dMan.init(75. / 180. * M_PI); + + _dialogsMan.loadGTO("DIALOG1.GTO"); + setupDialogVariables(); + setupDialogShows(); + + setupPaintingsTitles(); + setupImgScripts(); + + _mainPalette = new byte[3 * 256]; + setupFonts(); + setupSprites(); + loadCursorsPalette(); + + // Objects need messages and sprites + setupObjects(); + + _transparentPaletteMap = new byte[256]; + _transparentSrcStart = 0; + _transparentSrcStop = 240; + _transparentDstStart = 0; + _transparentDstStop = 248; + _transparentNewStart = 248; + _transparentNewStop = 254; + + // Inventory has a size of 50 + _inventory.init(50, new Common::Functor1Mem<unsigned int, void, Toolbar>(&_toolbar, + &Toolbar::inventoryChanged)); + + // Init toolbar after we have setup sprites and fonts + _toolbar.init(&_sprites, &_fontManager, &_messages, &_inventory, this); + + _fixedImage = new ZonFixedImage(*this, _inventory, _sprites, &kFixedImageConfiguration); + + // Documentation is needed by noone at init time, let's do it last + initDocPeopleRecord(); + _docManager.init(&_sprites, &_fontManager, &_messages, this); + + initGraphics(640, 480); + setMousePos(Common::Point(320, 200)); + + _isPlaying = false; + _isVisiting = false; + +#if !defined(DEBUG_FAST_START) || DEBUG_FAST_START<1 + playTransitionEndLevel(-2); + if (g_engine->shouldQuit()) { + return Common::kNoError; + } + playTransitionEndLevel(-1); + if (g_engine->shouldQuit()) { + return Common::kNoError; + } +#endif + + bool stopGame = false; + while (!stopGame) { + bool exitLoop = false; + unsigned int nextStep = 0; +#if defined(DEBUG_FAST_START) && DEBUG_FAST_START>=2 + nextStep = 27; + // Called in options + syncOmni3DSettings(); +#endif + setCursor(181); + while (!exitLoop) { + _isPlaying = false; + if (!nextStep) { + nextStep = displayOptions(); + } + if (nextStep == 40) { + // Quit action + exitLoop = true; + stopGame = true; + } else if (nextStep == 27 || nextStep == 28 || nextStep == 65) { + // New game, Load game, Next level + if (nextStep == 27) { + // New game +#if !defined(DEBUG_FAST_START) || DEBUG_FAST_START<1 + playTransitionEndLevel(0); + if (g_engine->shouldQuit()) { + stopGame = true; + exitLoop = true; + break; + } +#endif + changeLevel(1); + } else if (nextStep == 28) { + // Load game + loadGame(_isVisiting, _loadedSave); + } else if (nextStep == 65) { + changeLevel(_currentLevel + 1); + } + + _isPlaying = true; + _toolbar.setInventoryEnabled(!_isVisiting); + nextStep = 0; + _abortCommand = AbortNoAbort; + + gameStep(); + + // We shouldn't return from gameStep without an abort command + assert(_abortCommand != AbortNoAbort); + switch (_abortCommand) { + default: + error("Invalid abortCommand: %d", _abortCommand); + // Shouldn't return + return Common::kUnknownError; + case AbortFinished: + case AbortGameOver: + // Go back to menu + exitLoop = true; + break; + case AbortQuit: + exitLoop = true; + stopGame = true; + break; + case AbortNewGame: + nextStep = 27; + break; + case AbortLoadGame: + nextStep = 28; + break; + case AbortNextLevel: + nextStep = 65; + break; + } + } + } + } + return Common::kNoError; +} + +void CryOmni3DEngine_Versailles::setupFonts() { + Common::Array<Common::String> fonts; + fonts.push_back("font01.CRF"); + fonts.push_back("font02.CRF"); + fonts.push_back("font03.CRF"); + fonts.push_back("font04.CRF"); + fonts.push_back("font05.CRF"); + fonts.push_back("font06.CRF"); + fonts.push_back("font07.CRF"); + fonts.push_back("font08.CRF"); + fonts.push_back("font09.CRF"); + fonts.push_back("font10.CRF"); + fonts.push_back("font11.CRF"); + + _fontManager.loadFonts(fonts); +} + +void CryOmni3DEngine_Versailles::setupSprites() { + Common::File file; + + if (!file.open("all_spr.bin")) { + error("Failed to open all_spr.bin file"); + } + _sprites.loadSprites(file); + + for (unsigned int i = 0; i < _sprites.getSpritesCount(); i++) { + const Graphics::Cursor &cursor = _sprites.getCursor(i); + if (cursor.getWidth() != 32 || cursor.getHeight() != 32) { + _sprites.setSpriteHotspot(i, 8, 8); + } else { + _sprites.setSpriteHotspot(i, 16, 16); + } + } + _sprites.setupMapTable(kSpritesMapTable, kSpritesMapTableSize); + + + _sprites.setSpriteHotspot(181, 4, 0); + // Replace 2-keys, 3-keys and 4-keys icons with 1-key ones + _sprites.replaceSprite(80, 64); + _sprites.replaceSprite(84, 66); + _sprites.replaceSprite(93, 78); + _sprites.replaceSprite(97, 82); + _sprites.replaceSprite(92, 64); + _sprites.replaceSprite(96, 66); + _sprites.replaceSprite(116, 78); + _sprites.replaceSprite(121, 82); + _sprites.replaceSprite(115, 64); + _sprites.replaceSprite(120, 66); + _sprites.replaceSprite(135, 78); + _sprites.replaceSprite(140, 82); +} + +void CryOmni3DEngine_Versailles::loadCursorsPalette() { + Image::BitmapDecoder bmpDecoder; + + Common::File file; + + if (!file.open("bou1_cA.bmp")) { + error("Failed to open BMP file"); + } + + if (!bmpDecoder.loadStream(file)) { + error("Failed to load BMP file"); + } + + _cursorPalette = new byte[3 * (bmpDecoder.getPaletteColorCount() + + bmpDecoder.getPaletteStartIndex())]; + memset(_cursorPalette, 0, 3 * (bmpDecoder.getPaletteColorCount() + + bmpDecoder.getPaletteStartIndex())); + memcpy(_cursorPalette + 3 * bmpDecoder.getPaletteStartIndex(), bmpDecoder.getPalette(), + 3 * bmpDecoder.getPaletteColorCount()); +} + +void CryOmni3DEngine_Versailles::setupPalette(const byte *palette, uint start, uint num, + bool commit) { + memcpy(_mainPalette + 3 * start, palette, 3 * num); + copySubPalette(_mainPalette, _cursorPalette, 240, 8); + + calculateTransparentMapping(); + if (commit) { + setPalette(_mainPalette, 0, 256); + } +} + +struct transparentScore { + unsigned int score; + byte redScaled; + byte greenScaled; + + int dScore(transparentScore &other) { return abs((int)score - (int)other.score); } + int dRed(transparentScore &other) { return abs((int)redScaled - (int)other.redScaled); } + int dGreen(transparentScore &other) { return abs((int)greenScaled - (int)other.greenScaled); } +}; + +static transparentScore transparentCalculateScore(byte red, byte green, byte blue) { + transparentScore ret; + ret.score = 10 * (blue + 3 * (red + 2 * green)) / 30; + if (ret.score) { + ret.redScaled = ((unsigned int)red) * 256 / ret.score; + ret.greenScaled = ((unsigned int)green) * 256 / ret.score; + } else { + ret.redScaled = 0; + ret.greenScaled = 0; + } + return ret; +} + +void CryOmni3DEngine_Versailles::calculateTransparentMapping() { + // Calculate colors proximity array + transparentScore *proximities = new transparentScore[256]; + + for (unsigned int i = _transparentSrcStart; i < _transparentSrcStop; i++) { + proximities[i] = transparentCalculateScore(_mainPalette[3 * i + 0], _mainPalette[3 * i + 1], + _mainPalette[3 * i + 2]); + } + + unsigned int newColorsNextId = _transparentNewStart; + unsigned int newColorsCount = 0; + for (unsigned int i = _transparentDstStart; i < _transparentDstStop; i++) { + byte transparentRed = ((unsigned int)_mainPalette[3 * i + 0]) * 60 / 128; + byte transparentGreen = ((unsigned int)_mainPalette[3 * i + 1]) * 50 / 128; + byte transparentBlue = ((unsigned int)_mainPalette[3 * i + 2]) * 35 / 128; + + // Find nearest color + transparentScore newColorScore = transparentCalculateScore(transparentRed, transparentGreen, + transparentBlue); + unsigned int distanceMin = -1u; + unsigned int nearestId = -1u; + for (unsigned int j = _transparentSrcStart; j < _transparentSrcStop; j++) { + if (j != i && newColorScore.dScore(proximities[j]) < 15) { + unsigned int distance = newColorScore.dRed(proximities[j]) + newColorScore.dGreen(proximities[j]); + if (distance < distanceMin) { + distanceMin = distance; + nearestId = j; + } + } + } + + if (nearestId == -1u) { + // Couldn't find a good enough color, try to create one + if (_transparentNewStart != -1u && newColorsNextId <= _transparentNewStop) { + _mainPalette[3 * newColorsNextId + 0] = transparentRed; + _mainPalette[3 * newColorsNextId + 1] = transparentGreen; + _mainPalette[3 * newColorsNextId + 2] = transparentBlue; + nearestId = newColorsNextId; + + newColorsCount++; + newColorsNextId++; + } + } + + if (nearestId == -1u) { + // Couldn't allocate a new color, return the original one + nearestId = i; + } + + _transparentPaletteMap[i] = nearestId; + } + + delete[] proximities; +} + +void CryOmni3DEngine_Versailles::makeTranslucent(Graphics::Surface &dst, + const Graphics::Surface &src) const { + assert(dst.w == src.w && dst.h == src.h); + + const byte *srcP = (const byte *) src.getPixels(); + byte *dstP = (byte *) dst.getPixels(); + for (unsigned int y = 0; y < dst.h; y++) { + for (unsigned int x = 0; x < dst.w; x++) { + dstP[x] = _transparentPaletteMap[srcP[x]]; + } + dstP += dst.pitch; + srcP += src.pitch; + } +} + +bool CryOmni3DEngine_Versailles::hasPlaceDocumentation() { + return _placeStates[_currentPlaceId].docImage != nullptr; +} + +bool CryOmni3DEngine_Versailles::displayPlaceDocumentation() { + if (!_placeStates[_currentPlaceId].docImage) { + return false; + } + + _docManager.handleDocInGame(_placeStates[_currentPlaceId].docImage); + return true; +} + +void CryOmni3DEngine_Versailles::syncOmni3DSettings() { + ConfMan.registerDefault("omni3d_speed", 0); + int confOmni3DSpeed = ConfMan.getInt("omni3d_speed"); + if (confOmni3DSpeed == 0) { + _omni3dSpeed = 0; + } else if (confOmni3DSpeed == 1) { + _omni3dSpeed = 2; + } else if (confOmni3DSpeed == 2) { + _omni3dSpeed = 4; + } else if (confOmni3DSpeed == 3) { + _omni3dSpeed = -1; + } else if (confOmni3DSpeed == 4) { + _omni3dSpeed = -2; + } else { + // Invalid value + _omni3dSpeed = 0; + } +} + +void CryOmni3DEngine_Versailles::syncSoundSettings() { + CryOmni3DEngine::syncSoundSettings(); + + int soundVolumeMusic = ConfMan.getInt("music_volume") / _musicVolumeFactor; + + bool mute = false; + if (ConfMan.hasKey("mute")) { + mute = ConfMan.getBool("mute"); + } + + bool musicMute = mute || ConfMan.getBool("music_mute"); + + _mixer->muteSoundType(Audio::Mixer::kMusicSoundType, musicMute); + _mixer->setVolumeForSoundType(Audio::Mixer::kMusicSoundType, soundVolumeMusic); +} + +void CryOmni3DEngine_Versailles::playTransitionEndLevel(int level) { + musicStop(); + _gameVariables[GameVariables::kWarnedIncomplete] = 0; + + Common::String video; + + unlockPalette(); + switch (level) { + case -2: + video = "logo.hnm"; + break; + case -1: + video = "a0_vf.hns"; + break; + case 0: + video = "a1_vf.hns"; + break; + case 1: + video = "a2_vf.hns"; + break; + case 2: + video = "a3_vf.hns"; + _inventory.removeByNameId(96); + _inventory.removeByNameId(104); + break; + case 3: + video = "a4_vf.hns"; + break; + case 4: + video = "a5_vf.hns"; + _inventory.removeByNameId(101); + _inventory.removeByNameId(127); + _inventory.removeByNameId(129); + _inventory.removeByNameId(130); + _inventory.removeByNameId(131); + _inventory.removeByNameId(132); + _inventory.removeByNameId(126); + break; + case 5: + video = "a6_vf.hns"; + _inventory.removeByNameId(115); + break; + case 6: + video = "a7_vf.hns"; + break; + case 7: + video = "a9_vf.hns"; + break; + case 8: + video = "a8_vf.hns"; + break; + default: + error("Invalid level : %d", level); + } + + fadeOutPalette(); + if (g_engine->shouldQuit()) { + _abortCommand = AbortQuit; + return; + } + + fillSurface(0); + + // Videos are like music because if you mute music in game it will mute videos soundtracks + playHNM(video, Audio::Mixer::kMusicSoundType); + clearKeys(); + if (g_engine->shouldQuit()) { + _abortCommand = AbortQuit; + return; + } + + fadeOutPalette(); + if (g_engine->shouldQuit()) { + _abortCommand = AbortQuit; + return; + } + + fillSurface(0); + + if (level == 7 || level == 8) { + _abortCommand = AbortFinished; + } else { + _abortCommand = AbortNextLevel; + } +} + +void CryOmni3DEngine_Versailles::changeLevel(int level) { + _currentLevel = level; + + musicStop(); + _mixer->stopAll(); + + if (_currentLevel == 1) { + _dialogsMan.reinitVariables(); + for (Common::Array<unsigned int>::iterator it = _gameVariables.begin(); it != _gameVariables.end(); + it++) { + *it = 0; + } + // TODO: countdown + _inventory.clear(); + } else { + // TODO: to implement + error("New level %d is not implemented (yet)", level); + } + + _gameVariables[GameVariables::kCurrentTime] = 1; + + // keep back place state for level 2 + int place8_state_backup; + if (level == 2) { + place8_state_backup = _placeStates[8].state; + } + _nextPlaceId = -1; + initNewLevel(_currentLevel); + // restore place state for level 2 + if (level == 2) { + _placeStates[8].state = place8_state_backup; + } +} + +void CryOmni3DEngine_Versailles::initNewLevel(int level) { + // SearchMan can't add several times the same basename + // We create several SearchSet with different names that we add to SearchMan instead + + // Visiting uses all levels + SearchMan.remove("__visitFiles"); + + SearchMan.remove("__levelFiles_animacti"); + SearchMan.remove("__levelFiles_warp"); + SearchMan.remove("__levelFiles_img_fix"); + + const Common::FSNode gameDataDir(ConfMan.get("path")); + if (level >= 1 && level <= 7) { + Common::SearchSet *levelFilesAnimacti = new Common::SearchSet(); + Common::SearchSet *levelFilesWarp = new Common::SearchSet(); + Common::SearchSet *levelFilesImgFix = new Common::SearchSet(); + + levelFilesAnimacti->addSubDirectoryMatching(gameDataDir, Common::String::format( + "datas_v/animacti/level%d", level), 1); + levelFilesWarp->addSubDirectoryMatching(gameDataDir, Common::String::format( + "datas_v/warp/level%d/cyclo", level), 1); + levelFilesWarp->addSubDirectoryMatching(gameDataDir, Common::String::format( + "datas_v/warp/level%d/hnm", level), 1); + levelFilesImgFix->addSubDirectoryMatching(gameDataDir, Common::String::format( + "datas_v/img_fix/level%d", level), 1); + + SearchMan.add("__levelFiles_animacti", levelFilesAnimacti); + SearchMan.add("__levelFiles_warp", levelFilesWarp); + SearchMan.add("__levelFiles_img_fix", levelFilesImgFix); + } else if (level == 8 && _isVisiting) { + // In visit mode, we take files from all levels, happily they have unique names + // Create a first SearchSet in which we will add all others to easily cleanup the mess + Common::SearchSet *visitFiles = new Common::SearchSet(); + + for (unsigned int lvl = 1; lvl <= 7; lvl++) { + Common::SearchSet *visitFilesAnimacti = new Common::SearchSet(); + Common::SearchSet *visitFilesWarp = new Common::SearchSet(); + Common::SearchSet *visitFilesImgFix = new Common::SearchSet(); + + visitFilesAnimacti->addSubDirectoryMatching(gameDataDir, Common::String::format( + "datas_v/animacti/level%d", lvl), 1); + visitFilesWarp->addSubDirectoryMatching(gameDataDir, Common::String::format( + "datas_v/warp/level%d/cyclo", lvl), 1); + visitFilesWarp->addSubDirectoryMatching(gameDataDir, Common::String::format( + "datas_v/warp/level%d/hnm", lvl), 1); + visitFilesImgFix->addSubDirectoryMatching(gameDataDir, Common::String::format( + "datas_v/img_fix/level%d", lvl), 1); + + visitFiles->add(Common::String::format("__visitFiles_animacti_%d", lvl), visitFilesAnimacti); + visitFiles->add(Common::String::format("__visitFiles_warp_%d", lvl), visitFilesWarp); + visitFiles->add(Common::String::format("__visitFiles_img_fix_%d", lvl), visitFilesImgFix); + } + + SearchMan.add("__visitFiles", visitFiles); + } else { + error("Invalid level %d", level); + } + + // TODO: countdown + initPlacesStates(); + initWhoSpeaksWhere(); + setupLevelWarps(level); + updateGameTimeDialVariables(); + _dialogsMan["{JOUEUR-ESSAYE-OUVRIR-PORTE-SALON}"] = 'Y'; + _dialogsMan["{JOUEUR-ESSAYE-OUVRIR-PORTE-CHAMBRE}"] = 'Y'; + setupLevelActionsMask(); +} + +void CryOmni3DEngine_Versailles::setupLevelWarps(int level) { + Common::File wamFile; + Common::String wamFName = Common::String::format("level%d.wam", level); + if (!wamFile.open(wamFName)) { + error("Can't open WAM file '%s'", wamFName.c_str()); + } + _wam.loadStream(wamFile); + + const LevelInitialState &initialState = kLevelInitialStates[level - 1]; + + if (_nextPlaceId == -1u) { + _nextPlaceId = initialState.placeId; + } + _omni3dMan.setAlpha(initialState.alpha); + _omni3dMan.setBeta(initialState.beta); +} + +void CryOmni3DEngine_Versailles::setGameTime(unsigned int newTime, unsigned int level) { + if (_currentLevel != level) { + error("Level %u != current level %u", level, _currentLevel); + } + + _gameVariables[GameVariables::kCurrentTime] = newTime; + updateGameTimeDialVariables(); + + if (_currentLevel == 1) { + if (newTime == 2) { + setPlaceState(1, 1); + setPlaceState(2, 1); + _whoSpeaksWhere[PlaceActionKey(2, 11201)] = "12E_HUI"; + setPlaceState(3, 1); + } else if (newTime == 3) { + setPlaceState(2, 2); + } + } else if (_currentLevel == 2) { + if (newTime == 2) { + setPlaceState(9, 1); + _whoSpeaksWhere[PlaceActionKey(9, 52902)] = "22G_DAU"; + } else if (newTime == 4) { + setPlaceState(10, 1); + setPlaceState(11, 1); + setPlaceState(12, 1); + setPlaceState(13, 1); + } + } else if (_currentLevel == 3) { + if (newTime == 2) { + if (_placeStates[13].state) { + setPlaceState(13, 3); + } else { + setPlaceState(13, 2); + } + setPlaceState(15, 1); + setPlaceState(17, 1); + } else if (newTime == 3) { + setPlaceState(10, 1); + setPlaceState(14, 1); + } + } else if (_currentLevel == 4) { + if (newTime == 2) { + setPlaceState(7, 1); + setPlaceState(8, 1); + setPlaceState(10, 1); + setPlaceState(16, 1); + } else if (newTime == 3) { + setPlaceState(10, 2); + setPlaceState(9, 1); + } else if (newTime == 4) { + setPlaceState(9, 2); + _whoSpeaksWhere[PlaceActionKey(9, 54091)] = "4_MAI"; + _whoSpeaksWhere[PlaceActionKey(9, 14091)] = "4_MAI"; + } + } else if (_currentLevel == 5) { + if (newTime == 2) { + setPlaceState(9, 1); + setPlaceState(13, 1); + } else if (newTime == 3) { + if (!_placeStates[16].state) { + setPlaceState(16, 2); + } + } else if (newTime == 4) { + _whoSpeaksWhere[PlaceActionKey(9, 15090)] = "54I_BON"; + } + } else if (_currentLevel == 6) { + if (newTime == 2) { + setPlaceState(14, 1); + setPlaceState(19, 2); + } + } +} + +void CryOmni3DEngine_Versailles::gameStep() { + while (!_abortCommand) { + if (_nextPlaceId != -1u) { + // TODO: check selected object == -2 if needed + if (_placeStates[_nextPlaceId].initPlace) { + (this->*_placeStates[_nextPlaceId].initPlace)(); + // TODO: check selected object == -2 if needed + } + doPlaceChange(); + musicUpdate(); + } + if (_forcePaletteUpdate) { + redrawWarp(); + } + unsigned int actionId = handleWarp(); + debug("handleWarp returned %u", actionId); + // TODO: handle keyboard levels 4 and 5 + + // Get selected object there to detect when it has just been deselected + Object *selectedObject = _inventory.selectedObject(); + + _nextPlaceId = -1; + bool doEvent; + if (_placeStates[_currentPlaceId].filterEvent && !_isVisiting) { + doEvent = (this->*_placeStates[_currentPlaceId].filterEvent)(&actionId); + } else { + doEvent = true; + } + + if (_abortCommand) { + break; + } + + if (!selectedObject) { + // Execute only when no object was used before filtering event + if (actionId >= 1 && actionId < 10000) { + // Move to another place + if (doEvent) { + executeTransition(actionId); + } + } else if (actionId >= 10000 && actionId < 20000) { + // Speak + if (doEvent) { + executeSpeakAction(actionId); + // Force refresh of place + if (_nextPlaceId == -1u) { + _nextPlaceId = _currentPlaceId; + } + } + } else if (actionId >= 20000 && actionId < 30000) { + // Documentation + executeDocAction(actionId); + } else if (actionId >= 30000 && actionId < 40000) { + // Use + // In original game there is a handler for use actions but it's + // only for some events, we will implement them in the filterEvent handler + if (doEvent) { + error("Not implemented yet"); + } + } else if (actionId >= 40000 && actionId < 50000) { + // See + executeSeeAction(actionId); + } else if (actionId >= 50000 && actionId < 60000) { + // Listening + // never filtered + executeSpeakAction(actionId); + // Force refresh of place + if (_nextPlaceId == -1u) { + _nextPlaceId = _currentPlaceId; + } + } else if (actionId == 66666) { + // Abort + assert(_abortCommand != AbortNoAbort); + return; + } + } else if (!actionId) { + // Click on nothing with an object: deselect it + _inventory.setSelectedObject(nullptr); + } + // TODO: selected_object == -2 if needed + } +} + +void CryOmni3DEngine_Versailles::doGameOver() { + musicStop(); + fadeOutPalette(); + fillSurface(0); + // This test is not really relevant because it's for 2CDs edition but let's follow the code + if (_currentLevel < 4) { + playInGameVideo("1gameove"); + } else { + playInGameVideo("4gameove"); + } + fillSurface(0); + _abortCommand = AbortGameOver; +} + +void CryOmni3DEngine_Versailles::doPlaceChange() { + const Place *nextPlace = _wam.findPlaceById(_nextPlaceId); + unsigned int state = _placeStates[_nextPlaceId].state; + if (state == -1u) { + state = 0; + } + + if (state >= nextPlace->warps.size()) { + error("invalid warp %d/%d/%d", _currentLevel, _nextPlaceId, state); + } + + Common::String warpFile = nextPlace->warps[state]; + warpFile.toUppercase(); + if (warpFile.size() > 0) { + if (warpFile.hasPrefix("NOT_MOVE")) { + // Don't move so do nothing but cancel place change + _nextPlaceId = -1; + } else { + _currentPlace = nextPlace; + if (!warpFile.hasPrefix("NOT_STOP")) { + if (_currentWarpImage) { + delete _currentWarpImage; + } + debug("Loading warp %s", warpFile.c_str()); + _currentWarpImage = loadHLZ(warpFile); + if (!_currentWarpImage) { + error("Can't load warp %s", warpFile.c_str()); + } +#if 0 + // This is not correct but to debug zones I think it's OK + Graphics::Surface *tmpSurf = (Graphics::Surface *) _currentWarpImage->getSurface(); + for (Common::Array<Zone>::const_iterator it = _currentPlace->zones.begin(); + it != _currentPlace->zones.end(); it++) { + Common::Rect tmp = it->rct; + tmp.bottom = tmpSurf->h - it->rct.top; + tmp.top = tmpSurf->h - it->rct.bottom; + tmpSurf->frameRect(tmp, 244); + } +#endif + _currentPlace->setupWarpConstraints(_omni3dMan); + _omni3dMan.setSourceSurface(_currentWarpImage->getSurface()); + + setupPalette(_currentWarpImage->getPalette(), _currentWarpImage->getPaletteStartIndex(), + _currentWarpImage->getPaletteColorCount(), !_fadedPalette); + + setMousePos(Common::Point(320, 240)); // Center of screen + + _currentPlaceId = _nextPlaceId; + _nextPlaceId = -1; + } + } + } else { + error("invalid warp %d/%d/%d", _currentLevel, _nextPlaceId, state); + } +} + +void CryOmni3DEngine_Versailles::setPlaceState(unsigned int placeId, unsigned int newState) { + unsigned int numStates = _wam.findPlaceById(placeId)->getNumStates(); + unsigned int oldState = _placeStates[placeId].state; + + if (newState > numStates) { + warning("CryOmni3DEngine_Versailles::setPlaceState: newState '%d' > numStates '%d'", + newState, numStates); + return; + } + _placeStates[placeId].state = newState; + + if (_currentPlaceId == placeId && oldState != newState) { + // force reload of the place + _nextPlaceId = _currentPlaceId; + } +} + +void CryOmni3DEngine_Versailles::executeTransition(unsigned int nextPlaceId) { + const Transition *transition; + unsigned int animationId = determineTransitionAnimation(_currentPlaceId, nextPlaceId, &transition); + + _nextPlaceId = nextPlaceId; + + Common::String animation = transition->animations[animationId]; + animation.toUppercase(); + debug("Transition animation: %s", animation.c_str()); + if (animation.hasPrefix("NOT_FLI")) { + return; + } + + if (_transitionAnimateWarp) { + animateWarpTransition(transition); + } else { + _transitionAnimateWarp = true; + } + if (musicWouldChange(_currentLevel, _nextPlaceId)) { + musicStop(); + } + if (animation.hasPrefix("FADE_PAL")) { + _fadedPalette = true; + fadeOutPalette(); + } else if (animation != "") { + _fadedPalette = false; + // Normally transitions don't overwrite the cursors colors and game doesn't restore palette + playInGameVideo(animation, false); + } + + _omni3dMan.setAlpha(transition->dstAlpha); + _omni3dMan.setBeta(-transition->dstBeta); + + unsigned int nextState = _placeStates[nextPlaceId].state; + if (nextState == -1u) { + nextState = 0; + } + const Place *nextPlace = _wam.findPlaceById(nextPlaceId); + Common::String warpFile = nextPlace->warps[nextState]; + warpFile.toUppercase(); + if (warpFile.hasPrefix("NOT_STOP")) { + unsigned int transitionNum; + // Determine transition to take + if (nextPlace->getNumTransitions() == 1) { + // Only one + transitionNum = 0; + } else if (nextPlace->findTransition(_currentPlaceId) == &nextPlace->transitions[0]) { + // Don't take the transition returning to where we come from + transitionNum = 1; + } else { + transitionNum = 0; + } + unsigned int nextNextPlaceId = nextPlace->transitions[transitionNum].dstId; + + animationId = determineTransitionAnimation(nextPlaceId, nextNextPlaceId, &transition); + animation = transition->animations[animationId]; + + animation.toUppercase(); + if (animation.hasPrefix("NOT_FLI")) { + return; + } + if (animation.hasPrefix("FADE_PAL")) { + _fadedPalette = true; + fadeOutPalette(); + } else if (animation != "") { + _fadedPalette = false; + // Normally transitions don't overwrite the cursors colors and game doesn't restore palette + playInGameVideo(animation, false); + } + + _nextPlaceId = nextNextPlaceId; + + _omni3dMan.setAlpha(transition->dstAlpha); + _omni3dMan.setBeta(-transition->dstBeta); + } +} + +void CryOmni3DEngine_Versailles::fakeTransition(unsigned int dstPlaceId) { + // No need of animation, caller will take care + // We just setup the camera in good place for the caller + const Place *srcPlace = _wam.findPlaceById(_currentPlaceId); + const Transition *transition = srcPlace->findTransition(dstPlaceId); + + animateWarpTransition(transition); + + _omni3dMan.setAlpha(transition->dstAlpha); + _omni3dMan.setBeta(-transition->dstBeta); +} + +unsigned int CryOmni3DEngine_Versailles::determineTransitionAnimation(unsigned int srcPlaceId, + unsigned int dstPlaceId, const Transition **transition_) { + const Place *srcPlace = _wam.findPlaceById(srcPlaceId); + const Place *dstPlace = _wam.findPlaceById(dstPlaceId); + const Transition *transition = srcPlace->findTransition(dstPlaceId); + + if (transition_) { + *transition_ = transition; + } + + unsigned int srcNumStates = srcPlace->getNumStates(); + unsigned int dstNumStates = dstPlace->getNumStates(); + unsigned int animsNum = transition->getNumAnimations(); + + unsigned int srcState = _placeStates[srcPlaceId].state; + unsigned int dstState = _placeStates[dstPlaceId].state; + + if (srcState >= srcNumStates) { + error("Invalid src state"); + } + + if (dstState >= dstNumStates) { + error("Invalid dst state"); + } + + if (animsNum <= 1) { + return 0; + } + + if (srcNumStates == 2 && dstNumStates == 2) { + if (animsNum == 2) { + return dstState; + } else if (animsNum == 4) { + return srcState * 2 + dstState; + } + } + + if (animsNum == dstNumStates) { + return dstState; + } + + if (animsNum == srcNumStates) { + return srcState; + } + + // Other case + return 0; +} + +int CryOmni3DEngine_Versailles::handleWarp() { + bool exit = false; + bool leftButtonPressed = false; + bool firstDraw = true; + bool moving = true; + unsigned int actionId; + g_system->showMouse(true); + while (!leftButtonPressed && !exit) { + int xDelta = 0, yDelta = 0; + unsigned int movingCursor = -1; + + pollEvents(); + Common::Point mouse = getMousePos(); + + if (mouse.y < 100) { + movingCursor = 245; + yDelta = 100 - mouse.y; + } else if (mouse.y > 380) { + movingCursor = 224; + yDelta = 380 - mouse.y; + } + if (mouse.x < 100) { + movingCursor = 241; + xDelta = 100 - mouse.x; + } else if (mouse.x > 540) { + movingCursor = 228; + xDelta = 540 - mouse.x; + } + if (_omni3dSpeed > 0) { + xDelta <<= _omni3dSpeed; + yDelta <<= _omni3dSpeed; + } else if (_omni3dSpeed < 0) { + xDelta >>= -_omni3dSpeed; + yDelta >>= -_omni3dSpeed; + } + leftButtonPressed = (getCurrentMouseButton() == 1); + + Common::Point mouseRev = _omni3dMan.mapMouseCoords(mouse); + mouseRev.y = 768 - mouseRev.y; + + actionId = _currentPlace->hitTest(mouseRev); + + exit = handleWarpMouse(&actionId, movingCursor); + if (g_engine->shouldQuit()) { + _abortCommand = AbortQuit; + exit = true; + } + if (exit) { + actionId = 66666; + } + + if (firstDraw || xDelta || yDelta || _omni3dMan.hasSpeed()) { + bool useOldSpeed = false; + if (_omni3dSpeed <= 2) { + useOldSpeed = true; + } + _omni3dMan.updateCoords(xDelta, -yDelta, useOldSpeed); + + const Graphics::Surface *result = _omni3dMan.getSurface(); + g_system->copyRectToScreen(result->getPixels(), result->pitch, 0, 0, result->w, result->h); + if (!exit) { + // TODO: countdown + g_system->updateScreen(); + if (firstDraw && _fadedPalette) { + fadeInPalette(_mainPalette); + _fadedPalette = false; + } + } + moving = true; + firstDraw = false; + } else if (moving) { + const Graphics::Surface *result = _omni3dMan.getSurface(); + g_system->copyRectToScreen(result->getPixels(), result->pitch, 0, 0, result->w, result->h); + if (!exit) { + // TODO: countdown + g_system->updateScreen(); + } + // TODO: cursorUseZones + moving = false; + } else { + if (!exit) { + // TODO: countdown + g_system->updateScreen(); + } + } + if (!exit && !leftButtonPressed) { + g_system->delayMillis(50); + } + } + g_system->showMouse(false); + return actionId; +} + +bool CryOmni3DEngine_Versailles::handleWarpMouse(unsigned int *actionId, + unsigned int movingCursor) { + PlaceStateActionKey mask = PlaceStateActionKey(_currentPlaceId, _placeStates[_currentPlaceId].state, + *actionId); + *actionId = _actionMasks.getVal(mask, *actionId); + + if (getCurrentMouseButton() == 2 || + getNextKey().keycode == Common::KEYCODE_SPACE) { + // Prepare background using alpha + const Graphics::Surface *original = _omni3dMan.getSurface(); + bool mustRedraw = displayToolbar(original); + // Don't redraw if we abort game + if (_abortCommand != AbortNoAbort) { + return true; + } + if (mustRedraw) { + _forceRedrawWarp = true; + redrawWarp(); + } + // Force a cycle to recalculate the correct mouse cursor + return false; + } + + // TODO: countdown + + const Object *selectedObj = _inventory.selectedObject(); + if (selectedObj) { + if (*actionId != 0) { + setCursor(selectedObj->idSA()); + } else { + setCursor(selectedObj->idSl()); + } + } else if (*actionId >= 1 && *actionId < 10000) { + setCursor(243); + } else if (*actionId >= 10000 && *actionId < 20000) { + setCursor(113); + } else if (*actionId >= 20000 && *actionId < 30000) { + setCursor(198); + } else if (*actionId >= 30000 && *actionId < 40000) { + setCursor(99); + } else if (*actionId >= 40000 && *actionId < 50000) { + setCursor(145); + } else if (*actionId >= 50000 && *actionId < 60000) { + setCursor(136); + } else if (movingCursor != -1u) { + setCursor(movingCursor); + } else { + setCursor(45); + } + return false; +} + +void CryOmni3DEngine_Versailles::animateWarpTransition(const Transition *transition) { + double srcAlpha = transition->srcAlpha; + double srcBeta = transition->srcBeta; + + double oldDeltaAlpha = 1000., oldDeltaBeta = 1000.; + + clearKeys(); + + bool exit = false; + while (!exit) { + double deltaAlpha = 2.*M_PI - srcAlpha + _omni3dMan.getAlpha(); + if (deltaAlpha >= 2.*M_PI) { + deltaAlpha -= 2.*M_PI; + } else if (deltaAlpha < 0) { + deltaAlpha += 2.*M_PI; + } + int deltaAlphaI; + if (deltaAlpha < M_PI) { + deltaAlphaI = -(deltaAlpha * 512.); + } else { + deltaAlphaI = (2.*M_PI - deltaAlpha) * 512.; + } + + double deltaBeta = -srcBeta - _omni3dMan.getBeta(); + int deltaBetaI = -(deltaBeta * 512.); + + if (_omni3dSpeed > 0) { + deltaAlphaI <<= 2; + deltaBetaI <<= 2; + } else if (_omni3dSpeed < 0) { + deltaAlphaI >>= 2; + deltaBetaI >>= 2; + } + + _omni3dMan.updateCoords(deltaAlphaI, -deltaBetaI, false); + + const Graphics::Surface *result = _omni3dMan.getSurface(); + g_system->copyRectToScreen(result->getPixels(), result->pitch, 0, 0, result->w, result->h); + // TODO: countdown + g_system->updateScreen(); + + if (abs(oldDeltaAlpha - deltaAlpha) < 0.001 && abs(oldDeltaBeta - deltaBeta) < 0.001) { + exit = true; + } + oldDeltaAlpha = deltaAlpha; + oldDeltaBeta = deltaBeta; + + if (pollEvents() && checkKeysPressed(2, Common::KEYCODE_ESCAPE, Common::KEYCODE_SPACE)) { + exit = true; + } + + if (!exit) { + g_system->delayMillis(50); + } + } +} + +void CryOmni3DEngine_Versailles::redrawWarp() { + setupPalette(_currentWarpImage->getPalette(), _currentWarpImage->getPaletteStartIndex(), + _currentWarpImage->getPaletteColorCount(), true); + if (_forceRedrawWarp) { + const Graphics::Surface *result = _omni3dMan.getSurface(); + g_system->copyRectToScreen(result->getPixels(), result->pitch, 0, 0, result->w, result->h); + // TODO: countdown + g_system->updateScreen(); + _forceRedrawWarp = false; + } + _forcePaletteUpdate = false; +} + +void CryOmni3DEngine_Versailles::warpMsgBoxCB() { + pollEvents(); +} + +void CryOmni3DEngine_Versailles::animateCursor(const Object *obj) { + if (obj == nullptr) { + return; + } + + g_system->showMouse(true); + + for (unsigned int i = 4; i > 0; i--) { + // Wait 100ms + for (unsigned int j = 10; j > 0; j--) { + // pollEvents sleeps 10ms + pollEvents(); + g_system->updateScreen(); + } + setCursor(obj->idSA()); + g_system->updateScreen(); + // Wait 100ms + for (unsigned int j = 10; j > 0; j--) { + // pollEvents sleeps 10ms + pollEvents(); + g_system->updateScreen(); + } + setCursor(obj->idSl()); + g_system->updateScreen(); + } + + g_system->showMouse(false); +} + +void CryOmni3DEngine_Versailles::collectObject(unsigned int nameID, const ZonFixedImage *fimg, + bool showObject) { + Object *obj = _objects.findObjectByNameID(nameID); + _inventory.add(obj); + Object::ViewCallback cb = obj->viewCallback(); + if (showObject && cb) { + (*cb)(); + if (fimg) { + fimg->display(); + } else { + _forceRedrawWarp = true; + redrawWarp(); + } + } + animateCursor(obj); +} + +void CryOmni3DEngine_Versailles::displayObject(const Common::String &imgName, + DisplayObjectHook hook) { + Image::ImageDecoder *imageDecoder = loadHLZ(imgName); + if (!imageDecoder) { + error("Can't display object"); + } + + if (imageDecoder->hasPalette()) { + // We don't need to calculate transparency but it's simpler to call this function + setupPalette(imageDecoder->getPalette(), imageDecoder->getPaletteStartIndex(), + imageDecoder->getPaletteColorCount()); + } + + const Graphics::Surface *image = imageDecoder->getSurface(); + + // Duplicate image to let hook modify it + Graphics::ManagedSurface dstSurface(image->w, image->h, image->format); + dstSurface.blitFrom(*image); + + delete imageDecoder; + imageDecoder = nullptr; + + if (hook) { + (this->*hook)(dstSurface); + } + + g_system->copyRectToScreen(dstSurface.getPixels(), dstSurface.pitch, 0, 0, + dstSurface.w, dstSurface.h); + g_system->updateScreen(); + + setMousePos(Common::Point(320, 240)); // Center of screen + setCursor(181); + + g_system->showMouse(true); + + bool exitImg = false; + while (!g_engine->shouldQuit() && !exitImg) { + if (pollEvents()) { + if (getCurrentMouseButton() == 1) { + exitImg = true; + } + } + g_system->updateScreen(); + } + waitMouseRelease(); + clearKeys(); + + g_system->showMouse(false); + setMousePos(Common::Point(320, 240)); // Center of screen +} + +void CryOmni3DEngine_Versailles::executeSeeAction(unsigned int actionId) { + if (_currentLevel == 7 && _currentPlaceId != 20) { + // Not enough time for paintings + displayMessageBoxWarp(14); + return; + } + + const FixedImgCallback &cb = _imgScripts.getVal(actionId, nullptr); + if (cb != nullptr) { + handleFixedImg(cb); + } else { + warning("Image script %u not found", actionId); + } +} + +void CryOmni3DEngine_Versailles::executeSpeakAction(unsigned int actionId) { + PlaceActionKey key(_currentPlaceId, actionId); + Common::HashMap<PlaceActionKey, Common::String>::iterator it = _whoSpeaksWhere.find(key); + g_system->showMouse(true); + bool doneSth = false; + if (it != _whoSpeaksWhere.end()) { + doneSth = _dialogsMan.play(it->_value); + } + g_system->showMouse(false); + _forcePaletteUpdate = true; + if (doneSth) { + setMousePos(Common::Point(320, 240)); // Center of screen + } +} + +void CryOmni3DEngine_Versailles::executeDocAction(unsigned int actionId) { + if (_currentLevel == 7) { + // Not enough time for doc + displayMessageBoxWarp(13); + return; + } + + Common::HashMap<unsigned int, const char *>::iterator it = _docPeopleRecord.find(actionId); + if (it == _docPeopleRecord.end() || !it->_value) { + warning("Missing documentation record for action %u", actionId); + return; + } + + _docManager.handleDocInGame(it->_value); + + _forcePaletteUpdate = true; + setMousePos(Common::Point(320, 240)); // Center of screen +} + +void CryOmni3DEngine_Versailles::handleFixedImg(const FixedImgCallback &callback) { + if (!callback) { + return; + } + + ZonFixedImage::CallbackFunctor *functor = + new Common::Functor1Mem<ZonFixedImage *, void, CryOmni3DEngine_Versailles>(this, callback); + _fixedImage->run(functor); + // functor is deleted in ZoneFixedImage + functor = nullptr; + + if (_nextPlaceId == -1u) { + _forcePaletteUpdate = true; + } +} + +unsigned int CryOmni3DEngine_Versailles::getFakeTransition(unsigned int actionId) const { + for (const FakeTransitionActionPlace *ft = kFakeTransitions; ft->actionId != nullptr; ft++) { + if (ft->actionId == actionId) { + return ft->placeId; + } + } + return 0; +} + +void CryOmni3DEngine_Versailles::playInGameVideo(const Common::String &filename, + bool restoreCursorPalette) { + if (!_isPlaying) { + return; + } + + g_system->showMouse(false); + lockPalette(0, 241); + // Videos are like music because if you mute music in game it will mute videos soundtracks + playHNM(filename, Audio::Mixer::kMusicSoundType); + clearKeys(); + unlockPalette(); + if (restoreCursorPalette) { + // Restore cursors colors as 2 first ones may have been erased by the video + setPalette(&_cursorPalette[3 * 240], 240, 248); + } + g_system->showMouse(true); +} + +} // End of namespace Versailles +} // End of namespace CryOmni3D diff --git a/engines/cryomni3d/versailles/engine.h b/engines/cryomni3d/versailles/engine.h new file mode 100644 index 0000000000..4f56fd3a17 --- /dev/null +++ b/engines/cryomni3d/versailles/engine.h @@ -0,0 +1,453 @@ +/* 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 CRYOMNI3D_VERSAILLES_ENGINE_H +#define CRYOMNI3D_VERSAILLES_ENGINE_H + +#include "common/events.h" +#include "common/random.h" +#include "common/array.h" +#include "common/hashmap.h" +#include "common/str.h" + +#include "cryomni3d/cryomni3d.h" +#include "cryomni3d/omni3d.h" +#include "cryomni3d/sprites.h" +#include "cryomni3d/wam_parser.h" + +#include "cryomni3d/versailles/documentation.h" +#include "cryomni3d/versailles/toolbar.h" +#include "cryomni3d/versailles/dialogs_manager.h" + +namespace Graphics { +class ManagedSurface; +class Surface; +} + +namespace CryOmni3D { +struct FixedImageConfiguration; +class ZonFixedImage; +} + +namespace CryOmni3D { +namespace Versailles { +struct PlaceStateActionKey { + unsigned int placeId; + unsigned int placeState; + unsigned int actionId; + PlaceStateActionKey(unsigned int placeId_, unsigned int placeState_, unsigned int actionId_) : + placeId(placeId_), placeState(placeState_), actionId(actionId_) {} + + bool operator==(const PlaceStateActionKey &other) const { + return other.placeId == placeId && other.placeState == placeState && other.actionId == actionId; + } +}; + +struct PlaceActionKey { + unsigned int placeId; + unsigned int actionId; + PlaceActionKey(unsigned int placeId_, unsigned int actionId_) : + placeId(placeId_), actionId(actionId_) {} + + bool operator==(const PlaceActionKey &other) const { + return other.placeId == placeId && other.actionId == actionId; + } +}; +} +} + +namespace Common { +template<> +struct Hash<CryOmni3D::Versailles::PlaceStateActionKey> { + uint operator()(const CryOmni3D::Versailles::PlaceStateActionKey &k) const { + // placeState shouldn't be greater than 8 and placeId shouldn't be greater than 100 + // originalActionId shouldn't be greater than 65536 + return (k.placeId << 24 | k.placeState << 16) ^ k.actionId; + } +}; +template<> +struct Hash<CryOmni3D::Versailles::PlaceActionKey> { + uint operator()(const CryOmni3D::Versailles::PlaceActionKey &k) const { + // placeId shouldn't be greater than 100 + // originalActionId shouldn't be greater than 65536 + return (k.placeId << 16) ^ k.actionId; + } +}; +} + +namespace CryOmni3D { +namespace Versailles { + +class CryOmni3DEngine_Versailles; + +enum AbortCommand { + AbortNoAbort = 0, + AbortQuit = 1, + AbortLoadGame = 2, + AbortNewGame = 3, + AbortNextLevel = 5, + AbortFinished = 6, + AbortGameOver = 7 +}; + +struct GameVariables { + enum Var { + // TODO: make these enum members more correct + kCollectPartition = 0, // 0 + kUnlockPetitePorte, + kAlreadyCame31, + kDrawerStatus, + kCurrentTime, + kGotMedaillesSolution, + kDrawerFurnitureStatus, + kCollectePartition, + kCollectPamphletArchi, + kGotRevealedPaper, // OK + kCollectCle, // 10 + kCollectCartonDessin, + kEsquissePainted, + kStateFauxCroquis, + kCollectNourriture, + kCollectPlume, + kStatePamphletReligion, + kCollectPetiteCle3, + kCollectGravure, + kCollectCordon, + kCollectPlanVauban, // 20 + kCollectPlanVauban2, + kCollectEchelle, + kLostCordon, + kDescendreLustre, + kOrangerRatisse, + kDiscussedLabyrOrder, + kUsedBougieAllumee, + kStateBombe, + kInkSpilled, // OK + kCollectedPaperOnTable, // OK // 30 + kCoffreUnlocked, + //kUselessVar, + kCollectedPaperInTrunk = 33, // OK + kUsingPinceauColor, + kUsedScissors, // OK + kUsedClefsCombles, + kHasPlayedLebrun, // OK + kWarnedIncomplete, + kUsedPlanVauban1, + kUsedPlanVauban2, // 40 + kSeenMemorandum, + kCollectScissors, // OK + kSavedCountdown, // TODO: calculate it in real time + kMax + }; +}; + +// For random sounds we set a constant ID and avoid to use it elsewhere +struct SoundIds { + enum { + kOrgue = 0, + kLeb001, + kMax + }; +}; + +struct PlaceState { + typedef void (CryOmni3DEngine_Versailles::*InitFunc)(); + typedef bool (CryOmni3DEngine_Versailles::*FilterEventFunc)(unsigned int *event); + + PlaceState() : initPlace(nullptr), filterEvent(nullptr), docImage(nullptr), state(0) {} + PlaceState(InitFunc initPlace_, FilterEventFunc filterEvent_, const char *docImage_) : + initPlace(initPlace_), filterEvent(filterEvent_), docImage(docImage_), state(0) {} + + InitFunc initPlace; + FilterEventFunc filterEvent; + const char *docImage; + unsigned int state; +}; + +struct LevelInitialState { + unsigned int placeId; + double alpha; + double beta; +}; + +struct FakeTransitionActionPlace { + unsigned int actionId; + unsigned int placeId; +}; + +typedef void (CryOmni3DEngine_Versailles::*FixedImgCallback)(ZonFixedImage *); + +struct MsgBoxParameters { + int font; + byte foreColor; + unsigned int lineHeight; + unsigned int spaceWidth; + unsigned int charSpacing; + unsigned int initialWidth; + unsigned int incrementWidth; + unsigned int initialHeight; + unsigned int incrementHeight; + unsigned int timeoutChar; +}; + +class CryOmni3DEngine_Versailles : public CryOmni3DEngine { + friend class Versailles_DialogsManager; +protected: + Common::Error run() override; + +public: + CryOmni3DEngine_Versailles(OSystem *syst, const CryOmni3DGameDescription *gamedesc); + virtual ~CryOmni3DEngine_Versailles(); + + void setupPalette(const byte *colors, uint start, uint num) override { setupPalette(colors, start, num, true); } + void makeTranslucent(Graphics::Surface &dst, const Graphics::Surface &src) const override; + + virtual bool displayToolbar(const Graphics::Surface *original) override { return _toolbar.displayToolbar(original); }; + virtual bool hasPlaceDocumentation() override; + virtual bool displayPlaceDocumentation() override; + virtual unsigned int displayOptions() override; + virtual bool shouldAbort() override { return g_engine->shouldQuit() || _abortCommand != AbortNoAbort; } + + +private: + void setupFonts(); + void setupSprites(); + void loadCursorsPalette(); + void calculateTransparentMapping(); + void setupMessages(); + void setupObjects(); + void setupDialogVariables(); + void setupImgScripts(); + void setupPaintingsTitles(); + + void syncOmni3DSettings(); + void syncSoundSettings(); + + void playTransitionEndLevel(int level); + void changeLevel(int level); + void initNewLevel(int level); + void setupLevelWarps(int level); + void initPlacesStates(); + void initWhoSpeaksWhere(); + void initDocPeopleRecord(); + void setupLevelActionsMask(); + + unsigned int currentGameTime() const { return _gameVariables[GameVariables::kCurrentTime]; } + void setGameTime(unsigned int newTime, unsigned int level); + void updateGameTimeDialVariables(); + + void gameStep(); + void doGameOver(); + + void setPlaceState(unsigned int placeId, unsigned int newState); + void doPlaceChange(); + void executeTransition(unsigned int nextPlaceId); + void fakeTransition(unsigned int dstPlaceId); + unsigned int determineTransitionAnimation(unsigned int srcId, unsigned int dstId, + const Transition **transition); + + unsigned int getFakeTransition(unsigned int actionId) const; + + int handleWarp(); + bool handleWarpMouse(unsigned int *actionId, unsigned int movingCuror); + void animateWarpTransition(const Transition *transition); + void redrawWarp(); + + void handleFixedImg(const FixedImgCallback &callback); + void executeSeeAction(unsigned int actionId); + + void executeSpeakAction(unsigned int actionId); + void setupDialogShows(); + bool preprocessDialog(const Common::String &sequence); + void postprocessDialog(const Common::String &sequence); + + void executeDocAction(unsigned int actionId); + + void drawMenuTitle(Graphics::ManagedSurface *surface, byte color); + unsigned int displayFilePicker(const Graphics::Surface *bgFrame, bool saveMode, + Common::String &saveName); + unsigned int displayYesNoBox(Graphics::ManagedSurface &surface, const Common::Rect &position, + unsigned int msg_id); + void displayMessageBox(const MsgBoxParameters ¶ms, const Graphics::Surface *surface, + unsigned int msg_id, const Common::Point &position, + const Common::Functor0<void> &callback) { displayMessageBox(params, surface, _messages[msg_id], position, callback); } + void displayMessageBox(const MsgBoxParameters ¶ms, const Graphics::Surface *surface, + const Common::String &msg, const Common::Point &position, + const Common::Functor0<void> &callback); + void displayMessageBoxWarp(const Common::String &message); + void displayMessageBoxWarp(unsigned int msg_id) { displayMessageBoxWarp(_messages[msg_id]); } + void displayCredits(); + + void warpMsgBoxCB(); + + bool canVisit() const; + Common::String getSaveFileName(bool visit, unsigned int saveNum) const; + void getSavesList(bool visit, Common::Array<Common::String> &saveNames); + void saveGame(bool visit, unsigned int saveNum, const Common::String &saveName) const; + bool loadGame(bool visit, unsigned int saveNum); + + void animateCursor(const Object *object); + void collectObject(unsigned int nameID, const ZonFixedImage *fimg = nullptr, + bool showObject = true); + typedef void (CryOmni3DEngine_Versailles::*DisplayObjectHook)(Graphics::ManagedSurface &surface); + void displayObject(const Common::String &imgName, DisplayObjectHook hook = nullptr); + + void setupPalette(const byte *colors, uint start, uint num, bool commit); + + bool showSubtitles() const; + + void playInGameVideo(const Common::String &filename, bool restoreCursorPalette = true); + + unsigned int getMusicId(unsigned int level, unsigned int placeId) const; + bool musicWouldChange(unsigned int level, unsigned int placeId) const; + void musicUpdate(); + void musicPause(); + void musicResume(); + void musicStop(); + void musicSetQuiet(bool quiet); + + Common::StringArray _messages; + static const unsigned int kSpritesMapTable[]; + static const unsigned int kSpritesMapTableSize; + static const LevelInitialState kLevelInitialStates[]; + static const FakeTransitionActionPlace kFakeTransitions[]; + Common::HashMap<unsigned int, FixedImgCallback> _imgScripts; + Common::Array<Common::String> _paintingsTitles; + + Toolbar _toolbar; + + byte *_mainPalette; + byte *_cursorPalette; + bool _fadedPalette; + bool _forcePaletteUpdate; + bool _forceRedrawWarp; + + byte *_transparentPaletteMap; + unsigned int _transparentSrcStart; + unsigned int _transparentSrcStop; + unsigned int _transparentDstStart; + unsigned int _transparentDstStop; + unsigned int _transparentNewStart; + unsigned int _transparentNewStop; + + bool _isPlaying; + bool _isVisiting; + AbortCommand _abortCommand; + unsigned int _loadedSave; + + int _omni3dSpeed; + + unsigned int _currentLevel; + Versailles_DialogsManager _dialogsMan; + + Omni3DManager _omni3dMan; + ZonFixedImage *_fixedImage; + + Common::Array<unsigned int> _gameVariables; + Common::Array<PlaceState> _placeStates; + Common::HashMap<PlaceStateActionKey, unsigned int> _actionMasks; + Common::HashMap<PlaceActionKey, Common::String> _whoSpeaksWhere; + Common::HashMap<unsigned int, const char *> _docPeopleRecord; + bool _transitionAnimateWarp; + unsigned int _nextPlaceId; + WAMParser _wam; + unsigned int _currentPlaceId; + const Place *_currentPlace; + const Image::ImageDecoder *_currentWarpImage; + + const char *_musicCurrentFile; + Audio::SoundHandle _musicHandle; + float _musicVolumeFactor; + static const char *kMusicFiles[8][8]; + + Versailles_Documentation _docManager; + + static const MsgBoxParameters kWarpMsgBoxParameters; + static const MsgBoxParameters kFixedimageMsgBoxParameters; + static const FixedImageConfiguration kFixedImageConfiguration; + + //Objects + template<unsigned int ID> + void genericDisplayObject(); + + // Fixed image + template<unsigned int ID> + void genericDumbImage(ZonFixedImage *fimg); + template<unsigned int ID> + void genericPainting(ZonFixedImage *fimg); +#define IMG_CB(name) void img_ ## name(ZonFixedImage *fimg) + IMG_CB(31142); + IMG_CB(31142b); + IMG_CB(31142c); + IMG_CB(31142d); + IMG_CB(31143); + IMG_CB(31143b); + IMG_CB(31143c); + IMG_CB(31143d); + IMG_CB(41202); + IMG_CB(41202b); + IMG_CB(41801); + IMG_CB(41801b); + IMG_CB(41801c); + IMG_CB(41802); + IMG_CB(41802b); + IMG_CB(41802c); + IMG_CB(41802d); +#undef IMG_CB + +#define FILTER_EVENT(level, place) bool filterEventLevel ## level ## Place ## place(unsigned int *event) +#define INIT_PLACE(level, place) void initPlaceLevel ## level ## Place ## place() + FILTER_EVENT(1, 1); + FILTER_EVENT(1, 2); + INIT_PLACE(1, 3); + FILTER_EVENT(1, 3); + //FILTER_EVENT(1, 7); // Not used + FILTER_EVENT(1, 14); +#undef FILTER_EVENT +#undef INIT_PLACE + + // Dialogs shows + void dialogShowBontempsShowThird(); + void dialogShowHuissierShowPamphlet(); + void dialogShowMonseigneurSorts(); + void dialogShowLeBrunWatches(); + void dialogShowDoorsOpen(); + void dialogShowSwissGuardGives(); + void dialogShowLullyCorrects(); + void dialogShowBontempsGivesAuth(); + void dialogShowCroissyLeave(); + void dialogShowMaintenonGives(); + void dialogShowLaChaizeGivesBack(); + void dialogShowLaChaizeWrites(); + void dialogShowLaChaizeGivesPamphlet(); + void dialogShowBontempsGivesKey(); + void dialogShowDuMaineLeaves(); + void dialogShowTransitionScene(); + void dialogShowEndOfGame(); + void dialogShowLeBrunGives(); + void dialogShowLeBrunLeave(); +}; + +} // End of namespace Versailles +} // End of namespace CryOmni3D + +#endif diff --git a/engines/cryomni3d/versailles/logic.cpp b/engines/cryomni3d/versailles/logic.cpp new file mode 100644 index 0000000000..1144318ec0 --- /dev/null +++ b/engines/cryomni3d/versailles/logic.cpp @@ -0,0 +1,1036 @@ +/* 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 "audio/audiostream.h" +#include "audio/decoders/wave.h" +#include "audio/mixer.h" +#include "common/file.h" +#include "common/system.h" + +#include "cryomni3d/fixed_image.h" + +#include "cryomni3d/versailles/engine.h" + +namespace CryOmni3D { +namespace Versailles { + +static const char *imagesObjects[] = { + "PAMP.gif", // 0: 96 + "PAPT_2.gif", // 1: 98 + "PAML.gif", // 2: 101 + "ESQ1.gif", // 3: 105a, 106b + "ESQ2.gif", // 4: 105b, 106c + "ESQ3.gif", // 5: 105c + "ESQ4.gif", // 6: 105d, 106a, 107a + "ESQ4T.gif", // 7: 107b + "ESQ4D.gif", // 8: 109 + "PAMA.gif", // 9: 115 + "PAMM1.gif", // 10: 118a + "PAMM2.gif", // 11: 118b + "MEDP.gif", // 12: 121a + "MEDP2.gif", // 13: 121b + "PAMR1.gif", // 14: 125a + "PAMR4.gif", // 15: 125b + "EPIL.gif", // 16: 126 + "PAMG.gif", // 17: 127 + "PBETE.gif", // 18: 129 + "VAU2.gif", // 19: 131 + "VAU3.gif", // 20: 132 + "GRAV2.gif", // 21: 134 + "MEM.gif", // 22: 137 + "VS1.gif", // 23: 138 + "VS2.gif", // 24: 139 + "FAB.gif", // 25: 141 + "LABYR.gif", // 26: 142 +}; + +void CryOmni3DEngine_Versailles::setupObjects() { + _objects.reserve(51); +#define SET_OBJECT(cursorId, nameId) _objects.push_back(Object(_sprites, cursorId, nameId)) +#define SET_OBJECT_CB(cursorId, nameId, cb) do { \ + _objects.push_back(Object(_sprites, cursorId, nameId)); \ + _objects.back().setViewCallback(new Common::Functor0Mem<void, CryOmni3DEngine_Versailles>(this, &CryOmni3DEngine_Versailles::cb)); \ + } while (false) +#define SET_OBJECT_GENERIC_CB(cursorId, nameId, imageId) SET_OBJECT_CB(cursorId, nameId, genericDisplayObject<imageId>) + SET_OBJECT(161, 93); + SET_OBJECT(107, 94); + SET_OBJECT(69, 95); + SET_OBJECT_GENERIC_CB(230, 96, 0); + SET_OBJECT(64, 97); + SET_OBJECT_GENERIC_CB(250, 98, 1); + SET_OBJECT(202, 99); + SET_OBJECT(235, 100); + SET_OBJECT_GENERIC_CB(167, 101, 2); + SET_OBJECT(191, 102); + SET_OBJECT(171, 103); + SET_OBJECT(47, 104); + SET_OBJECT(205, 105); + SET_OBJECT(214, 106); + SET_OBJECT(6, 107); + SET_OBJECT(58, 108); + SET_OBJECT_GENERIC_CB(5, 109, 8); + SET_OBJECT(38, 110); + SET_OBJECT(119, 113); + SET_OBJECT(186, 114); + SET_OBJECT_GENERIC_CB(246, 115, 9); + SET_OBJECT(80, 116); + SET_OBJECT(180, 117); + SET_OBJECT(34, 118); + SET_OBJECT(173, 119); + SET_OBJECT(81, 120); + SET_OBJECT(156, 121); + SET_OBJECT(143, 122); + SET_OBJECT(101, 123); + SET_OBJECT(204, 124); + SET_OBJECT(10, 125); + SET_OBJECT(112, 126); // TODO: EPIL.gif + SET_OBJECT_GENERIC_CB(90, 127, 17); + SET_OBJECT(216, 128); + SET_OBJECT_GENERIC_CB(32, 129, 18); + SET_OBJECT(37, 130); + SET_OBJECT_GENERIC_CB(134, 131, 19); + SET_OBJECT_GENERIC_CB(150, 132, 20); + SET_OBJECT(28, 133); + SET_OBJECT_GENERIC_CB(22, 134, 21); + SET_OBJECT(92, 135); + SET_OBJECT(115, 136); // Out of order in EXE + SET_OBJECT_GENERIC_CB(16, 137, 22); + SET_OBJECT_GENERIC_CB(237, 138, 23); + SET_OBJECT_GENERIC_CB(0, 139, 24); + SET_OBJECT(31, 140); + SET_OBJECT_GENERIC_CB(87, 141, 25); + SET_OBJECT(95, 142); // TODO: LABYR.gif + SET_OBJECT(157, 143); + SET_OBJECT(168, 144); + SET_OBJECT(65, 145); +#undef SET_OBJECT +} + +template<unsigned int ID> +void CryOmni3DEngine_Versailles::genericDisplayObject() { + displayObject(imagesObjects[ID]); +} + +// This array contains images for all paintings it must be kept in sync with _paintingsTitles +static const char *imagesPaintings[] = { + "10E_1.GIF", // 0: 41201 + nullptr, // 1: 41202 + "10E_3.GIF", // 2: 41203 + "10E_4.GIF", // 3: 41204 + "10E_5.GIF", // 4: 41205 + "10D_1.GIF", // 5: 41301 + "10D_2.GIF", // 6: 41302 + "20C_1.GIF", // 7: 42401 + "20G_11.GIF", // 8: 42901 + "20G_12.GIF", // 9: 42902 + "20G_13.GIF", // 10: 42903 + "20G_14.GIF", // 11: 42904 + "20G_15.GIF", // 12: 42905 + "20G_16.GIF", // 13: 42906 + "20G_21.GIF", // 14: 42907 + "20G_22.GIF", // 15: 42908 + "20G_23.GIF", // 16: 42909 + "20G_31.GIF", // 17: 42910 + "20G_32.GIF", // 18: 42911 + "20G_33.GIF", // 19: 42912 + "20G_34.GIF", // 20: 42913 + "20G_35.GIF", // 21: 42914 + "20G_36.GIF", // 22: 42915 + "30N_1.GIF", // 23: 43090 + "30N_2.GIF", // 24: 43091 + "30N_3.GIF", // 25: 43092 + "30O_1.GIF", // 26: 43100 + "30O_2.GIF", // 27: 43101 + "30O_31.GIF", // 28: 43102 + "30O_32.GIF", // 29: 43103 + "30O_33.GIF", // 30: 43104 + "30M_1.GIF", // 31: 43130 + "30M_2.GIF", // 32: 43131 + "30M_3.GIF", // 33: 43132 + "30L_11.GIF", // 34: 43140 + "30L_12.GIF", // 35: 43141 + "30L_21.GIF", // 36: 43142 + nullptr, // 37: 43143 + "30L_32.GIF", // 38: 43144 + "30J_11.GIF", // 39: 43150 + "30J_12.GIF", // 40: 43151 + "30J_13.GIF", // 41: 43152 + "30J_21.GIF", // 42: 43153 + "30J_22.GIF", // 43: 43154 + "30J_31.GIF", // 44: 43155 + "30J_32.GIF", // 45: 43156 + "30J_33.GIF", // 46: 43157 + "51A_1.GIF", // 47: 45260 + // Now let's put dumb images, those without description and any special action, they are not synced with _paintingsTitles + "30Q_1.GIF", // 48: 43060 + "30Q_2.GIF", // 49: 43061 + "52M2.GIF", // 50: 45130 // Almost dumb + "53I_LUST.GIF", // 51: 45280 // Almost dumb + "DUC.GIF", // 52: 46001 + "COQ.GIF", // 53: 46002 + "CHAT.GIF", // 54: 46003 + "DRAGON.GIF", // 55: 46004 + "GRUE.GIF", // 56: 46005 + "RENARD.GIF", // 57: 46006 + "POULE.GIF", // 58: 46007 + "LOUP.GIF", // 59: 46008 + "MILAN.GIF", // 60: 46009 + "GRENOU.GIF", // 61: 46010 + "AIGLE.GIF", // 62: 46011 + "SOURIS.GIF", // 63: 46012 + "CYGNE.GIF", // 64: 46013 and 46440 + "LOUPTETE.GIF", // 65: 46014 + "CANNES.GIF", // 66: 46015 +}; + +// Setup array for all see actions +void CryOmni3DEngine_Versailles::setupImgScripts() { + // First all paintings to keep it simple for counting +#define SET_SCRIPT_BY_ID(id) _imgScripts[id] = &CryOmni3DEngine_Versailles::img_ ## id +#define SET_SCRIPT_BY_PAINTING(id, image) _imgScripts[id] = &CryOmni3DEngine_Versailles::genericPainting<image> + SET_SCRIPT_BY_PAINTING(41201, 0); + SET_SCRIPT_BY_ID(41202); + SET_SCRIPT_BY_PAINTING(41203, 2); + SET_SCRIPT_BY_PAINTING(41204, 3); + SET_SCRIPT_BY_PAINTING(41205, 4); + SET_SCRIPT_BY_PAINTING(41301, 5); + SET_SCRIPT_BY_PAINTING(41302, 6); + SET_SCRIPT_BY_PAINTING(42401, 7); + SET_SCRIPT_BY_PAINTING(42901, 8); + SET_SCRIPT_BY_PAINTING(42902, 9); + SET_SCRIPT_BY_PAINTING(42903, 10); + SET_SCRIPT_BY_PAINTING(42904, 11); + SET_SCRIPT_BY_PAINTING(42905, 12); + SET_SCRIPT_BY_PAINTING(42906, 13); + SET_SCRIPT_BY_PAINTING(42907, 14); + SET_SCRIPT_BY_PAINTING(42908, 15); + SET_SCRIPT_BY_PAINTING(42909, 16); + SET_SCRIPT_BY_PAINTING(42910, 17); + SET_SCRIPT_BY_PAINTING(42911, 18); + SET_SCRIPT_BY_PAINTING(42912, 19); + SET_SCRIPT_BY_PAINTING(42913, 20); + SET_SCRIPT_BY_PAINTING(42914, 21); + SET_SCRIPT_BY_PAINTING(42915, 22); + SET_SCRIPT_BY_PAINTING(43090, 23); + SET_SCRIPT_BY_PAINTING(43091, 24); + SET_SCRIPT_BY_PAINTING(43092, 25); + SET_SCRIPT_BY_PAINTING(43100, 26); + SET_SCRIPT_BY_PAINTING(43101, 27); + SET_SCRIPT_BY_PAINTING(43102, 28); + SET_SCRIPT_BY_PAINTING(43103, 29); + SET_SCRIPT_BY_PAINTING(43104, 30); + SET_SCRIPT_BY_PAINTING(43130, 31); + SET_SCRIPT_BY_PAINTING(43131, 32); + SET_SCRIPT_BY_PAINTING(43132, 33); + SET_SCRIPT_BY_PAINTING(43140, 34); + SET_SCRIPT_BY_PAINTING(43141, 35); + SET_SCRIPT_BY_PAINTING(43142, 36); + //SET_SCRIPT_BY_ID(43143); // TODO: implement it + SET_SCRIPT_BY_PAINTING(43144, 38); + SET_SCRIPT_BY_PAINTING(43150, 39); + SET_SCRIPT_BY_PAINTING(43151, 40); + SET_SCRIPT_BY_PAINTING(43152, 41); + SET_SCRIPT_BY_PAINTING(43153, 42); + SET_SCRIPT_BY_PAINTING(43154, 43); + SET_SCRIPT_BY_PAINTING(43155, 44); + SET_SCRIPT_BY_PAINTING(43156, 45); + SET_SCRIPT_BY_PAINTING(43157, 46); + SET_SCRIPT_BY_PAINTING(45260, 47); +#undef SET_SCRIPT_BY_PAINTING + // From now dumb images (like paintings but without interrogation mark handling) +#define SET_SCRIPT_BY_DUMB(id, image) _imgScripts[id] = &CryOmni3DEngine_Versailles::genericDumbImage<image> + SET_SCRIPT_BY_DUMB(43060, 48); + SET_SCRIPT_BY_DUMB(43061, 49); + SET_SCRIPT_BY_DUMB(46001, 52); + SET_SCRIPT_BY_DUMB(46002, 53); + SET_SCRIPT_BY_DUMB(46003, 54); + SET_SCRIPT_BY_DUMB(46004, 55); + SET_SCRIPT_BY_DUMB(46005, 56); + SET_SCRIPT_BY_DUMB(46006, 57); + SET_SCRIPT_BY_DUMB(46007, 58); + SET_SCRIPT_BY_DUMB(46008, 59); + SET_SCRIPT_BY_DUMB(46009, 60); + SET_SCRIPT_BY_DUMB(46010, 61); + SET_SCRIPT_BY_DUMB(46011, 62); + SET_SCRIPT_BY_DUMB(46012, 63); + SET_SCRIPT_BY_DUMB(46013, 64); + SET_SCRIPT_BY_DUMB(46014, 65); + SET_SCRIPT_BY_DUMB(46015, 66); + SET_SCRIPT_BY_DUMB(46440, 64); // Same as 46013 +#undef SET_SCRIPT_BY_DUMB + // From now specific handlers for anything that is not a painting + SET_SCRIPT_BY_ID(41801); + SET_SCRIPT_BY_ID(41802); + //SET_SCRIPT_BY_ID(43145); // TODO: implement it + //SET_SCRIPT_BY_ID(43146); // TODO: implement it + //SET_SCRIPT_BY_ID(43160); // TODO: implement it + //SET_SCRIPT_BY_ID(43190); // TODO: implement it + //SET_SCRIPT_BY_ID(44071); // TODO: implement it + //SET_SCRIPT_BY_ID(44161); // TODO: implement it + //SET_SCRIPT_BY_ID(45130); // TODO: implement it // Almost dumb + //SET_SCRIPT_BY_ID(45270); // TODO: implement it + //SET_SCRIPT_BY_ID(45280); // TODO: implement it // Almost dumb + //SET_SCRIPT_BY_ID(88001); // TODO: implement it + //SET_SCRIPT_BY_ID(88002); // TODO: implement it + //SET_SCRIPT_BY_ID(88003); // TODO: implement it + //SET_SCRIPT_BY_ID(88004); // TODO: implement it +#undef SET_SCRIPT_BY_ID +} + +// Generic handler for dumb fixed images +template<unsigned int ID> +void CryOmni3DEngine_Versailles::genericDumbImage(ZonFixedImage *fimg) { + fimg->load(imagesPaintings[ID]); + while (1) { + fimg->manage(); + if (fimg->_exit || fimg->_zoneLow) { + fimg->_exit = true; + break; + } + } +} + +// Generic handler for interrogation mark action: display the painting title +#define HANDLE_QUESTION(ID) \ + do { \ + if (fimg->_zoneQuestion) { \ + displayMessageBox(kFixedimageMsgBoxParameters, fimg->surface(), _paintingsTitles[ID], Common::Point(600, 400), \ + Common::Functor0Mem<void, ZonFixedImage>(fimg, &ZonFixedImage::manage)); \ + } \ + } while (false) + +// Generic handler for paintings fixed images +template<unsigned int ID> +void CryOmni3DEngine_Versailles::genericPainting(ZonFixedImage *fimg) { + fimg->load(imagesPaintings[ID]); + while (1) { + fimg->manage(); + if (fimg->_exit || fimg->_zoneLow) { + fimg->_exit = true; + break; + } + HANDLE_QUESTION(ID); + } +} + +// Specific fixed images callbacks +#define IMG_CB(name) void CryOmni3DEngine_Versailles::img_ ## name(ZonFixedImage *fimg) + +IMG_CB(31142) { + fimg->load("10D2_4.GIF"); + while (1) { + fimg->manage(); + if (fimg->_exit || fimg->_zoneLow) { + fimg->_exit = true; + break; + } + if (fimg->_zoneUse) { + displayMessageBox(kFixedimageMsgBoxParameters, fimg->surface(), 7, + fimg->getZoneCenter(fimg->_currentZone), + Common::Functor0Mem<void, ZonFixedImage>(fimg, &ZonFixedImage::manage)); + } + } +} + +IMG_CB(31142b) { + fimg->load("11D2_2.GIF"); + while (1) { + fimg->manage(); + if (fimg->_exit || fimg->_zoneLow) { + fimg->_exit = true; + break; + } + if (fimg->_zoneUse) { + if (_gameVariables[GameVariables::kCollectScissors] || _inventory.inInventoryByNameId(94)) { + // Empty drawer + ZonFixedImage::CallbackFunctor *functor = + new Common::Functor1Mem<ZonFixedImage *, void, CryOmni3DEngine_Versailles>(this, + &CryOmni3DEngine_Versailles::img_31142d); + fimg->changeCallback(functor); + break; + } else { + // Drawer with scissors in it + ZonFixedImage::CallbackFunctor *functor = + new Common::Functor1Mem<ZonFixedImage *, void, CryOmni3DEngine_Versailles>(this, + &CryOmni3DEngine_Versailles::img_31142c); + fimg->changeCallback(functor); + break; + } + } + } +} + +IMG_CB(31142c) { + fimg->load("11D2_21.GIF"); + while (1) { + fimg->manage(); + if (fimg->_exit || fimg->_zoneLow) { + fimg->_exit = true; + break; + } + if (fimg->_zoneUse) { + if (!_inventory.inInventoryByNameId(94) && !_gameVariables[GameVariables::kCollectScissors]) { + collectObject(94, fimg); + } + _gameVariables[GameVariables::kCollectScissors] = 1; + // Display empty drawer + ZonFixedImage::CallbackFunctor *functor = + new Common::Functor1Mem<ZonFixedImage *, void, CryOmni3DEngine_Versailles>(this, + &CryOmni3DEngine_Versailles::img_31142d); + fimg->changeCallback(functor); + break; + } + } +} + +IMG_CB(31142d) { + fimg->load("11D2_22.GIF"); + while (1) { + fimg->manage(); + if (fimg->_exit || fimg->_zoneLow) { + fimg->_exit = true; + break; + } + if (fimg->_zoneUse) { + // Close drawer + ZonFixedImage::CallbackFunctor *functor = + new Common::Functor1Mem<ZonFixedImage *, void, CryOmni3DEngine_Versailles>(this, + &CryOmni3DEngine_Versailles::img_31142b); + fimg->changeCallback(functor); + break; + } + } +} + +IMG_CB(31143) { + fimg->load("10D2_3.GIF"); + while (1) { + fimg->manage(); + if (fimg->_exit || fimg->_zoneLow) { + fimg->_exit = true; + break; + } + if (fimg->_zoneUse) { + displayMessageBox(kFixedimageMsgBoxParameters, fimg->surface(), 7, + fimg->getZoneCenter(fimg->_currentZone), + Common::Functor0Mem<void, ZonFixedImage>(fimg, &ZonFixedImage::manage)); + } + } +} + +IMG_CB(31143b) { + fimg->load("11D2_1.GIF"); + while (1) { + fimg->manage(); + if (fimg->_exit || fimg->_zoneLow) { + fimg->_exit = true; + break; + } + if (fimg->_zoneUse) { + if (_inventory.inInventoryByNameId(96)) { + // Empty drawer + ZonFixedImage::CallbackFunctor *functor = + new Common::Functor1Mem<ZonFixedImage *, void, CryOmni3DEngine_Versailles>(this, + &CryOmni3DEngine_Versailles::img_31143d); + fimg->changeCallback(functor); + break; + } else { + // Drawer with pamphlet about arts in it + ZonFixedImage::CallbackFunctor *functor = + new Common::Functor1Mem<ZonFixedImage *, void, CryOmni3DEngine_Versailles>(this, + &CryOmni3DEngine_Versailles::img_31143c); + fimg->changeCallback(functor); + break; + } + } + } +} + +IMG_CB(31143c) { + fimg->load("11D2_11.GIF"); + while (1) { + fimg->manage(); + if (fimg->_exit || fimg->_zoneLow) { + fimg->_exit = true; + break; + } + if (fimg->_zoneUse) { + if (!_inventory.inInventoryByNameId(96)) { + collectObject(96, fimg); + } + // Display empty drawer + ZonFixedImage::CallbackFunctor *functor = + new Common::Functor1Mem<ZonFixedImage *, void, CryOmni3DEngine_Versailles>(this, + &CryOmni3DEngine_Versailles::img_31143d); + fimg->changeCallback(functor); + break; + } + } +} + +IMG_CB(31143d) { + fimg->load("11D2_12.GIF"); + while (1) { + fimg->manage(); + if (fimg->_exit || fimg->_zoneLow) { + fimg->_exit = true; + break; + } + if (fimg->_zoneUse) { + // Close drawer + ZonFixedImage::CallbackFunctor *functor = + new Common::Functor1Mem<ZonFixedImage *, void, CryOmni3DEngine_Versailles>(this, + &CryOmni3DEngine_Versailles::img_31143b); + fimg->changeCallback(functor); + break; + } + } +} + +IMG_CB(41202) { + fimg->load("10E_20.GIF"); + while (1) { + fimg->manage(); + if (fimg->_exit || fimg->_zoneLow) { + fimg->_exit = true; + break; + } + HANDLE_QUESTION(1); + if (fimg->_zoneUse) { + if (fimg->_currentZone == 2 && !_inventory.inInventoryByNameId(97)) { + // Open the jar + ZonFixedImage::CallbackFunctor *functor = + new Common::Functor1Mem<ZonFixedImage *, void, CryOmni3DEngine_Versailles>(this, + &CryOmni3DEngine_Versailles::img_41202b); + fimg->changeCallback(functor); + break; + } else { + displayMessageBox(kFixedimageMsgBoxParameters, fimg->surface(), 11, + fimg->getZoneCenter(fimg->_currentZone), + Common::Functor0Mem<void, ZonFixedImage>(fimg, &ZonFixedImage::manage)); + } + } + } +} + +IMG_CB(41202b) { + fimg->load("10E_21.GIF"); + while (1) { + fimg->manage(); + if (fimg->_exit) { + break; + } + HANDLE_QUESTION(1); + if (fimg->_zoneLow) { + // Go back to jars closed + ZonFixedImage::CallbackFunctor *functor = + new Common::Functor1Mem<ZonFixedImage *, void, CryOmni3DEngine_Versailles>(this, + &CryOmni3DEngine_Versailles::img_41202); + fimg->changeCallback(functor); + break; + } + if (fimg->_zoneUse) { + if (!_inventory.inInventoryByNameId(97)) { + collectObject(97, fimg); + } + // Go back to jars closed + ZonFixedImage::CallbackFunctor *functor = + new Common::Functor1Mem<ZonFixedImage *, void, CryOmni3DEngine_Versailles>(this, + &CryOmni3DEngine_Versailles::img_41202); + fimg->changeCallback(functor); + break; + } + } +} + +IMG_CB(41801) { + fimg->load("12E2_10.GIF"); + while (1) { + fimg->manage(); + if (fimg->_exit || fimg->_zoneLow) { + fimg->_exit = true; + break; + } + if (fimg->_currentZone == 0) { + bool open = false; + if (fimg->_zoneUse) { + // Using without object + if (_gameVariables[GameVariables::kUsedScissors]) { + open = true; + } else { + // Closed + displayMessageBox(kFixedimageMsgBoxParameters, fimg->surface(), 8, + fimg->getZoneCenter(fimg->_currentZone), + Common::Functor0Mem<void, ZonFixedImage>(fimg, &ZonFixedImage::manage)); + } + } else if (fimg->_usedObject && fimg->_usedObject->idOBJ() == 94) { + _gameVariables[GameVariables::kUsedScissors] = 1; + _inventory.removeByNameId(94); + open = true; + } + if (open) { + if (_gameVariables[GameVariables::kCollectedPaperInTrunk]) { + // Display empty trunk + ZonFixedImage::CallbackFunctor *functor = + new Common::Functor1Mem<ZonFixedImage *, void, CryOmni3DEngine_Versailles>(this, + &CryOmni3DEngine_Versailles::img_41801c); + fimg->changeCallback(functor); + break; + } else { + // Display trunk with paper in it + // Animate opening + playInGameVideo("12E2_11"); + // Force reload of the place + if (_nextPlaceId == -1u) { + _nextPlaceId = _currentPlaceId; + } + + ZonFixedImage::CallbackFunctor *functor = + new Common::Functor1Mem<ZonFixedImage *, void, CryOmni3DEngine_Versailles>(this, + &CryOmni3DEngine_Versailles::img_41801b); + fimg->changeCallback(functor); + break; + } + } + } + } +} + +IMG_CB(41801b) { + fimg->load("12E2_11.GIF"); + while (1) { + fimg->manage(); + if (fimg->_exit) { + break; + } + if (fimg->_zoneLow) { + // Animate closing + playInGameVideo("12E2_13"); + // Force reload of the place + if (_nextPlaceId == -1u) { + _nextPlaceId = _currentPlaceId; + } + fimg->_exit = true; + break; + } + if (fimg->_zoneUse) { + if (!_inventory.inInventoryByNameId(100)) { + collectObject(100, fimg); + } + _gameVariables[GameVariables::kCollectedPaperInTrunk] = 1; + + // Go to empty trunk + ZonFixedImage::CallbackFunctor *functor = + new Common::Functor1Mem<ZonFixedImage *, void, CryOmni3DEngine_Versailles>(this, + &CryOmni3DEngine_Versailles::img_41801c); + fimg->changeCallback(functor); + break; + } + } +} + +IMG_CB(41801c) { + fimg->load("12E2_12.GIF"); + while (1) { + fimg->manage(); + if (fimg->_exit) { + break; + } + if (fimg->_zoneLow) { + // Animate closing + playInGameVideo("12E2_13"); + // Force reload of the place + if (_nextPlaceId == -1u) { + _nextPlaceId = _currentPlaceId; + } + fimg->_exit = true; + break; + } + } +} + +IMG_CB(41802) { + // Dispatch to the correct state + if (_gameVariables[GameVariables::kInkSpilled] && + !_gameVariables[GameVariables::kCollectedPaperOnTable]) { + // Draw paper with ink on it + ZonFixedImage::CallbackFunctor *functor = + new Common::Functor1Mem<ZonFixedImage *, void, CryOmni3DEngine_Versailles>(this, + &CryOmni3DEngine_Versailles::img_41802b); + fimg->changeCallback(functor); + return; + } + if (!_gameVariables[GameVariables::kInkSpilled] && + _gameVariables[GameVariables::kCollectedPaperOnTable]) { + // Draw table with ink in inkpot and without paper + ZonFixedImage::CallbackFunctor *functor = + new Common::Functor1Mem<ZonFixedImage *, void, CryOmni3DEngine_Versailles>(this, + &CryOmni3DEngine_Versailles::img_41802c); + fimg->changeCallback(functor); + return; + } + if (_gameVariables[GameVariables::kInkSpilled] && + _gameVariables[GameVariables::kCollectedPaperOnTable]) { + // Draw table with ink directly on table + ZonFixedImage::CallbackFunctor *functor = + new Common::Functor1Mem<ZonFixedImage *, void, CryOmni3DEngine_Versailles>(this, + &CryOmni3DEngine_Versailles::img_41802d); + fimg->changeCallback(functor); + return; + } + + // There we have paper on table and ink is in its inkpot + fimg->load("12E2_20.GIF"); + while (1) { + fimg->manage(); + if (fimg->_exit || fimg->_zoneLow) { + fimg->_exit = true; + break; + } + if (fimg->_zoneUse && fimg->_currentZone == 1) { + // Collected paper + collectObject(95, fimg); + _gameVariables[GameVariables::kCollectedPaperOnTable] = 1; + setPlaceState(8, 1); + // Draw table with ink in inkpot and without paper + ZonFixedImage::CallbackFunctor *functor = + new Common::Functor1Mem<ZonFixedImage *, void, CryOmni3DEngine_Versailles>(this, + &CryOmni3DEngine_Versailles::img_41802c); + fimg->changeCallback(functor); + break; + } + if (fimg->_zoneUse && fimg->_currentZone == 2) { + _gameVariables[GameVariables::kInkSpilled] = 1; + setPlaceState(8, 3); + // Draw paper with ink on it + ZonFixedImage::CallbackFunctor *functor = + new Common::Functor1Mem<ZonFixedImage *, void, CryOmni3DEngine_Versailles>(this, + &CryOmni3DEngine_Versailles::img_41802b); + fimg->changeCallback(functor); + break; + } + if (fimg->_usedObject && fimg->_currentZone == 0) { + unsigned int objID = fimg->_usedObject->idOBJ(); + if (objID == 100) { + playInGameVideo("12E2_24"); + // Force reload of the place + if (_nextPlaceId == -1u) { + _nextPlaceId = _currentPlaceId; + } + _inventory.removeByNameId(100); + // Revealed paper + collectObject(98, fimg); + _gameVariables[GameVariables::kGotRevealedPaper] = 1; + setGameTime(3, 1); + } else if (objID == 96) { + // Pamphlet about arts + playInGameVideo("PAP_BRUL"); + // Force reload of the place + if (_nextPlaceId == -1u) { + _nextPlaceId = _currentPlaceId; + } + doGameOver(); + } + } + } +} + +IMG_CB(41802b) { + // There we have paper on table with ink on it + fimg->load("12E2_21.GIF"); + while (1) { + fimg->manage(); + if (fimg->_exit || fimg->_zoneLow) { + fimg->_exit = true; + break; + } + if (fimg->_zoneUse && fimg->_currentZone == 1) { + // Collected paper with ink on it + collectObject(99, fimg); + _gameVariables[GameVariables::kCollectedPaperOnTable] = 1; + setPlaceState(8, 2); + // Draw table with ink spilled and without paper + ZonFixedImage::CallbackFunctor *functor = + new Common::Functor1Mem<ZonFixedImage *, void, CryOmni3DEngine_Versailles>(this, + &CryOmni3DEngine_Versailles::img_41802d); + fimg->changeCallback(functor); + break; + } + if (fimg->_usedObject && fimg->_currentZone == 0) { + unsigned int objID = fimg->_usedObject->idOBJ(); + if (objID == 100) { + playInGameVideo("12E2_24"); + // Force reload of the place + if (_nextPlaceId == -1u) { + _nextPlaceId = _currentPlaceId; + } + _inventory.removeByNameId(100); + // Revealed paper + collectObject(98, fimg); + _gameVariables[GameVariables::kGotRevealedPaper] = 1; + setGameTime(3, 1); + } else if (objID == 96) { + // Pamphlet about arts + playInGameVideo("PAP_BRUL"); + // Force reload of the place + if (_nextPlaceId == -1u) { + _nextPlaceId = _currentPlaceId; + } + doGameOver(); + } + } + } +} + +IMG_CB(41802c) { + // There we have ink in inkpot and without paper + fimg->load("12E2_22.GIF"); + while (1) { + fimg->manage(); + if (fimg->_exit || fimg->_zoneLow) { + fimg->_exit = true; + break; + } + if (fimg->_zoneUse && fimg->_currentZone == 1) { + _gameVariables[GameVariables::kInkSpilled] = 1; + setPlaceState(8, 2); + // Draw table with ink on it + ZonFixedImage::CallbackFunctor *functor = + new Common::Functor1Mem<ZonFixedImage *, void, CryOmni3DEngine_Versailles>(this, + &CryOmni3DEngine_Versailles::img_41802d); + fimg->changeCallback(functor); + break; + } + if (fimg->_usedObject && fimg->_currentZone == 0) { + unsigned int objID = fimg->_usedObject->idOBJ(); + if (objID == 100) { + playInGameVideo("12E2_24"); + // Force reload of the place + if (_nextPlaceId == -1u) { + _nextPlaceId = _currentPlaceId; + } + _inventory.removeByNameId(100); + // Revealed paper + collectObject(98, fimg); + _gameVariables[GameVariables::kGotRevealedPaper] = 1; + setGameTime(3, 1); + } else if (objID == 96) { + // Pamphlet about arts + playInGameVideo("PAP_BRUL"); + // Force reload of the place + if (_nextPlaceId == -1u) { + _nextPlaceId = _currentPlaceId; + } + doGameOver(); + } + } + } +} + +IMG_CB(41802d) { + // There we have ink directly on table + fimg->load("12E2_23.GIF"); + while (1) { + fimg->manage(); + if (fimg->_exit || fimg->_zoneLow) { + fimg->_exit = true; + break; + } + if (fimg->_usedObject && fimg->_currentZone == 0) { + unsigned int objID = fimg->_usedObject->idOBJ(); + if (objID == 100) { + playInGameVideo("12E2_24"); + // Force reload of the place + if (_nextPlaceId == -1u) { + _nextPlaceId = _currentPlaceId; + } + _inventory.removeByNameId(100); + // Revealed paper + collectObject(98, fimg); + _gameVariables[GameVariables::kGotRevealedPaper] = 1; + setGameTime(3, 1); + } else if (objID == 96) { + // Pamphlet about arts + playInGameVideo("PAP_BRUL"); + // Force reload of the place + if (_nextPlaceId == -1u) { + _nextPlaceId = _currentPlaceId; + } + doGameOver(); + } + } + } +} + +#undef IMG_CB + +// Init place and filter event +#define FILTER_EVENT(level, place) bool CryOmni3DEngine_Versailles::filterEventLevel ## level ## Place ## place(unsigned int *event) +#define INIT_PLACE(level, place) void CryOmni3DEngine_Versailles::initPlaceLevel ## level ## Place ## place() + +FILTER_EVENT(1, 1) { + if (*event > 0 && *event < 9999) { + _gameVariables[GameVariables::kWarnedIncomplete] = 0; + } + if (*event == 11015 && currentGameTime() < 3) { + return false; + } else { + return true; + } +} + +FILTER_EVENT(1, 2) { + if (*event == 7 && currentGameTime() < 2) { + // Closed + displayMessageBoxWarp(2); + return false; + } + + if (*event == 1 && currentGameTime() < 3) { + _dialogsMan.play("11E_HUI"); + _forcePaletteUpdate = true; + // Force reload of the place + if (_nextPlaceId == -1u) { + _nextPlaceId = _currentPlaceId; + } + return false; + } + + return true; +} + +INIT_PLACE(1, 3) { + if (!_gameVariables[GameVariables::kHasPlayedLebrun]) { + Common::File *audioFile = new Common::File(); + if (!audioFile->open("LEB001__.WAV")) { + warning("Failed to open sound file %s", "LEB001__.WAV"); + delete audioFile; + return; + } + + Audio::SeekableAudioStream *audioDecoder = Audio::makeWAVStream(audioFile, DisposeAfterUse::YES); + // We lost ownership of the audioFile just set it to nullptr and don't use it + audioFile = nullptr; + if (!audioDecoder) { + return; + } + + _mixer->playStream(Audio::Mixer::kSpeechSoundType, nullptr, audioDecoder, SoundIds::kLeb001); + // We lost ownership of the audioDecoder just set it to nullptr and don't use it + audioDecoder = nullptr; + + _gameVariables[GameVariables::kHasPlayedLebrun] = 1; + } +} + +FILTER_EVENT(1, 3) { + if (*event == 11301) { + while (!g_engine->shouldQuit() && _mixer->isSoundIDActive(SoundIds::kLeb001)) { + g_system->updateScreen(); + pollEvents(); + } + clearKeys(); + return true; + } + + if (*event > 0 && *event < 10000) { + _mixer->stopID(SoundIds::kLeb001); + return true; + } + return true; +} + +// Event 19 is not in this room: must be a leftover +/* +FILTER_EVENT(1, 7) { + if (*event == 19) { + // Too dark + displayMessageBoxWarp(7); + return false; + } + + return true; +} +*/ + +FILTER_EVENT(1, 14) { + if (*event == 31141 && _placeStates[14].state == 0) { + // Open the curtain + unsigned int fakePlaceId = getFakeTransition(*event); + fakeTransition(fakePlaceId); + playInGameVideo("10D2_1"); + setPlaceState(14, 1); + // setPlaceState will force reload + // Don't pass the event as we try to avoid implementing use + return false; + } + + if (*event != 31142 && *event != 31143) { + // Not for us + return true; + } + + const char *video; + FixedImgCallback callback; + + if (_currentLevel == 1 && _placeStates[14].state == 0) { + if (*event == 31142) { + video = "10D2_4"; + callback = &CryOmni3DEngine_Versailles::img_31142; + } else if (*event == 31143) { + video = "10D2_3"; + callback = &CryOmni3DEngine_Versailles::img_31143; + } + } else if (_currentLevel == 2 || _placeStates[14].state == 1) { + if (*event == 31142) { + video = "11D2_2"; + callback = &CryOmni3DEngine_Versailles::img_31142b; + } else if (*event == 31143) { + video = "11D2_1"; + callback = &CryOmni3DEngine_Versailles::img_31143b; + } + } else { + error("Invalid state in filter event 1/14: level: %d/ placeState: %d", _currentLevel, + _placeStates[14].state); + } + + unsigned int fakePlaceId = getFakeTransition(*event); + fakeTransition(fakePlaceId); + + playInGameVideo(video); + + // Force reload of the place + if (_nextPlaceId == -1u) { + _nextPlaceId = _currentPlaceId; + } + + handleFixedImg(callback); + + // Don't pass the event as we try to avoid implementing use + return false; +} + +#undef FILTER_EVENT +#undef INIT_PLACE + +} // End of namespace Versailles +} // End of namespace CryOmni3D diff --git a/engines/cryomni3d/versailles/menus.cpp b/engines/cryomni3d/versailles/menus.cpp new file mode 100644 index 0000000000..a60726f1a8 --- /dev/null +++ b/engines/cryomni3d/versailles/menus.cpp @@ -0,0 +1,1066 @@ +/* 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 "audio/audiostream.h" +#include "audio/decoders/wave.h" +#include "common/config-manager.h" +#include "common/file.h" +#include "common/system.h" +#include "graphics/managed_surface.h" +#include "graphics/palette.h" +#include "image/bmp.h" +#include "image/image_decoder.h" + +#include "cryomni3d/mouse_boxes.h" +#include "cryomni3d/font_manager.h" + +#include "cryomni3d/versailles/engine.h" + +namespace CryOmni3D { +namespace Versailles { + +bool CryOmni3DEngine_Versailles::showSubtitles() const { + return ConfMan.getBool("subtitles"); +} + +void CryOmni3DEngine_Versailles::drawMenuTitle(Graphics::ManagedSurface *surface, byte color) { + int offY; + + int oldFont = _fontManager.getCurrentFont(); + _fontManager.setSurface(surface); + _fontManager.setForeColor(color); + _fontManager.setCurrentFont(1); + offY = _fontManager.getFontMaxHeight(); + _fontManager.displayStr(144, 160 - offY, _messages[23]); + _fontManager.setCurrentFont(3); + offY = _fontManager.getFontMaxHeight(); + _fontManager.displayStr(305, 160 - offY, _messages[24]); + + surface->vLine(100, 146, 172, color); + surface->hLine(100, 172, 168, color); // minus 1 because hLine draws inclusive + + _fontManager.setCurrentFont(oldFont); +} + +unsigned int CryOmni3DEngine_Versailles::displayOptions() { + Common::Array<int> menuEntries; + menuEntries.push_back(26); + menuEntries.push_back(27); + menuEntries.push_back(28); + menuEntries.push_back(29); + menuEntries.push_back(48); + menuEntries.push_back(30); + menuEntries.push_back(32); +#if 0 + // Music on HDD setting + menuEntries.push_back(34); +#endif + menuEntries.push_back(25); + menuEntries.push_back(-42); + menuEntries.push_back(43); + menuEntries.push_back(40); + // 1 is for volume box + MouseBoxes boxes(menuEntries.size() + 1); + + bool end = false; + + int drawState = 1; + + unsigned int volumeCursorMiddleY = _sprites.getCursor(102).getHeight() / 2; + unsigned int volume = CLIP(ConfMan.getInt("sfx_volume"), 0, 256); + unsigned int soundVolumeY = ((283 * (256 - volume)) >> 8) + 101; + byte volumeForeColor = 243; + + Graphics::ManagedSurface optionsSurface; + Image::ImageDecoder *imageDecoder = loadHLZ("option.hlz"); + const Graphics::Surface *bgFrame = imageDecoder->getSurface(); + + optionsSurface.create(bgFrame->w, bgFrame->h, bgFrame->format); + + setCursor(181); + g_system->showMouse(true); + + unsigned int hoveredBox = -1; + unsigned int selectedBox; + int selectedMsg = 0; + unsigned int volumeBox; + bool resetScreen = true; + bool forceEvents = true; + + while (!g_engine->shouldQuit() && !end) { + if (resetScreen) { + setPalette(imageDecoder->getPalette(), imageDecoder->getPaletteStartIndex(), + imageDecoder->getPaletteColorCount()); + // _cursorPalette has only 248 colors as 8 last colors are for translucency + setPalette(_cursorPalette + 240 * 3, 240, 8); + + _fontManager.setCurrentFont(3); + _fontManager.setTransparentBackground(true); + _fontManager.setForeColor(243); + _fontManager.setLineHeight(14); + _fontManager.setSpaceWidth(0); + _fontManager.setCharSpacing(1); + _fontManager.setSurface(&optionsSurface); + resetScreen = false; + } + if (drawState > 0) { + if (drawState == 1) { + optionsSurface.blitFrom(*bgFrame); + } + drawMenuTitle(&optionsSurface, 243); + _fontManager.setForeColor(volumeForeColor); + _fontManager.displayStr(550, 407, _messages[39]); + optionsSurface.vLine(544, 402, 429, volumeForeColor); + optionsSurface.hLine(544, 429, 613, volumeForeColor); // minus 1 because hLine draws inclusive + + boxes.reset(); + unsigned int boxId = 0; + unsigned int top = 195; + unsigned int bottom; + unsigned int width; + + for (Common::Array<int>::iterator it = menuEntries.begin(); it != menuEntries.end(); it++) { + if (*it == 30 && !ConfMan.getBool("subtitles")) { + *it = 31; + } else if (*it == 32 && (ConfMan.getBool("mute") || + ConfMan.getBool("music_mute"))) { + *it = 33; + } +#if 0 + else if (*it == 34) { + // What to do with music on HDD setting? + } +#endif + else if (*it == 26 && !_isPlaying) { + *it = -26; + } else if (*it == 29 && !_isPlaying) { + *it = -29; + } else if (*it == -42 && canVisit()) { + *it = 42; + } else if (*it == 48) { + unsigned int omni3D_speed = ConfMan.getInt("omni3d_speed"); + switch (omni3D_speed) { + case 1: + *it = 51; + break; + case 2: + *it = 52; + break; + case 3: + *it = 49; + break; + case 4: + *it = 50; + break; + } + } + + if (*it > 0) { + int msgId = *it; + bottom = top; + top += 24; + + // Patch on the fly the text displayed + if (_isVisiting) { + if (msgId == 26) { + msgId = 44; + } else if (msgId == 29) { + msgId = 45; + } + } + + width = _fontManager.getStrWidth(_messages[msgId]); + //Common::Rect rct(144, top - 39, width + 144, bottom); + //optionsSurface.frameRect(rct, 0); + boxes.setupBox(boxId, 144, top - 39, width + 144, bottom); + if (boxId == hoveredBox) { + _fontManager.setForeColor(240); + } else { + _fontManager.setForeColor(243); + } + _fontManager.displayStr(144, top - 39, _messages[msgId]); + } + boxId++; + } + + volumeBox = boxId; + boxes.setupBox(boxId, 525, 101, 570, 401); + optionsSurface.transBlitFrom(_sprites.getSurface(102), Common::Point(553, soundVolumeY), + _sprites.getKeyColor(102)); + + g_system->copyRectToScreen(optionsSurface.getPixels(), optionsSurface.pitch, 0, 0, optionsSurface.w, + optionsSurface.h); + drawState = 0; + } + g_system->updateScreen(); + + if (pollEvents() || forceEvents) { // always call pollEvents + forceEvents = false; + Common::Point mouse = getMousePos(); + unsigned int boxId = 0; + Common::Array<int>::iterator it; + for (it = menuEntries.begin(); it != menuEntries.end(); it++) { + if (boxes.hitTest(boxId, mouse)) { + if (hoveredBox != boxId) { + hoveredBox = boxId; + drawState = 2; + } + // We met a hit, no need to look further + break; + } + boxId++; + } + if (it != menuEntries.end()) { + if (getDragStatus() == 2) { + selectedMsg = *it; + selectedBox = hoveredBox; + } + } else { + // no menu selected, check volume + if (boxes.hitTest(volumeBox, mouse)) { + if (volumeForeColor != 240) { + volumeForeColor = 240; + drawState = 1; + } + if (getCurrentMouseButton() == 1) { + if (soundVolumeY != getMousePos().y - volumeCursorMiddleY) { + soundVolumeY = CLIP(getMousePos().y - volumeCursorMiddleY, 101u, 384u); + drawState = 1; + volume = CLIP(((384 - soundVolumeY) << 8) / 283, 0u, 256u); + // Global setting + ConfMan.setInt("music_volume", volume); + ConfMan.setInt("speech_volume", volume); + ConfMan.setInt("sfx_volume", volume); + syncSoundSettings(); + } + } else if (getDragStatus() == 2 && + !_mixer->hasActiveChannelOfType(Audio::Mixer::kMusicSoundType) && + _mixer->getVolumeForSoundType(Audio::Mixer::kSFXSoundType) > 0) { + // Finished dragging + _mixer->stopID(SoundIds::kOrgue); + do { + Common::File *audioFile = new Common::File(); + if (!audioFile->open("ORGUE.WAV")) { + warning("Failed to open sound file %s", "ORGUE.WAV"); + delete audioFile; + break; + } + + Audio::SeekableAudioStream *audioDecoder = Audio::makeWAVStream(audioFile, DisposeAfterUse::YES); + // We lost ownership of the audioFile just set it to nullptr and don't use it + audioFile = nullptr; + if (!audioDecoder) { + break; + } + + _mixer->playStream(Audio::Mixer::kSFXSoundType, nullptr, audioDecoder, SoundIds::kOrgue); + // We lost ownership of the audioDecoder just set it to nullptr and don't use it + audioDecoder = nullptr; + } while (false); + } + } else { + if (hoveredBox != -1u) { + hoveredBox = -1; + drawState = 2; + } + if (volumeForeColor != 243) { + volumeForeColor = 243; + drawState = 1; + } + } + } + if (getNextKey().keycode == Common::KEYCODE_ESCAPE && _isPlaying) { + selectedMsg = 26; + } + if (selectedMsg == 27 || selectedMsg == 28 || selectedMsg == 40 || selectedMsg == 42) { + // New game, Load game, Quit, Visit + if (!_isPlaying || _isVisiting) { + end = true; + } else { + end = displayYesNoBox(optionsSurface, Common::Rect(235, 420, 505, 465), 57); + } + drawState = 1; + if (end) { + _isPlaying = false; + } else { + selectedMsg = 0; + } + } + if (selectedMsg == 25) { + // Documentation area + _docManager.handleDocArea(); + drawState = 1; + resetScreen = true; + forceEvents = true; + waitMouseRelease(); + selectedMsg = 0; + } else if (selectedMsg == 26) { + // Continue game + end = true; + } else if (selectedMsg == 28) { + Common::String saveName; + bool wasVisiting = _isVisiting; + _isVisiting = false; + unsigned int saveNumber = displayFilePicker(bgFrame, false, saveName); + if (saveNumber == -1u) { + _isVisiting = wasVisiting; + drawState = 1; + selectedMsg = 0; + } else { + _loadedSave = saveNumber; + _isPlaying = false; + end = true; + } + waitMouseRelease(); + } else if (selectedMsg == 42) { + Common::String saveName; + bool wasVisiting = _isVisiting; + _isVisiting = true; + unsigned int saveNumber = displayFilePicker(bgFrame, false, saveName); + if (saveNumber == -1u) { + _isVisiting = wasVisiting; + drawState = 1; + selectedMsg = 0; + } else { + _loadedSave = saveNumber; + _isPlaying = false; + end = true; + } + waitMouseRelease(); + } else if (selectedMsg == 29) { + Common::String saveName; + unsigned int saveNumber = displayFilePicker(bgFrame, true, saveName); + if (saveNumber != -1u) { + saveGame(_isVisiting, saveNumber, saveName); + } + drawState = 1; + selectedMsg = 0; + waitMouseRelease(); + } else if (selectedMsg == 30) { + ConfMan.setBool("subtitles", false); + drawState = 1; + menuEntries[selectedBox] = 31; + selectedMsg = 0; + waitMouseRelease(); + } else if (selectedMsg == 31) { + ConfMan.setBool("subtitles", true); + drawState = 1; + menuEntries[selectedBox] = 30; + selectedMsg = 0; + waitMouseRelease(); + } else if (selectedMsg == 32) { + ConfMan.setBool("music_mute", true); + syncSoundSettings(); + drawState = 1; + menuEntries[selectedBox] = 33; + selectedMsg = 0; + waitMouseRelease(); + } else if (selectedMsg == 33) { + ConfMan.setBool("mute", false); + ConfMan.setBool("music_mute", false); + syncSoundSettings(); + drawState = 1; + menuEntries[selectedBox] = 32; + selectedMsg = 0; + waitMouseRelease(); + } +#if 0 + // Music on disk settings + else if (selectedMsg == 35) { + drawState = 1; + menuEntries[selectedBox] = 34; + selectedMsg = 0; + waitMouseRelease(); + } else if (selectedMsg == 34) { + drawState = 1; + menuEntries[selectedBox] = 36; + selectedMsg = 0; + waitMouseRelease(); + } else if (selectedMsg == 36) { + drawState = 1; + menuEntries[selectedBox] = 35; + selectedMsg = 0; + waitMouseRelease(); + } +#endif + else if (selectedMsg == 39) { + // Volume + selectedMsg = 0; + } else if (selectedMsg == 47) { + // Unknown + selectedMsg = 0; + } else if (selectedMsg == 48) { + ConfMan.setInt("omni3d_speed", 1); + drawState = 1; + menuEntries[selectedBox] = 51; + selectedMsg = 0; + waitMouseRelease(); + } else if (selectedMsg == 51) { + ConfMan.setInt("omni3d_speed", 2); + drawState = 1; + menuEntries[selectedBox] = 52; + selectedMsg = 0; + waitMouseRelease(); + } else if (selectedMsg == 52) { + ConfMan.setInt("omni3d_speed", 3); + drawState = 1; + menuEntries[selectedBox] = 49; + selectedMsg = 0; + waitMouseRelease(); + } else if (selectedMsg == 49) { + ConfMan.setInt("omni3d_speed", 4); + drawState = 1; + menuEntries[selectedBox] = 50; + selectedMsg = 0; + waitMouseRelease(); + } else if (selectedMsg == 50) { + ConfMan.setInt("omni3d_speed", 0); + drawState = 1; + menuEntries[selectedBox] = 48; + selectedMsg = 0; + waitMouseRelease(); + } else if (selectedMsg == 43) { + displayCredits(); + drawState = 1; + resetScreen = true; + forceEvents = true; + selectedMsg = 0; + waitMouseRelease(); + } + } + } + + g_system->showMouse(false); + + if (selectedMsg == 42) { + _abortCommand = AbortLoadGame; + // For return value + selectedMsg = 28; + } else if (selectedMsg == 28) { + _abortCommand = AbortLoadGame; + } else if (selectedMsg == 40) { + _abortCommand = AbortQuit; + } else if (selectedMsg == 27) { + _abortCommand = AbortNewGame; + _isVisiting = false; + } else if (g_engine->shouldQuit()) { + // Fake a quit + selectedMsg = 40; + _abortCommand = AbortQuit; + } + + ConfMan.flushToDisk(); + syncOmni3DSettings(); + musicUpdate(); + + delete imageDecoder; + return selectedMsg; +} + +unsigned int CryOmni3DEngine_Versailles::displayYesNoBox(Graphics::ManagedSurface &surface, + const Common::Rect &position, unsigned int msg_id) { + unsigned int confirmWidth = _fontManager.getStrWidth(_messages[53]); + unsigned int cancelWidth = _fontManager.getStrWidth(_messages[54]); + unsigned int oldFont = _fontManager.getCurrentFont(); + + _fontManager.setSurface(&surface); + _fontManager.setForeColor(240); + _fontManager.setLineHeight(20); + surface.frameRect(position, 243); + + _fontManager.setupBlock(Common::Rect(position.left + 5, position.top + 5, position.right - 5, + position.bottom - 5)); + _fontManager.setCurrentFont(5); + _fontManager.displayBlockText(_messages[msg_id]); + _fontManager.setCurrentFont(3); + + MouseBoxes boxes(2); + boxes.setupBox(1, position.left + 5, position.bottom - 15, position.left + confirmWidth, + position.bottom, &_messages[53]); + boxes.setupBox(0, position.right - cancelWidth - 5, position.bottom - 15, position.right, + position.bottom, &_messages[54]); + + bool end = false; + bool redraw = true; + unsigned int result = -1u; + + while (!end || redraw) { + if (redraw) { + for (unsigned int boxId = 0; boxId < 2; boxId++) { + if (boxId == result) { + _fontManager.setForeColor(240); + } else { + _fontManager.setForeColor(243); + } + boxes.display(boxId, _fontManager); + } + redraw = false; + + g_system->copyRectToScreen(surface.getPixels(), surface.pitch, 0, 0, surface.w, surface.h); + } + g_system->updateScreen(); + + if (pollEvents()) { + Common::Point mouse = getMousePos(); + unsigned int hit_result = -1u; + if (boxes.hitTest(1, mouse)) { + hit_result = 1; + } else if (boxes.hitTest(0, mouse)) { + hit_result = 0; + } + if (!end && hit_result != result) { + result = hit_result; + redraw = true; + } + if ((getCurrentMouseButton() == 1) && (result != -1u)) { + end = true; + } + Common::KeyCode keyPressed = getNextKey().keycode; + if (keyPressed == Common::KEYCODE_ESCAPE) { + result = 0; + redraw = true; + end = true; + } else if (keyPressed == Common::KEYCODE_RETURN) { + result = 1; + redraw = true; + end = true; + } + } + } + _fontManager.setCurrentFont(oldFont); + return result; +} + +unsigned int CryOmni3DEngine_Versailles::displayFilePicker(const Graphics::Surface *bgFrame, + bool saveMode, Common::String &saveName) { + Graphics::ManagedSurface surface(bgFrame->w, bgFrame->h, bgFrame->format); + surface.blitFrom(*bgFrame); + + drawMenuTitle(&surface, 243); + + int subtitleId; + if (_isVisiting) { + subtitleId = saveMode ? 45 : 46; + } else { + subtitleId = saveMode ? 29 : 28; + } + _fontManager.displayStr(164, 214, _messages[subtitleId]); + + // Draw an empty screen before we list saves + g_system->showMouse(false); + g_system->copyRectToScreen(surface.getPixels(), surface.pitch, 0, 0, surface.w, surface.h); + g_system->updateScreen(); + + Common::Array<Common::String> savesList; + getSavesList(_isVisiting, savesList); + Common::String saveNameBackup; + + g_system->showMouse(true); + + MouseBoxes boxes(10); // 6 files + Yes/No/Up/Down buttons + + // Yes/No buttons + const Common::String &okMsg = _messages[53]; + unsigned int okWidth = _fontManager.getStrWidth(okMsg); + boxes.setupBox(6, 246, 430, 246 + okWidth, 450, &okMsg); + const Common::String &cancelMsg = _messages[54]; + unsigned int cancelWidth = _fontManager.getStrWidth(cancelMsg); + boxes.setupBox(7, 146, 430, 146 + cancelWidth, 450, &cancelMsg); + + // Up/Down buttons + boxes.setupBox(8, 428, 320, 448, 340); + boxes.setupBox(9, 428, 360, 448, 380); + surface.transBlitFrom(_sprites.getSurface(162), Common::Point(428, 320), _sprites.getKeyColor(162)); + surface.transBlitFrom(_sprites.getSurface(185), Common::Point(428, 360), _sprites.getKeyColor(185)); + + setCursor(181); + + unsigned int fileListOffset = 0; // TODO: store in config + + unsigned int boxHovered = -1; + unsigned int boxSelected = -1; + + bool textCursorState = false; + unsigned int textCursorNextState = 0; + unsigned int textCursorPos = -1; + + bool autoRepeatInhibit = false; + unsigned int autoRepeatDelay = 250; + unsigned int autoRepeatEndInhibit = 0; + + bool finished = false; + bool filesListChanged = true; + bool redraw = false; + while (!finished) { + if (filesListChanged || redraw) { + if (filesListChanged) { + for (unsigned int file = 0, fileY = 280; file < 6; file++, fileY += 20) { + boxes.setupBox(file, 146, fileY, 408, fileY + 14, &savesList[file + fileListOffset]); + } + // Redraw background as file list changed + surface.blitFrom(*bgFrame, Common::Rect(116, 280, 408, 400), Common::Point(116, 280)); + filesListChanged = false; + } + // Don't redraw the scroll buttons + for (unsigned int box = 0; box < 8; box++) { + if (box == boxSelected) { + // Selected + _fontManager.setForeColor(240); + } else if (box == 6 && boxSelected == -1u) { + // Ok and no file selected + _fontManager.setForeColor(245); + } else if (box == boxHovered) { + // Hovered + _fontManager.setForeColor(241); + } else { + // Other cases + _fontManager.setForeColor(243); + } + + if (box == boxSelected && saveMode) { + Common::Rect boxRct = boxes.getBoxRect(box); + boxRct.top -= 2; + surface.blitFrom(*bgFrame, boxRct, Common::Point(boxRct.left, boxRct.top)); + boxRct.top += 2; + if (textCursorState) { + surface.vLine(textCursorPos, boxRct.top, boxRct.top + 11, 240); + } + } + boxes.display(box, _fontManager); + if (box < 6) { + // Draw line below + surface.hLine(116, 280 + box * 20 + 15, 407, 243); // minus 1 because hLine draws inclusive + + // Display file number + _fontManager.displayInt(126, 280 + box * 20, fileListOffset + box + 1); + } + } + redraw = false; + g_system->copyRectToScreen(surface.getPixels(), surface.pitch, 0, 0, surface.w, surface.h); + } + + g_system->updateScreen(); + pollEvents(); + Common::KeyState key = getNextKey(); + unsigned int mousePressed = getCurrentMouseButton(); + + if (!mousePressed) { + bool boxFound = false; + // Don't handle scroll arrows hovering + for (unsigned int box = 0; box < 8; box++) { + if (boxes.hitTest(box, getMousePos())) { + boxFound = true; + if (boxHovered != box) { + boxHovered = box; + redraw = true; + } + } + } + if (!boxFound && boxHovered != -1u) { + boxHovered = -1; + redraw = true; + } + } + if (key == Common::KEYCODE_RETURN || (mousePressed == 1 && boxHovered == 6)) { + // OK + if (boxSelected != -1u) { + Common::String &selectedSaveName = savesList[boxSelected + fileListOffset]; + if (!selectedSaveName.size()) { + selectedSaveName = _messages[56]; // No name + } + redraw = true; + finished = true; + } + } else if (mousePressed == 1) { + if (boxHovered == 7) { + // Cancel + boxSelected = -1; + finished = true; + } else if (boxHovered != -1u && boxHovered != boxSelected) { + // This can only be a file + bool existingSave = (savesList[boxHovered + fileListOffset] != _messages[55]); + // Don't allow to save on slot 0 when visiting to avoid problems with original visit save + bool validSave = !(_isVisiting && saveMode && boxSelected == 0); + if ((saveMode || existingSave) && validSave) { + // Restore old name + if (saveMode && boxSelected != -1u) { + savesList[boxSelected + fileListOffset] = saveNameBackup; + filesListChanged = true; + } + boxSelected = boxHovered; + // Backup new one + saveNameBackup = savesList[boxSelected + fileListOffset]; + // Not an existing save clear free name + if (!existingSave) { + savesList[boxSelected + fileListOffset] = ""; + } + redraw = true; + } + } + } + if (boxSelected != -1u && saveMode) { + if (key.keycode != Common::KEYCODE_INVALID) { + // Reference means we edit in place + Common::String &selectedSaveName = savesList[boxSelected + fileListOffset]; + if (key == Common::KEYCODE_BACKSPACE && selectedSaveName.size() > 0) { + selectedSaveName.deleteLastChar(); + textCursorNextState = 0; + redraw = true; + } else if (key.ascii > 32 && key.ascii < 256 && selectedSaveName.size() < 20) { + selectedSaveName += key.ascii; + textCursorNextState = 0; + redraw = true; + } + } + if (g_system->getMillis() > textCursorNextState) { + textCursorNextState = g_system->getMillis() + 200; // Blink at 200ms period + unsigned int width = _fontManager.getStrWidth(savesList[boxSelected + fileListOffset]); + Common::Rect boxRct = boxes.getBoxRect(boxSelected); + textCursorPos = boxRct.left + width; + textCursorState = !textCursorState; + redraw = true; + } + } + if (!autoRepeatInhibit) { + bool autoRepeatTrigger = false; + unsigned int oldFileListOffset = fileListOffset; + if (mousePressed) { + if (boxes.hitTest(8, getMousePos()) && fileListOffset > 0) { + fileListOffset--; + autoRepeatTrigger = true; + } else if (boxes.hitTest(9, getMousePos()) && fileListOffset < 99 - 6) { + fileListOffset++; + autoRepeatTrigger = true; + } + } else if (key == Common::KEYCODE_UP) { + if (fileListOffset > 0) { + fileListOffset--; + autoRepeatTrigger = true; + } + } else if (key == Common::KEYCODE_DOWN) { + if (fileListOffset < 99 - 6) { + fileListOffset++; + autoRepeatTrigger = true; + } + } else if (key == Common::KEYCODE_PAGEUP) { + if (fileListOffset > 6) { + fileListOffset -= 6; + } else { + fileListOffset = 0; + } + } else if (key == Common::KEYCODE_PAGEDOWN) { + if (fileListOffset < 99 - 6 - 6) { + fileListOffset += 6; + } else { + fileListOffset = 99 - 6; + } + } + if (autoRepeatTrigger) { + // Restore old name + if (saveMode && boxSelected != -1u) { + savesList[boxSelected + oldFileListOffset] = saveNameBackup; + } + boxHovered = -1; + boxSelected = -1; + autoRepeatInhibit = true; + autoRepeatEndInhibit = g_system->getMillis() + autoRepeatDelay; + filesListChanged = true; + } + } + if (autoRepeatInhibit && g_system->getMillis() > autoRepeatEndInhibit) { + autoRepeatInhibit = false; + autoRepeatDelay = 60; // Next rounds will wait 60ms after first one + } + if (!mousePressed && key == Common::KEYCODE_INVALID) { + // Nothing was clicked or pressed: set back autoRepeatDelay to 250ms + autoRepeatDelay = 250; + } + } + if (boxSelected != -1u) { + saveName = savesList[boxSelected + fileListOffset]; + // TODO: save list offset + return boxSelected + fileListOffset + 1; + } else { + return -1; + } +} + +const MsgBoxParameters CryOmni3DEngine_Versailles::kWarpMsgBoxParameters = { + 9, 241, 22, 2, 1, 36, 18, 20, 10, 5 +}; + +const MsgBoxParameters CryOmni3DEngine_Versailles::kFixedimageMsgBoxParameters = { + 3, 241, 22, 2, 1, 40, 20, 20, 10, 3 +}; + +void CryOmni3DEngine_Versailles::displayMessageBox(const MsgBoxParameters ¶ms, + const Graphics::Surface *surface, const Common::String &msg, const Common::Point &position, + const Common::Functor0<void> &callback) { + Graphics::ManagedSurface dstSurface; + dstSurface.create(surface->w, surface->h, surface->format); + dstSurface.blitFrom(*surface); + + _fontManager.setSurface(&dstSurface); + _fontManager.setCurrentFont(params.font); + _fontManager.setTransparentBackground(true); + _fontManager.setForeColor(params.foreColor); + _fontManager.setLineHeight(params.lineHeight); + _fontManager.setSpaceWidth(params.spaceWidth); + _fontManager.setCharSpacing(params.charSpacing); + + unsigned int width = params.initialWidth; + unsigned int height = params.initialHeight; + unsigned int lineCount = 0; + Common::Point pt = position; + Common::Rect rct; + + bool notEnough = true; + bool tooLarge = false; + + while (notEnough && !tooLarge) { + width += params.incrementWidth; + height += params.incrementHeight; + rct = Common::Rect::center(pt.x, pt.y, width, height); + if (rct.left < 10) { + rct.left = 10; + if (pt.x < 320) { + pt.x += 10; + } + } + if (rct.right >= 630) { + rct.right = 630; + if (pt.x > 320) { + pt.x -= 10; + } + } + if (rct.top <= 10) { + rct.top = 10; + if (pt.y < 240) { + pt.y += 10; + } + } + if (rct.bottom >= 470) { + rct.bottom = 470; + if (pt.y > 235) { // sic. + pt.y -= 10; + } + } + if (rct.left == 10 && rct.top == 10 && rct.right == 630 && rct.bottom == 470) { + tooLarge = true; + } + lineCount = _fontManager.getLinesCount(msg, rct.width() - 12); + if (lineCount && lineCount * _fontManager.lineHeight() + 18 < (unsigned int)rct.height()) { + notEnough = false; + } + } + rct.setHeight(lineCount * _fontManager.lineHeight() + 12); + if (rct.bottom > 479) { + rct.bottom = 479; + } + + Graphics::Surface subSurface = dstSurface.getSubArea(rct); + makeTranslucent(subSurface, surface->getSubArea(rct)); + rct.grow(-6); + _fontManager.setupBlock(rct); + _fontManager.displayBlockText(msg); + // TODO: countdown + + g_system->copyRectToScreen(dstSurface.getPixels(), dstSurface.pitch, 0, 0, + dstSurface.w, dstSurface.h); + + waitMouseRelease(); + unsigned int disappearTime = g_system->getMillis() + msg.size() * params.timeoutChar * 10; + bool finished = false; + while (!finished) { + g_system->updateScreen(); + + callback(); + + if (g_system->getMillis() > disappearTime) { + finished = true; + } + if (getCurrentMouseButton() == 1) { + finished = true; + } + } + + // Restore image + g_system->copyRectToScreen(surface->getPixels(), surface->pitch, 0, 0, surface->w, surface->h); +} + +void CryOmni3DEngine_Versailles::displayMessageBoxWarp(const Common::String &message) { + Common::Point mousePos = getMousePos(); + mousePos += Common::Point(0, 32); + if (mousePos.x > 639) { + mousePos.x = 639; + } + if (mousePos.y > 479) { + mousePos.y = 479; + } + displayMessageBox(kWarpMsgBoxParameters, _omni3dMan.getSurface(), message, mousePos, + Common::Functor0Mem<void, CryOmni3DEngine_Versailles>(this, + &CryOmni3DEngine_Versailles::warpMsgBoxCB)); +} + +void CryOmni3DEngine_Versailles::displayCredits() { + waitMouseRelease(); + + Graphics::ManagedSurface creditsSurface; + Image::ImageDecoder *imageDecoder = loadHLZ("credits.hlz"); + if (!imageDecoder) { + return; + } + + const Graphics::Surface *bgFrame = imageDecoder->getSurface(); + + byte palette[256 * 3]; + memset(palette, 0, 256 * 3); + // getPalette returns the first color not index 0 + memcpy(palette + 3 * imageDecoder->getPaletteStartIndex(), imageDecoder->getPalette(), + 3 * imageDecoder->getPaletteColorCount()); + copySubPalette(palette, _cursorPalette, 240, 8); + + creditsSurface.create(bgFrame->w, bgFrame->h, bgFrame->format); + + _fontManager.setCurrentFont(3); + _fontManager.setTransparentBackground(true); + _fontManager.setForeColor(243); + _fontManager.setLineHeight(14); + _fontManager.setSpaceWidth(0); + _fontManager.setCharSpacing(1); + _fontManager.setSurface(&creditsSurface); + + Common::File creditsFile; + if (!creditsFile.open("credits.txt")) { + warning("Failed to open credits file: %s", "credits.txt"); + delete imageDecoder; + return; + } + + g_system->showMouse(false); + + char line[256]; + bool end = false; + bool calculatedScreen = false; + unsigned int lineHeight = 20; + unsigned int currentY = 0; + int32 fileOffset = 0; + bool skipScreen = false; + + while (!end && creditsFile.readLine(line, ARRAYSIZE(line))) { + // Remove line ending + line[strlen(line) - 1] = '\0'; + if (!strncmp(line, "###", 3)) { + // Prefix for commands + if (!strncmp(line + 3, "ECRAN", 5)) { + // ECRAN command + if (calculatedScreen) { + g_system->copyRectToScreen(creditsSurface.getPixels(), creditsSurface.pitch, 0, 0, + creditsSurface.w, creditsSurface.h); + if (skipScreen) { + // Just display palette + setPalette(palette, 0, 256); + } else { + fadeInPalette(palette); + } + skipScreen = false; + // Wait + unsigned int endScreenTime = g_system->getMillis() + 6000; + while (g_system->getMillis() < endScreenTime && !skipScreen) { + g_system->updateScreen(); + if (pollEvents()) { + if (getCurrentMouseButton() == 1) { + skipScreen = true; + } + Common::KeyCode kc = getNextKey().keycode; + while (kc != Common::KEYCODE_INVALID) { + if (kc == Common::KEYCODE_SPACE) { + skipScreen = true; + break; + } else if (kc == Common::KEYCODE_ESCAPE) { + skipScreen = true; + end = true; + break; + } + kc = getNextKey().keycode; + } + clearKeys(); + } + if (g_engine->shouldQuit()) { + skipScreen = true; + end = true; + } + } + if (!skipScreen) { + fadeOutPalette(); + fillSurface(0); + } + currentY = 0; + fileOffset = creditsFile.pos(); + calculatedScreen = false; + } else { + // We just finished calculated all lines, roll back and display them + creditsFile.seek(fileOffset, SEEK_SET); + calculatedScreen = true; + if (currentY <= 480 - lineHeight) { + // Center in screen + currentY = (480 - lineHeight) / 2 - currentY / 2; + } else { + currentY = 3; + } + creditsSurface.blitFrom(*bgFrame); + } + } else if (!strcmp(line + 3, "T0")) { + _fontManager.setCurrentFont(1); + lineHeight = _fontManager.getFontMaxHeight() + 10; + } else if (!strcmp(line + 3, "T1")) { + _fontManager.setCurrentFont(2); + lineHeight = _fontManager.getFontMaxHeight() + 10; + } else if (!strcmp(line + 3, "T2")) { + _fontManager.setCurrentFont(4); + lineHeight = _fontManager.getFontMaxHeight() + 10; + } else if (!strcmp(line + 3, "T3")) { + _fontManager.setCurrentFont(2); + lineHeight = _fontManager.getFontMaxHeight() + 10; + } else if (!strcmp(line + 3, "T4")) { + _fontManager.setCurrentFont(5); + lineHeight = _fontManager.getFontMaxHeight() + 10; + } else if (!strcmp(line + 3, "T5")) { + _fontManager.setCurrentFont(6); + lineHeight = _fontManager.getFontMaxHeight() + 10; + } else { + warning("Unknown ### command : %s", line + 3); + } + } else { + // Text + if (calculatedScreen) { + unsigned int width = _fontManager.getStrWidth(line); + // Center around 315 + _fontManager.displayStr(315 - width / 2, currentY, line); + } + currentY += lineHeight; + } + } + g_system->showMouse(true); +} + +} // End of namespace Versailles +} // End of namespace CryOmni3D diff --git a/engines/cryomni3d/versailles/music.cpp b/engines/cryomni3d/versailles/music.cpp new file mode 100644 index 0000000000..524c5244a4 --- /dev/null +++ b/engines/cryomni3d/versailles/music.cpp @@ -0,0 +1,281 @@ +/* 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 "audio/audiostream.h" +#include "audio/decoders/wave.h" + +#include "common/config-manager.h" +#include "common/error.h" +#include "common/file.h" + +#include "cryomni3d/versailles/engine.h" + +namespace CryOmni3D { +namespace Versailles { + +const char *CryOmni3DEngine_Versailles::kMusicFiles[8][8] = { + { "1amb", }, // Level 1 + { "2amb", "2amb2", "2amb1" }, // Level 2 + { "3amb", "3amb1", "3amb2" }, // Level 3 + { "4amb", "4amb1" }, // Level 4 + { "5amb1", "5amb2" }, // Level 5 + { "6amb1", "6amb2", "6amb3", "6amb4" }, // Level 6 + { "7amb", }, // Level 7 + { "3amb", "3amb1", "3amb2", "2amb", "2amb1", "2amb2", "4amb" }, // Level 8 +}; + +void CryOmni3DEngine_Versailles::musicUpdate() { + if (!_isPlaying || _currentLevel <= 0 || + _mixer->isSoundTypeMuted(Audio::Mixer::kMusicSoundType) || + _mixer->getVolumeForSoundType(Audio::Mixer::kMusicSoundType) == 0) { + // No music in all of these cases + musicStop(); + return; + } + + unsigned int musicId = getMusicId(_currentLevel, _currentPlaceId); + const char *musicBName = kMusicFiles[_currentLevel - 1][musicId]; + assert(musicBName != nullptr); + + // Ensure sound is playing in all cases + musicResume(); + + if (musicBName == _musicCurrentFile) { + // Same file, nothing more to do + return; + } + + // New file, stop the old one first + musicStop(); + + Common::String musicFName = musicBName; + musicFName += ".wav"; + + Common::File *musicFile = new Common::File(); + if (!musicFile->open(musicFName)) { + warning("Failed to open music file %s/%s", musicBName, musicFName.c_str()); + delete musicFile; + return; + } + + Audio::SeekableAudioStream *musicDecoder = Audio::makeWAVStream(musicFile, DisposeAfterUse::YES); + // We lost ownership of the musicFile just set it to nullptr and don't use it + musicFile = nullptr; + + if (!musicDecoder) { + warning("Failed to decode music file %s/%s", musicBName, musicFName.c_str()); + return; + } + + Audio::AudioStream *loopStream = Audio::makeLoopingAudioStream(musicDecoder, 0); + // We lost ownership of musicDecoder just set it to nullptr and don't use it + musicDecoder = nullptr; + + _mixer->playStream(Audio::Mixer::kMusicSoundType, &_musicHandle, loopStream); + _musicCurrentFile = musicBName; +} + +void CryOmni3DEngine_Versailles::musicPause() { + _mixer->pauseHandle(_musicHandle, true); +} + +void CryOmni3DEngine_Versailles::musicResume() { + _mixer->pauseHandle(_musicHandle, false); +} + +void CryOmni3DEngine_Versailles::musicStop() { + // Fade the music first + if (_mixer->isSoundHandleActive(_musicHandle)) { + // We recreate the real channel volume to decrease this one 2 by 2 + int musicVol = _mixer->getVolumeForSoundType(Audio::Mixer::kMusicSoundType); + byte channelVol = _mixer->getChannelVolume(_musicHandle); + int realVolume = (musicVol * channelVol) / Audio::Mixer::kMaxChannelVolume; + bool skip = false; + while (realVolume > 0 && !skip) { + // pollEvents waits for 10ms + realVolume -= 2; + channelVol = CLIP((realVolume * Audio::Mixer::kMaxChannelVolume) / musicVol, 0, 255); + _mixer->setChannelVolume(_musicHandle, channelVol); + if (pollEvents() && checkKeysPressed(1, Common::KEYCODE_SPACE)) { + skip = true; + } + } + } + _mixer->stopHandle(_musicHandle); + _musicCurrentFile = nullptr; +} + +void CryOmni3DEngine_Versailles::musicSetQuiet(bool quiet) { + float newFactor = quiet ? 3.5f : 1.f; + if (newFactor != _musicVolumeFactor) { + _musicVolumeFactor = newFactor; + syncSoundSettings(); + } +} + +bool CryOmni3DEngine_Versailles::musicWouldChange(unsigned int level, unsigned int placeId) const { + unsigned int musicId = getMusicId(level, placeId); + const char *musicFile = kMusicFiles[_currentLevel - 1][musicId]; + + return musicFile != _musicCurrentFile; +} + +unsigned int CryOmni3DEngine_Versailles::getMusicId(unsigned int level, + unsigned int placeId) const { + // No need of place state + switch (level) { + case 1: + // Only one music + return 0; + case 2: + switch (placeId) { + case 4: + return 1; + case 10: + case 11: + case 13: + return 2; + default: + return 0; + } + case 3: + switch (placeId) { + case 1: + case 2: + case 3: + case 4: + return 2; + case 6: + case 7: + case 8: + case 12: + case 24: + return 1; + default: + return 0; + } + case 4: + switch (placeId) { + case 1: + case 2: + case 3: + case 4: + return 1; + default: + return 0; + } + case 5: + switch (placeId) { + case 6: + case 7: + case 8: + case 12: + case 26: + case 27: + case 30: + case 31: + return 1; + default: + return 0; + } + case 6: + switch (placeId) { + case 1: + return 3; + case 3: + case 4: + case 5: + case 6: + case 8: + case 9: + case 10: + case 11: + return 0; + case 14: + case 16: + case 17: + case 19: + case 20: + case 22: + case 24: + case 26: + case 27: + case 32: + case 34: + case 38: + case 44: + return 2; + default: + return 1; + } + case 7: + return 0; + case 8: + switch (placeId) { + case 1: + case 2: + case 3: + case 4: + return 2; + case 6: + case 7: + case 8: + return 1; + case 9: + case 10: + case 11: + return 0; + case 12: + return 1; + case 13: + case 14: + case 15: + case 16: + return 0; + case 24: + return 1; + case 33: + case 34: + case 35: + return 5; + case 36: + case 37: + case 38: + case 39: + return 3; + case 40: + return 4; + case 42: + case 43: + case 44: + return 6; + default: + return 0; + + } + default: + error("Invalid level %d when choosing music", level); + } +} + +} // End of namespace Versailles +} // End of namespace CryOmni3D diff --git a/engines/cryomni3d/versailles/saveload.cpp b/engines/cryomni3d/versailles/saveload.cpp new file mode 100644 index 0000000000..8afefa35ec --- /dev/null +++ b/engines/cryomni3d/versailles/saveload.cpp @@ -0,0 +1,315 @@ +/* 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 "common/archive.h" +#include "common/config-manager.h" +#include "common/file.h" +#include "common/savefile.h" +#include "common/system.h" + +#include "cryomni3d/versailles/engine.h" + +#define DEBUG_SAVE + +namespace CryOmni3D { +namespace Versailles { + +#define SAVE_DESCRIPTION_LEN 20 + +Common::String CryOmni3DEngine_Versailles::getSaveFileName(bool visit, unsigned int saveNum) const { + return Common::String::format("%s%s.%04u", _targetName.c_str(), visit ? "_visit" : "", saveNum); +} + +bool CryOmni3DEngine_Versailles::canVisit() const { + // Build a custom SearchSet + const Common::FSNode gameDataDir(ConfMan.get("path")); + Common::SearchSet visitsSearchSet; + visitsSearchSet.addSubDirectoryMatching(gameDataDir, "datas_v/savegame/visite", 1); + return visitsSearchSet.hasFile("game0001.sav"); +} + +void CryOmni3DEngine_Versailles::getSavesList(bool visit, Common::StringArray &saveNames) { + Common::SaveFileManager *saveMan = g_system->getSavefileManager(); + + char saveName[SAVE_DESCRIPTION_LEN + 1]; + saveName[SAVE_DESCRIPTION_LEN] = '\0'; + Common::String pattern = Common::String::format("%s%s.????", _targetName.c_str(), + visit ? "_visit" : ""); + Common::StringArray filenames = saveMan->listSavefiles(pattern); + sort(filenames.begin(), filenames.end()); // Sort (hopefully ensuring we are sorted numerically..) + + saveNames.clear(); + saveNames.reserve(100); + + int num = 1; + int slotNum; + + if (visit) { + // Add bootstrap visit + const Common::FSNode gameDataDir(ConfMan.get("path")); + Common::SearchSet visitsSearchSet; + visitsSearchSet.addSubDirectoryMatching(gameDataDir, "datas_v/savegame/visite", 1); + if (visitsSearchSet.hasFile("game0001.sav")) { + Common::File visitFile; + if (!visitFile.open("game0001.sav", visitsSearchSet)) { + error("Can't load visit file"); + } + visitFile.read(saveName, SAVE_DESCRIPTION_LEN); + saveNames.push_back(saveName); + } else { + warning("visiting mode but no bootstrap"); + // No bootstrap visit, too bad + saveNames.push_back(_messages[55]); //Fill with free slot + } + num++; + } + + for (Common::StringArray::const_iterator file = filenames.begin(); file != filenames.end(); + ++file) { + // Obtain the last 4 digits of the filename, since they correspond to the save slot + slotNum = atoi(file->c_str() + file->size() - 4); + + if (slotNum >= 1 && slotNum <= 99) { + while (num < slotNum) { + saveNames.push_back(_messages[55]); //Fill with free slot + num++; + } + + num++; +#ifdef DEBUG_SAVE + Common::InSaveFile *in = _saveFileMan->openRawFile(*file); +#else + Common::InSaveFile *in = _saveFileMan->openForLoading(*file); +#endif + if (in) { + in->read(saveName, SAVE_DESCRIPTION_LEN); + saveNames.push_back(saveName); + delete in; + } + } + } + + for (unsigned int i = saveNames.size(); i < 100; i++) { + saveNames.push_back(_messages[55]); + } +} + +void CryOmni3DEngine_Versailles::saveGame(bool visit, unsigned int saveNum, + const Common::String &saveName) const { + if (visit && saveNum == 1) { + error("Can't erase bootstrap visit"); + } + + Common::String saveFileName = getSaveFileName(visit, saveNum); + + Common::OutSaveFile *out; + + if (!(out = _saveFileMan->openForSaving(saveFileName, +#ifdef DEBUG_SAVE + false +#else + true +#endif + ))) { + return; + } + + // Write save name + char saveNameC[SAVE_DESCRIPTION_LEN]; + memset(saveNameC, 0, sizeof(saveNameC)); + strncpy(saveNameC, saveName.c_str(), sizeof(saveNameC)); + out->write(saveNameC, sizeof(saveNameC)); + + // dummy values + out->writeUint32LE(0); + out->writeUint32BE(0); + out->writeUint32BE(0); + + // Dialog variables + assert(_dialogsMan.size() < 200); + for (unsigned int i = 0; i < _dialogsMan.size(); i++) { + out->writeByte(_dialogsMan[i]); + } + for (unsigned int i = _dialogsMan.size(); i < 200; i++) { + out->writeByte(0); + } + + // Inventory + assert(_inventory.size() == 50); + for (Inventory::const_iterator it = _inventory.begin(); it != _inventory.end(); it++) { + unsigned int objId = -1; + if (*it != nullptr) { + // Inventory contains pointers to objects stored in _objects + objId = *it - _objects.begin(); + } + out->writeUint32BE(objId); + } + // Offset of inventory in toolbar + out->writeUint32BE(_toolbar.inventoryOffset()); + + // Level, place, warp position + out->writeUint32BE(_currentLevel); + out->writeUint32BE(_currentPlaceId); + out->writeDoubleBE(_omni3dMan.getAlpha()); + out->writeDoubleBE(_omni3dMan.getBeta()); + + // Places states + assert(_placeStates.size() < 100); + Common::Array<PlaceState>::const_iterator placeIt = _placeStates.begin(); + for (unsigned int i = 0; placeIt != _placeStates.end(); placeIt++, i++) { + out->writeUint32BE(placeIt->state); + } + for (unsigned int i = _placeStates.size(); i < 100; i++) { + out->writeUint32BE(0); + } + + // Game variables + assert(_gameVariables.size() < 100); + for (Common::Array<unsigned int>::const_iterator it = _gameVariables.begin(); + it != _gameVariables.end(); it++) { + out->writeUint32BE(*it); + } + for (unsigned int i = _gameVariables.size(); i < 100; i++) { + out->writeUint32BE(0); + } + + out->finalize(); + + delete out; +} + +bool CryOmni3DEngine_Versailles::loadGame(bool visit, unsigned int saveNum) { + Common::SeekableReadStream *in; + + if (visit && saveNum == 1) { + // Load bootstrap visit + const Common::FSNode gameDataDir(ConfMan.get("path")); + Common::SearchSet visitsSearchSet; + visitsSearchSet.addSubDirectoryMatching(gameDataDir, "datas_v/savegame/visite", 1); + Common::File *visitFile = new Common::File(); + if (!visitFile->open("game0001.sav", visitsSearchSet)) { + delete visitFile; + error("Can't load visit file"); + } + in = visitFile; + } else { + Common::String saveFileName = getSaveFileName(visit, saveNum); + +#ifdef DEBUG_SAVE + in = _saveFileMan->openRawFile(saveFileName); +#else + in = _saveFileMan->openForLoading(saveFileName); +#endif + } + + if (!in || in->size() != 1260) { + return false; + } + + musicStop(); + + // Load save name but don't use it + char saveNameC[SAVE_DESCRIPTION_LEN]; + in->read(saveNameC, sizeof(saveNameC)); + + // dummy values + in->readUint32LE(); + in->readUint32BE(); + in->readUint32BE(); + + // Dialog variables + assert(_dialogsMan.size() < 200); + for (unsigned int i = 0; i < _dialogsMan.size(); i++) { + _dialogsMan[i] = in->readByte(); + } + for (unsigned int i = _dialogsMan.size(); i < 200; i++) { + in->readByte(); + } + + // Inventory + assert(_inventory.size() == 50); + for (Inventory::iterator it = _inventory.begin(); it != _inventory.end(); it++) { + unsigned int objId = in->readUint32BE(); + if (objId >= _objects.size()) { + objId = -1; + } + if (objId != -1u) { + *it = _objects.begin() + objId; + } else { + *it = nullptr; + } + } + // Offset of inventory in toolbar + _toolbar.setInventoryOffset(in->readUint32BE()); + + // Level, place, warp position + _currentLevel = in->readUint32BE(); + // Use nextPlace to force place move + _nextPlaceId = in->readUint32BE(); + + // Store alpha and beta for later use + double alpha = in->readDoubleBE(); + double beta = in->readDoubleBE(); + + // Places states + // Store them and use them once we called initNewLevel, we can't call it before because it needs _gameVariables (and especially kCurrentTime) to be correctly set + uint32 placesStates[100]; + for (unsigned int i = 0; i < 100; i++) { + placesStates[i] = in->readUint32BE(); + } + + // Game variables + assert(_gameVariables.size() < 100); + for (Common::Array<unsigned int>::iterator it = _gameVariables.begin(); it != _gameVariables.end(); + it++) { + *it = in->readUint32BE(); + } + for (unsigned int i = _gameVariables.size(); i < 100; i++) { + in->readUint32BE(); + } + + delete in; + + if (_gameVariables[GameVariables::kCurrentTime] == 0) { + _gameVariables[GameVariables::kCurrentTime] = 1; + } + + // Everything has been loaded, setup new level + // We will set places states and warp coordinates just after that to avoid them from being reset + initNewLevel(_currentLevel); + + _omni3dMan.setAlpha(alpha); + _omni3dMan.setBeta(beta); + + // _placeStates has just been resized in initNewLevel + unsigned int i = 0; + for (Common::Array<PlaceState>::iterator placeIt = _placeStates.begin(); + placeIt != _placeStates.end() && i < ARRAYSIZE(placesStates); placeIt++, i++) { + placeIt->state = placesStates[i]; + } + + // TODO: countdown + + return true; +} + +} // End of namespace Versailles +} // End of namespace CryOmni3D diff --git a/engines/cryomni3d/versailles/toolbar.cpp b/engines/cryomni3d/versailles/toolbar.cpp new file mode 100644 index 0000000000..e0168e29cc --- /dev/null +++ b/engines/cryomni3d/versailles/toolbar.cpp @@ -0,0 +1,599 @@ +/* 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 "common/system.h" + +#include "cryomni3d/cryomni3d.h" + +#include "cryomni3d/versailles/toolbar.h" + +namespace CryOmni3D { +namespace Versailles { + +void Toolbar::init(const Sprites *sprites, FontManager *fontManager, + const Common::Array<Common::String> *messages, Inventory *inventory, + CryOmni3DEngine *engine) { + _sprites = sprites; + _fontManager = fontManager; + _messages = messages; + _inventory = inventory; + _engine = engine; + + _bgSurface.create(640, 60, Graphics::PixelFormat::createFormatCLUT8()); + _destSurface.create(640, 60, Graphics::PixelFormat::createFormatCLUT8()); + + // Inventory + addZone(51, 56, Common::Point(211, 8), &Toolbar::callbackInventory<0>); + addZone(51, 56, Common::Point(258, 8), &Toolbar::callbackInventory<1>); + addZone(51, 56, Common::Point(305, 8), &Toolbar::callbackInventory<2>); + addZone(51, 56, Common::Point(352, 8), &Toolbar::callbackInventory<3>); + addZone(51, 56, Common::Point(399, 8), &Toolbar::callbackInventory<4>); + addZone(51, 56, Common::Point(446, 8), &Toolbar::callbackInventory<5>); + addZone(51, 56, Common::Point(493, 8), &Toolbar::callbackInventory<6>); + addZone(51, 56, Common::Point(540, 8), &Toolbar::callbackInventory<7>); + + // Documentation + const Graphics::Cursor &cursorDoc = _sprites->getCursor(133); + Common::Point docPos(627 - cursorDoc.getWidth(), 42 - cursorDoc.getHeight()); + addZone(133, 137, docPos, &Toolbar::callbackDocumentation); + + // Options + const Graphics::Cursor &cursorOpt = _sprites->getCursor(225); + Common::Point optPos(0, 60 - cursorOpt.getHeight()); + addZone(225, 225, optPos, &Toolbar::callbackOptions); + + // Previous or next + addZone(183, -1, Common::Point(190, 18), &Toolbar::callbackInventoryPrev); + addZone(240, -1, Common::Point(574, 18), &Toolbar::callbackInventoryNext); + // View + addZone(142, -1, Common::Point(158, 12), &Toolbar::callbackViewObject); +} + +Toolbar::~Toolbar() { + _bgSurface.free(); + _destSurface.free(); +} + +void Toolbar::inventoryChanged(unsigned int newPosition) { + if (newPosition != -1u && newPosition > _inventoryOffset) { + _inventoryOffset = newPosition - 7; + } + // Refresh + updateZones(); +} + +void Toolbar::addZone(uint16 cursorMainId, uint16 cursorSecondaryId, Common::Point position, + ZoneCallback callback) { + const Graphics::Cursor &cursorMain = _sprites->getCursor(cursorMainId); + Common::Rect rct(cursorMain.getWidth(), cursorMain.getHeight()); + rct.moveTo(position); + + // By default it's the secondary image + Zone zone = { rct, cursorMainId, cursorSecondaryId, callback, true, false }; + _zones.push_back(zone); +} + +Common::Array<Toolbar::Zone>::const_iterator Toolbar::hitTestZones(const Common::Point &mousePos) +const { + Common::Array<Zone>::const_iterator it; + for (it = _zones.begin(); it != _zones.end(); it++) { + if (!it->hidden && it->rect.contains(mousePos) && it->callback) { + break; + } + } + return it; +} + +unsigned int Toolbar::captureEvent(const Common::Point &mousePos, unsigned int dragStatus) { + unsigned int result = 0; + Common::Array<Zone>::const_iterator it = hitTestZones(mousePos); + if (it != _zones.end()) { + result = (this->*(it->callback))(dragStatus); + } + return result; +} + +void Toolbar::updateZones() { + _zones[8].secondary = !_engine->hasPlaceDocumentation(); + + Inventory::const_iterator inventoryIt, inventorySelectedIt; + if (!_inventoryEnabled) { + _inventoryMaxOffset = 0; + _inventoryOffset = 0; + _zones[10].secondary = true; + _zones[11].secondary = true; + } else { + _inventoryMaxOffset = 0; + // Find an object in inventory after the 8 first + for (inventoryIt = _inventory->begin() + 8; inventoryIt != _inventory->end(); inventoryIt++) { + if (*inventoryIt != nullptr) { + _inventoryMaxOffset = (inventoryIt - _inventory->begin()) - 7; + } + } + _zones[10].secondary = !_inventoryMaxOffset; + _zones[11].secondary = !_inventoryMaxOffset; + if (_inventoryOffset > _inventoryMaxOffset) { + // Clamp inventory offset to its max + _inventoryOffset = _inventoryMaxOffset; + } + inventoryIt = _inventory->begin() + _inventoryOffset; + inventorySelectedIt = _inventory->begin() + _inventorySelected; + } + // Inventory zones are from 0 to 7 + for (Common::Array<Zone>::iterator zoneIt = _zones.begin(); zoneIt != _zones.begin() + 8; + zoneIt++, inventoryIt++) { + if (!_inventoryEnabled) { + zoneIt->hidden = true; + zoneIt->imageMain = 0; + zoneIt->imageSecondary = 0; + zoneIt->secondary = false; + } else if (inventoryIt >= _inventory->end() || *inventoryIt == nullptr) { + // Nothing in inventory at this position + zoneIt->hidden = false; + zoneIt->imageMain = 51; + zoneIt->imageSecondary = 56; + zoneIt->secondary = true; + } else { + // Setup inventory icon + zoneIt->hidden = false; + zoneIt->imageMain = (*inventoryIt)->idCA(); + zoneIt->imageSecondary = (*inventoryIt)->idCl(); + zoneIt->secondary = (inventorySelectedIt != inventoryIt); + } + } +} + +unsigned int Toolbar::callbackInventory(unsigned int invId, unsigned int dragStatus) { + if (!_inventoryEnabled) { + return 0; + } + + invId += _inventoryOffset; + Object *obj = nullptr; + if (invId < _inventory->size()) { + obj = (*_inventory)[invId]; + } + if (obj == nullptr) { + return 0; + } + + if (!obj->valid()) { + return 0; + } + + switch (dragStatus) { + case kDragStatus_Pressed: + _inventorySelected = invId; + _engine->setCursor(181); + _zones[12].secondary = (obj->viewCallback() == nullptr); + _inventory_button_dragging = true; + return 1; + case kDragStatus_Dragging: + if (_inventorySelected == invId) { + return 0; + } + _inventorySelected = invId; + _zones[12].secondary = (obj->viewCallback() == nullptr); + _inventory_button_dragging = true; + return 1; + case kDragStatus_Finished: + _engine->setCursor(obj->idSl()); + _inventory->setSelectedObject(obj); + _inventorySelected = invId; + return 1; + default: + return 0; + } + +} + +unsigned int Toolbar::callbackInventoryPrev(unsigned int dragStatus) { + if (!_inventoryEnabled) { + return 0; + } + + if (dragStatus == kDragStatus_Pressed && _inventoryOffset > 0) { + // Restart auto repeat only if there could be something + _engine->setAutoRepeatClick(150); + _inventoryOffset--; + return 1; + } + // In any other case we didn't do anything + return 0; +} + +unsigned int Toolbar::callbackInventoryNext(unsigned int dragStatus) { + if (!_inventoryEnabled) { + return 0; + } + + if (dragStatus == kDragStatus_Pressed && _inventoryOffset < _inventoryMaxOffset) { + _engine->setAutoRepeatClick(150); + _inventoryOffset++; + return 1; + } + // In any other case we didn't do anything + return 0; +} + +unsigned int Toolbar::callbackViewObject(unsigned int dragStatus) { + if (!_inventoryEnabled) { + return 0; + } + + _mouse_in_view_object = true; + + if (_inventorySelected == -1u) { + // Nothing selected in toolbar + return 0; + } + Inventory::const_iterator inventorySelectedIt = _inventory->begin() + _inventorySelected; + Object *selectedObject = *inventorySelectedIt; + if (selectedObject == nullptr || selectedObject->viewCallback() == nullptr) { + // Nothing to view, the sprite isn't even displayed + return 0; + } + + switch (dragStatus) { + case kDragStatus_NoDrag: + _backup_selected_object = selectedObject; + _engine->setCursor(181); + return 0; + case kDragStatus_Pressed: + case kDragStatus_Dragging: + return 1; + case kDragStatus_Finished: + // Just clicked + g_system->showMouse(false); + (*selectedObject->viewCallback())(); + g_system->showMouse(true); + _parentMustRedraw = true; + _shortExit = true; + return 1; + default: + return 0; + } +} + +unsigned int Toolbar::callbackOptions(unsigned int dragStatus) { + _mouse_in_options = true; + + switch (dragStatus) { + case kDragStatus_NoDrag: + _backup_selected_object = _inventory->selectedObject(); + _engine->setCursor(181); + return 0; + case kDragStatus_Pressed: + case kDragStatus_Dragging: + // Nothing to do, we wait release + return 0; + case kDragStatus_Finished: + // Just clicked + _engine->displayOptions(); + _parentMustRedraw = true; + _shortExit = true; + _engine->setMousePos(Common::Point(320, 240)); // Center of screen + // Displaying options hides the mouse + g_system->showMouse(true); + return 0; + default: + return 0; + } +} + +unsigned int Toolbar::callbackDocumentation(unsigned int dragStatus) { + _mouse_in_options = true; + + switch (dragStatus) { + case kDragStatus_NoDrag: + case kDragStatus_Pressed: + case kDragStatus_Dragging: + // Nothing to do, we wait release + return 0; + case kDragStatus_Finished: + // Just clicked + if (_engine->displayPlaceDocumentation()) { + _parentMustRedraw = true; + _shortExit = true; + _engine->setMousePos(Common::Point(320, 240)); // Center of screen + } + return 0; + default: + return 0; + } +} + +void Toolbar::drawToolbar(const Graphics::Surface *original) { + if (_position > 60) { + _position = 60; + } + + if (_position != 0) { + // Not entirely drawn, we must copy a part of the original image + Common::Rect rct(0, 420, 640, 420 + _position); + _destSurface.copyRectToSurface(*original, 0, 0, rct); + } + + if (_position == 60) { + // Entirely hidden, just stop there, we have nothing to draw + return; + } + + // Not entirely hidden, we must display the transparent background prepared for us + Common::Rect rct(0, _position, 640, 60); + _destSurface.copyRectToSurface(_bgSurface, 0, _position, rct); + + // Now draw the various zones on the surface + for (Common::Array<Zone>::const_iterator it = _zones.begin(); it != _zones.end(); it++) { + if (it->hidden) { + continue; + } + + uint16 spriteId = it->secondary ? it->imageSecondary : it->imageMain; + if (spriteId == uint16(-1)) { + continue; + } + + Common::Rect dst = it->rect; + dst.translate(0, _position); + + // Clip the rectangle to fit inside the surface + dst.clip(Common::Rect(_destSurface.w, _destSurface.h)); + + if (dst.isEmpty()) { + continue; + } + + const Graphics::Surface &sprite = _sprites->getSurface(spriteId); + _destSurface.transBlitFrom(sprite, Common::Rect(dst.width(), dst.height()), dst, + _sprites->getKeyColor(spriteId)); + } + + // And now draw the object description if needed + if (_inventoryEnabled && _inventoryHovered != -1u) { + Object *obj = (*_inventory)[_inventoryHovered]; + + unsigned int zoneId = _inventoryHovered - _inventoryOffset; + if (zoneId >= 8) { + // The object is hidden: huh? + return; + } + + _fontManager->setSurface(&_destSurface); + _fontManager->setForeColor(243); + _fontManager->setCurrentFont(5); + _fontManager->setTransparentBackground(true); + const Common::String &objName = (*_messages)[obj->idOBJ()]; + unsigned int x = 195 - _fontManager->getStrWidth(objName); + unsigned int startX = _zones[zoneId].rect.left + kTextOffset; + _fontManager->displayStr(x, 38 + _position, objName); + _destSurface.hLine(x, 54 + _position, startX - 1, 243); // minus 1 because hLine draws inclusive + _destSurface.vLine(startX, 42 + _position, 54 + _position, 243); + } +} + +bool Toolbar::displayToolbar(const Graphics::Surface *original) { + /** + * In game there are 2 functions to handle toolbar: one in warp and one in fixed images + * This one is the warp one and fixed images have a more asynchronous one during pop-up/down phases + * Let's make it simple for now + */ + + // WORKAROUND: Set cursor here to be more consistent: it's thumb cursor just before showing until just after showed + _engine->setCursor(181); + + _parentMustRedraw = false; + _shortExit = false; + + // Prepare the background of the toolbar by making it translucent + // Get the lowest part of the image + const Graphics::Surface subset = original->getSubArea(Common::Rect(0, original->h - _bgSurface.h, + _bgSurface.w, original->h)); + _engine->makeTranslucent(_bgSurface, subset); + + // Draw the original surface on the surface to use for toolbar + g_system->copyRectToScreen(original->getPixels(), original->pitch, 0, 0, original->w, original->h); + + // WORKAROUND: Reset the inventory status at init to let sprites highlighted until toolbar is hidden + _inventorySelected = -1; + _inventoryHovered = -1; + _zones[12].secondary = true; + + updateZones(); + + for (_position = 60; _position > 0; _position--) { + // Make the toolbar go up + drawToolbar(original); + g_system->copyRectToScreen(_destSurface.getPixels(), _destSurface.pitch, 0, + original->h - _destSurface.h, _destSurface.w, _destSurface.h); + g_system->updateScreen(); + // pollEvents will slow down the animation because it waits 10ms + _engine->pollEvents(); + if (g_engine->shouldQuit()) { + return false; + } + } + + // Flush events + _engine->clearKeys(); + _engine->waitMouseRelease(); + + handleToolbarEvents(original); + if (g_engine->shouldQuit()) { + return false; + } + + if (_shortExit) { + return _parentMustRedraw; + } + + for (_position = 0; _position <= 60; _position++) { + // Make the toolbar go up + drawToolbar(original); + g_system->copyRectToScreen(_destSurface.getPixels(), _destSurface.pitch, 0, + original->h - _destSurface.h, _destSurface.w, _destSurface.h); + g_system->updateScreen(); + // pollEvents will slow down the animation because it waits 10ms + _engine->pollEvents(); + if (g_engine->shouldQuit()) { + return false; + } + } + + return _parentMustRedraw; +} + +void Toolbar::handleToolbarEvents(const Graphics::Surface *original) { + bool mouseInsideToolbar; + bool exitToolbar = false; + bool redrawToolbar; + + // Don't have anything hovered for now + _inventoryHovered = -1; + _inventorySelected = -1; + _inventory->setSelectedObject(nullptr); + _backup_selected_object = nullptr; + + // Refresh zones because we erased selected object + updateZones(); + + // No need of original surface because the toolbar is fully displayed + drawToolbar(original); + + // TODO: countdown + + g_system->copyRectToScreen(_destSurface.getPixels(), _destSurface.pitch, 0, + original->h - _destSurface.h, _destSurface.w, _destSurface.h); + g_system->updateScreen(); + + _engine->setCursor(181); + + mouseInsideToolbar = (_engine->getMousePos().y > 388); + + while (!exitToolbar) { + _mouse_in_options = false; + _mouse_in_view_object = false; + + _engine->pollEvents(); + if (g_engine->shouldQuit()) { + exitToolbar = true; + break; + } + + redrawToolbar = false; + if (_engine->checkKeysPressed(2, Common::KEYCODE_ESCAPE, Common::KEYCODE_SPACE) || + _engine->getCurrentMouseButton() == 2) { + _engine->waitMouseRelease(); + exitToolbar = true; + break; + } + + Common::Point mousePosInToolbar = _engine->getMousePos(); + mousePosInToolbar -= Common::Point(0, 420); + + if (captureEvent(mousePosInToolbar, _engine->getDragStatus())) { + // Something has changed with the zones handling, update zones + updateZones(); + redrawToolbar = true; + } else if (_engine->getDragStatus() == kDragStatus_Pressed) { + // A click happened and wasn't handled, deselect object + _inventorySelected = -1; + _inventory->setSelectedObject(nullptr); + _engine->setCursor(181); + // Reset view object + _zones[12].secondary = true; + updateZones(); + redrawToolbar = true; + } + + if (!mouseInsideToolbar) { + mouseInsideToolbar = (_engine->getMousePos().y > 388); + } else if (_engine->getMousePos().y <= 388) { + // mouseInsideToolbar is true and the mouse is outside the toolbar + exitToolbar = true; + break; + } + + if (_engine->getCurrentMouseButton() == 1) { + // When the mouse button is down, nothing is selected + // It's selected on release + _inventory->setSelectedObject(nullptr); + } + + if (_backup_selected_object != nullptr && !(_mouse_in_options || _mouse_in_view_object) && + !_engine->getCurrentMouseButton()) { + _inventory->setSelectedObject(_backup_selected_object); + _engine->setCursor(_backup_selected_object->idSl()); + _backup_selected_object = nullptr; + } + + // Hover the inventory objects + if (_inventory->selectedObject() == nullptr /* || _inventory_button_dragging */) { + // The 2nd above condition is maybe useless because when the mouse button is down the selected object is always null + bool shouldHover = false; + Common::Array<Zone>::const_iterator zoneIt = hitTestZones(mousePosInToolbar); + unsigned int zoneId = zoneIt - _zones.begin(); + unsigned int inventoryId = zoneId + _inventoryOffset; + if (zoneId < 8 && inventoryId < _inventory->size() && (*_inventory)[inventoryId] != nullptr) { + // It's the inventory + shouldHover = true; + if (_inventoryHovered != inventoryId && (*_inventory)[inventoryId]->valid()) { + // It's not the one currently hovered and it's a valid object + _inventoryHovered = inventoryId; + redrawToolbar = true; + } + } + if (!shouldHover && _inventoryHovered != -1u && !_mouse_in_view_object) { + // Remove hovering + _inventoryHovered = -1; + _inventorySelected = -1; + updateZones(); + if (!_inventory->selectedObject()) { + // Reset back the cursor if nothing is selected + _engine->setCursor(181); + } + // Remove view + _zones[12].secondary = true; + redrawToolbar = true; + } + _inventory_button_dragging = false; + } + + if (_parentMustRedraw) { + break; + } + + if (redrawToolbar) { + drawToolbar(original); + g_system->copyRectToScreen(_destSurface.getPixels(), _destSurface.pitch, 0, + original->h - _destSurface.h, _destSurface.w, _destSurface.h); + } + + g_system->updateScreen(); + } + + // Hide description when finished and selected object + // WORKAROUND: moved to the start to keep the selected object hilighted until the toolbar disappearance +} + +} // End of namespace Versailles +} // End of namespace CryOmni3D diff --git a/engines/cryomni3d/versailles/toolbar.h b/engines/cryomni3d/versailles/toolbar.h new file mode 100644 index 0000000000..11517cc10b --- /dev/null +++ b/engines/cryomni3d/versailles/toolbar.h @@ -0,0 +1,117 @@ +/* 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 CRYOMNI3D_VERSAILLES_TOOLBAR_H +#define CRYOMNI3D_VERSAILLES_TOOLBAR_H + +#include "common/array.h" +#include "common/rect.h" +#include "graphics/managed_surface.h" +#include "graphics/surface.h" + +#include "cryomni3d/font_manager.h" +#include "cryomni3d/objects.h" +#include "cryomni3d/sprites.h" + +namespace CryOmni3D { +class CryOmni3DEngine; + +namespace Versailles { + + +class Toolbar { +public: + Toolbar() : _sprites(nullptr), _fontManager(nullptr), _inventory(nullptr), + _messages(nullptr), _inventoryOffset(0), _engine(nullptr), + _inventoryHovered(-1), _inventorySelected(-1), _inventoryEnabled(true), + _position(60) { } + ~Toolbar(); + + void init(const Sprites *sprites, FontManager *fontManager, + const Common::Array<Common::String> *messages, Inventory *inventory, CryOmni3DEngine *engine); + + Graphics::Surface &getBackgroundSurface() { return _bgSurface; } + bool displayToolbar(const Graphics::Surface *original); + void inventoryChanged(unsigned int newPosition); + unsigned int inventoryOffset() const { return _inventoryOffset; } + void setInventoryOffset(unsigned int offset) { _inventoryOffset = offset; } + void setInventoryEnabled(bool enabled) { _inventoryEnabled = enabled; } + +private: + typedef unsigned int (Toolbar::*ZoneCallback)(unsigned int dragStatus); + struct Zone { + Common::Rect rect; + uint16 imageMain; + uint16 imageSecondary; + ZoneCallback callback; + bool secondary; + bool hidden; + }; + Common::Array<Zone> _zones; + const Sprites *_sprites; + FontManager *_fontManager; + const Common::Array<Common::String> *_messages; + Inventory *_inventory; + CryOmni3DEngine *_engine; + + static const unsigned int kTextOffset = 13; + + void addZone(uint16 cursorMainId, uint16 cursorSecondaryId, Common::Point position, + ZoneCallback callback); + void updateZones(); + Common::Array<Zone>::const_iterator hitTestZones(const Common::Point &mousePos) const; + unsigned int captureEvent(const Common::Point &mousePos, unsigned int dragStatus); + void drawToolbar(const Graphics::Surface *original); + void handleToolbarEvents(const Graphics::Surface *original); + + bool _inventoryEnabled; + unsigned int _inventoryMaxOffset; + unsigned int _inventoryOffset; + unsigned int _inventoryHovered; + unsigned int _inventorySelected; + + Object *_backup_selected_object; + bool _mouse_in_options; + bool _mouse_in_view_object; + bool _inventory_button_dragging; + + bool _parentMustRedraw; + bool _shortExit; + unsigned int _position; + + Graphics::Surface _bgSurface; + Graphics::ManagedSurface _destSurface; + + template<unsigned int N> + unsigned int callbackInventory(unsigned int dragStatus) { return callbackInventory(N, dragStatus); } + unsigned int callbackInventory(unsigned int invId, unsigned int dragStatus); + unsigned int callbackInventoryPrev(unsigned int dragStatus); + unsigned int callbackInventoryNext(unsigned int dragStatus); + unsigned int callbackViewObject(unsigned int dragStatus); + unsigned int callbackOptions(unsigned int dragStatus); + unsigned int callbackDocumentation(unsigned int dragStatus); +}; + +} // End of namespace Versailles +} // End of namespace CryOmni3D + +#endif |
