scummvm/engines/cryomni3d/versailles/documentation.cpp
Le Philousophe d9456b459f CRYOMNI3D: Add members initializer
This will fix static analyzer errors
2022-09-18 13:19:59 +02:00

2128 lines
67 KiB
C++

/* 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 3 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, see <http://www.gnu.org/licenses/>.
*
*/
#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 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,
const Common::String &allDocsFileName, const Common::String &linksDocsFileName) {
_sprites = sprites;
_fontManager = fontManager;
_messages = messages;
_engine = engine;
_allDocsFileName = allDocsFileName;
_linksDocsFileName = linksDocsFileName;
// Japanese version of Versailles handles records attributes with multilines
_multilineAttributes = (_engine->getLanguage() == Common::JA_JPN);
// Build list of records
Common::File allDocsFile;
if (!allDocsFile.open(_allDocsFileName)) {
error("Can't open %s", _allDocsFileName.c_str());
}
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 = { uint(-1), uint(-1), uint(-1) };
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 = uint(-1);
uint selectedBox = uint(-1);
while (selectedBox == uint(-1)) {
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 != uint(-1)) {
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 = uint(-1);
redraw = true;
}
}
if (_engine->getDragStatus() == kDragStatus_Finished) {
if (hoveredBox != uint(-1)) {
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 = uint(-1);
uint selectedBox = uint(-1);
while (selectedBox == uint(-1)) {
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 != uint(-1)) {
hoveredBox = uint(-1);
redraw = true;
}
}
if (_engine->getDragStatus() == kDragStatus_Finished) {
if (hoveredBox != uint(-1)) {
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 = uint(-1);
_currentRecord = record;
_visitTrace.clear();
Graphics::ManagedSurface docSurface;
Common::String nextRecord;
MouseBoxes boxes(10 + ARRAYSIZE(kTimelineEntries));
while (true) {
if (action == uint(-1)) {
_currentRecord.toUppercase();
//debug("Displaying %s", _currentRecord.c_str());
docAreaPrepareNavigation();
docAreaPrepareRecord(docSurface, boxes);
action = docAreaHandleRecord(docSurface, boxes, nextRecord);
}
switch (action) {
case 0:
action = uint(-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 = uint(-1);
// Follow hyperlink keeping trace
_visitTrace.push_back(_currentRecord);
_currentRecord = nextRecord;
break;
case 3:
action = uint(-1);
// Follow hyperlink losing trace
_visitTrace.clear();
_currentRecord = nextRecord;
break;
case 6:
// Quit
return 2;
case 7:
action = uint(-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 = uint(-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 = uint(-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 = uint(-1);
uint action = uint(-1);
while (action == uint(-1)) {
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 != uint(-1)) {
// 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 != uint(-1)) {
// Restore the previous entry hovered
_fontManager->setForeColor(243);
boxes.display(hoveredBox, *_fontManager);
hoveredBox = uint(-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 = uint(-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);
uint selectedItem = handlePopupMenu(surface, Common::Point(iconRect.right, iconRect.top),
true, 20, items);
if (selectedItem != uint(-1)) {
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);
uint selectedItem = handlePopupMenu(surface, Common::Point(iconRect.right, iconRect.top),
true, 20, items);
if (selectedItem != uint(-1)) {
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 != uint(-1)) {
// 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 = uint(-1);
continue;
}
if (action == 5 && _currentRecord == _categoryStartRecord) {
action = uint(-1);
continue;
}
Common::HashMap<Common::String, RecordInfo>::iterator hmIt = _records.find(_currentRecord);
if (hmIt == _records.end()) {
// Shouldn't happen
action = uint(-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 = uint(-1);
uint selectedBox = uint(-1);
while (selectedBox == uint(-1)) {
if (redraw) {
// Draw without worrying of already modified areas, that's handled when recomputing hoveredBox
if (hoveredBox != uint(-1)) {
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 != uint(-1)) {
hoveredBox = uint(-1);
redraw = true;
}
if (hoveredBox != oldHoveredBox && oldHoveredBox != uint(-1)) {
// 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 != uint(-1) && 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_ = uint(-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_ = uint(-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 != uint(-1)) {
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 = uint(-1);
uint selectedBox = uint(-1);
while (selectedBox == uint(-1)) {
if (redraw) {
// Draw without worrying of already modified areas, that's handled when recomputing hoveredBox
if (hoveredBox != uint(-1)) {
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 != uint(-1)) {
hoveredBox = uint(-1);
redraw = true;
}
if (hoveredBox != oldHoveredBox && oldHoveredBox != uint(-1)) {
// 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 != uint(-1) && 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 = uint(-1);
g_system->copyRectToScreen(surface.getPixels(), surface.pitch, 0, 0, surface.w, surface.h);
while (action == uint(-1)) {
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<LinkInfo>::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 != uint(-1)) {
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) {
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);
}
// Fix of overlapping areas for Chinese and Japanese (as in original binary)
if ((_engine->getLanguage() == Common::JA_JPN ||
_engine->getLanguage() == Common::ZH_TWN) &&
!_currentMapLayout) {
blockContent1.bottom += 30;
blockContent2.top += 30;
}
if (_currentInTimeline) {
background = "CHRONO1";
foreColor = 241;
} else {
background = _currentRecord;
}
background = _engine->prepareFileName(background, "hlz");
Common::File backgroundFl;
if (!backgroundFl.open(background)) {
background = _currentMapLayout ? "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;
if (_engine->getLanguage() == Common::JA_JPN ||
_engine->getLanguage() == Common::KO_KOR ||
_engine->getLanguage() == Common::ZH_TWN) {
_fontManager->setCurrentFont(8);
} else {
_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->displayBlockTextContinue()) {
// 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 = uint(-1);
uint action = uint(-1);
uint lastShownItem = items.size() - 1;
uint firstShownItem = lastShownItem - shownItems + 1;
uint slowScrollNextEvent = g_system->getMillis() + 250;
Common::Point mouse;
while (action == uint(-1)) {
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 = uint(-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 == uint(-1)) {
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 = uint(-1);
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;
if (_multilineAttributes) {
char *eoa = foundPos;
// Find next '='
for (; eoa < end && *eoa != '\0' && *eoa != '='; eoa++) {}
if (eoa == end || *eoa == '\0') {
// This is the end of block or data has already been split
return foundPos;
}
// Go back to start of line
for (; eoa != foundPos && *eoa != '\r'; eoa--) {}
*eoa = '\0';
} else {
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<Common::String, RecordInfo>::iterator it = _records.find(record);
if (it == _records.end()) {
return "";
}
const RecordInfo &recordInfo = it->_value;
Common::File allDocsFile;
if (!allDocsFile.open(_allDocsFileName)) {
error("Can't open %s", _allDocsFileName.c_str());
}
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(_allDocsFileName)) {
error("Can't open %s", _allDocsFileName.c_str());
}
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);
const char *textP = nullptr;
if (_multilineAttributes) {
const char *patterns[] = { "TEXTE=", "TEXT=", nullptr };
textP = getDocPartAddress(recordData, recordDataEnd, patterns);
} else {
textP = getDocTextAddress(recordData, recordDataEnd);
}
assert(textP != nullptr);
Common::String text(textP);
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(_linksDocsFileName)) {
error("Can't open links file: %s", _linksDocsFileName.c_str());
}
_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