/* 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); } uint 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 = { 0, 0, 0 }; char *currentPos = allDocs; char *lastRecordName = nullptr; 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() { _engine->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(); _engine->showMouse(true); } void Versailles_Documentation::handleDocInGame(const Common::String &record) { _visitTrace.clear(); _currentRecord = record; Graphics::ManagedSurface docSurface; Common::String nextRecord; MouseBoxes boxes(3); _engine->showMouse(false); bool end = false; while (!end) { inGamePrepareRecord(docSurface, boxes); uint 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); } } _engine->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; Image::ImageDecoder *imageDecoder = _engine->loadHLZ("SOM1.HLZ"); if (!imageDecoder) { return ""; } const Graphics::Surface *bgFrame = imageDecoder->getSurface(); for (uint 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(); } 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); _engine->showMouse(true); bool redraw = true; uint hoveredBox = -1; uint selectedBox = -1; while (selectedBox == -1u) { if (redraw) { // Draw without worrying of already modified areas, that's handled when recomputing hoveredBox for (uint i = 0; i < ARRAYSIZE(categories); i++) { uint 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) { uint x = categories[i].linesPos.right - _fontManager->getStrWidth(*categories[i].title); uint 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(); g_system->delayMillis(10); if (_engine->pollEvents()) { if (!_engine->getCurrentMouseButton()) { // Don't change highlighted icon when clicking Common::Point mouse = _engine->getMousePos(); bool foundBox = false; for (uint 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 (_engine->shouldAbort()) { selectedBox = 7; } } _engine->showMouse(false); for (uint i = 0; i < ARRAYSIZE(categories); i++) { categories[i].highlightedImg.free(); } 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 (uint 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 uint leaveBoxId = ARRAYSIZE(kTimelineEntries); boxes.setupBox(leaveBoxId, 639 - _sprites->getCursor(105).getWidth(), 479 - _sprites->getCursor(105).getHeight(), 640, 480); _engine->setCursor(181); _engine->showMouse(true); bool redraw = true; uint hoveredBox = -1; uint selectedBox = -1; while (selectedBox == -1u) { if (redraw) { // Draw without worrying of already modified areas, that's handled when recomputing hoveredBox for (uint 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(); g_system->delayMillis(10); if (_engine->pollEvents()) { Common::Point mouse = _engine->getMousePos(); if (!_engine->getCurrentMouseButton()) { // Don't change highlighted date when clicking bool foundBox = false; for (uint 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 (_engine->shouldAbort()) { selectedBox = leaveBoxId; } } _engine->showMouse(false); delete imageDecoder; if (selectedBox == leaveBoxId) { return ""; } else { Common::String ret = "VT"; ret += kTimelineEntries[selectedBox].year; return ret; } } uint Versailles_Documentation::docAreaHandleRecords(const Common::String &record) { uint 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 } else if (nextRecord != "planG") { _currentRecord = nextRecord; } 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]; uint 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 (uint box_id = 10; box_id < ARRAYSIZE(kTimelineEntries) + 10; box_id++) { boxes.display(box_id, *_fontManager); } } drawRecordBoxes(surface, true, boxes); } uint Versailles_Documentation::docAreaHandleRecord(Graphics::ManagedSurface &surface, MouseBoxes &boxes, Common::String &nextRecord) { // Hovering is only handled for timeline entries _engine->setCursor(181); _engine->showMouse(true); bool first = true; bool redraw = true; uint hoveredBox = -1; uint 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(); g_system->delayMillis(10); if (_engine->pollEvents() || first) { first = false; if (_engine->shouldAbort()) { // Fake the quit action = 6; } Common::Point mouse = _engine->getMousePos(); if (_currentInTimeline) { bool foundBox = false; for (uint 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::const_iterator it = _currentLinks.begin(); it != _currentLinks.end(); it++) { items.push_back(it->title); } Common::Rect iconRect = boxes.getBoxRect(2); uint 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::const_iterator it = _allLinks.begin(); it != _allLinks.end(); it++) { items.push_back(it->title); } Common::Rect iconRect = boxes.getBoxRect(3); uint 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]); uint 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]); uint 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::iterator hmIt = _records.find(_currentRecord); if (hmIt == _records.end()) { // Shouldn't happen action = -1; continue; } uint 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; } } } _engine->showMouse(false); _engine->setCursor(181); return action; } Common::String Versailles_Documentation::docAreaHandleGeneralMap() { struct Area { Common::Rect areaPos; const char *record; const char *bmp; uint messageId; const Common::String *message; Common::Point messagePos; Graphics::Surface highlightedImg; Area(const Common::Point &areaPos_, const char *bmp_, uint 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_, uint 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; Image::ImageDecoder *imageDecoder = _engine->loadHLZ("PLANGR.HLZ"); if (!imageDecoder) { return ""; } const Graphics::Surface *bgFrame = imageDecoder->getSurface(); for (uint 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]; uint 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); 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); _engine->showMouse(true); bool redraw = true; uint hoveredBox = -1; uint 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 { uint middleX = (areas[hoveredBox].areaPos.left + areas[hoveredBox].areaPos.right) / 2; uint middleY = (areas[hoveredBox].areaPos.top + areas[hoveredBox].areaPos.bottom) / 2; uint spriteX = middleX - _sprites->getCursor(163).getWidth() / 2; uint 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(uint 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(); g_system->delayMillis(10); if (_engine->pollEvents()) { Common::Point mouse = _engine->getMousePos(); if (!_engine->getCurrentMouseButton()) { // Don't change highlighted icon when clicking bool foundBox = false; uint oldHoveredBox = hoveredBox; for (uint 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 (_engine->shouldAbort()) { selectedBox = ARRAYSIZE(areas); } } } _engine->showMouse(false); for (uint i = 0; i < ARRAYSIZE(areas); i++) { areas[i].highlightedImg.free(); } 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; uint messageId; Common::String message; Common::Point messagePos; Common::Rect areaPos1; Common::Rect areaPos2; Area(const Common::Rect &areaPos_, const char *record_, bool fillArea_ = true, uint 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, uint 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 (uint 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); } uint lineWidth = _fontManager->getStrWidth(areas[i].message); uint 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); _engine->showMouse(true); bool redraw = true; uint hoveredBox = -1; uint 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 { uint middleX = (areas[hoveredBox].areaPos.left + areas[hoveredBox].areaPos.right) / 2; uint middleY = (areas[hoveredBox].areaPos.top + areas[hoveredBox].areaPos.bottom) / 2; uint spriteX = middleX - _sprites->getCursor(163).getWidth() / 2; uint 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(uint 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(); g_system->delayMillis(10); if (_engine->pollEvents()) { Common::Point mouse = _engine->getMousePos(); if (!_engine->getCurrentMouseButton()) { // Don't change highlighted icon when clicking bool foundBox = false; uint oldHoveredBox = hoveredBox; for (uint 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 (_engine->shouldAbort()) { selectedBox = ARRAYSIZE(areas); } } } _engine->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")) { uint 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); } uint Versailles_Documentation::inGameHandleRecord(Graphics::ManagedSurface &surface, MouseBoxes &boxes, Common::String &nextRecord) { _engine->setCursor(181); _engine->showMouse(true); uint action = -1; g_system->copyRectToScreen(surface.getPixels(), surface.pitch, 0, 0, surface.w, surface.h); while (action == -1u) { g_system->updateScreen(); g_system->delayMillis(10); if (_engine->pollEvents()) { if (_engine->shouldAbort()) { // Fake the quit action = 1; } Common::Point mouse = _engine->getMousePos(); if (_engine->getDragStatus() == kDragStatus_Pressed) { if (boxes.hitTest(2, mouse) && _currentLinks.size()) { Common::StringArray items; for (Common::Array::const_iterator it = _currentLinks.begin(); it != _currentLinks.end(); it++) { items.push_back(it->title); } Common::Rect iconRect = boxes.getBoxRect(2); uint 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; } } } } _engine->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 = _engine->prepareFileName(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);*/ uint 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); delete imageDecoder; } 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 uint allRecordsX = 640 - _sprites->getCursor(19).getWidth(); uint hyperlinksX = allRecordsX - _sprites->getCursor(242).getWidth() - 10; uint 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) { uint backX = _sprites->getCursor(225).getWidth() + 10; //Right to quit button _fontManager->setCurrentFont(0); _fontManager->setTransparentBackground(true); _fontManager->setSpaceWidth(0); _fontManager->setCharSpacing(1); uint 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 (uint 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 { uint 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)); } } uint Versailles_Documentation::handlePopupMenu(const Graphics::ManagedSurface &originalSurface, const Common::Point &anchor, bool rightAligned, uint itemHeight, const Common::StringArray &items) { uint maxTextWidth = 0; _fontManager->setCurrentFont(4); _fontManager->setTransparentBackground(true); _fontManager->setCharSpacing(1); for (Common::StringArray::const_iterator it = items.begin(); it != items.end(); it++) { uint width = _fontManager->getStrWidth(*it); if (width > maxTextWidth) { maxTextWidth = width; } } uint width = maxTextWidth + 2 * kPopupMenuMargin; uint height = itemHeight * items.size() + 2 * kPopupMenuMargin; uint 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 (uint 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; uint hoveredBox = -1; uint action = -1; uint lastShownItem = items.size() - 1; uint firstShownItem = lastShownItem - shownItems + 1; uint slowScrollNextEvent = g_system->getMillis() + 250; Common::Point mouse; while (action == -1u) { if (redraw) { if (fullRedraw) { surface.fillRect(popupRect, 247); fullRedraw = false; } for (uint 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(); g_system->delayMillis(10); if (_engine->pollEvents()) { if (_engine->shouldAbort()) { // Fake the quit break; } mouse = _engine->getMousePos(); uint newHovered = -1; for (uint 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; uint patternLen = 0; 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; } uint 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 (uint 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::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::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 &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 &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