/* 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 "sherlock/journal.h" #include "sherlock/sherlock.h" namespace Sherlock { Journal::Journal(SherlockEngine *vm): _vm(vm) { // Allow up to 1000 statements _journal.resize(1000); // Initialize fields _count = 0; _maxPage = 0; _index = 0; _sub = 0; _up = _down = 0; _page = 0; _converseNum = -1; // Load the journal directory and location names loadJournalLocations(); } /** * Records statements that are said, in the order which they are said. The player * can then read the journal to review them */ void Journal::record(int converseNum, int statementNum) { int saveIndex = _index; int saveSub = _sub; // Record the entry into the list _journal.push_back(JournalEntry(converseNum, statementNum)); _index = _journal.size() - 1; // Load the text for the new entry to get the number of lines it will have int newLines = loadJournalFile(true); // Restore old state _index = saveIndex; _sub = saveSub; // If new lines were added to the ournal, update the total number of lines // the journal continues if (newLines) { _maxPage += newLines; } else { // No lines in entry, so remove the new entry from the journal _journal.remove_at(_journal.size() - 1); } } void Journal::loadJournalLocations() { Resources &res = *_vm->_res; char c; _directory.clear(); Common::SeekableReadStream *dir = res.load("talk.lib"); dir->skip(4); // Skip header // Get the numer of entries _directory.resize(dir->readUint16LE()); // Read in each entry for (uint idx = 0; idx < _directory.size(); ++idx) { Common::String line; while ((c = dir->readByte()) != 0) line += c; _directory.push_back(line); } delete dir; // Load in the locations stored in journal.txt Common::SeekableReadStream *loc = res.load("journal.txt"); _locations.clear(); while (loc->pos() < loc->size()) { Common::String line; while ((c = loc->readByte()) != '\0') line += c; _locations.push_back(line); } delete loc; } /** * Loads the description for the current display index in the journal, and then * word wraps the result to prepare it for being displayed */ bool Journal::loadJournalFile(bool alreadyLoaded) { Inventory &inv = *_vm->_inventory; People &people = *_vm->_people; Screen &screen = *_vm->_screen; Talk &talk = *_vm->_talk; JournalEntry &journalEntry = _journal[_index]; Statement &statement = talk[journalEntry._statementNum]; Common::String dirFilename = _directory[journalEntry._converseNum]; bool replyOnly = journalEntry._replyOnly; Common::String locStr(dirFilename.c_str(), dirFilename.c_str() + 4); int newLocation = atoi(locStr.c_str()); // If not flagged as alrady loaded, load the conversation into script variables if (!alreadyLoaded) { // See if the file to be used is already loaded if (journalEntry._converseNum != _converseNum) { // Nope. Free any previously loaded talk talk.freeTalkVars(); // Find the person being talked to talk._talkTo = -1; for (int idx = 0; idx < MAX_PEOPLE; ++idx) { Common::String portrait = people[idx]._portrait; Common::String numStr(portrait.c_str(), portrait.c_str() + 4); if (locStr == numStr) { talk._talkTo = idx; break; } } // Load the talk file talk.loadTalkFile(dirFilename); } } if (talk[0]._statement.hasPrefix("*") || talk[0]._statement.hasPrefix("^")) replyOnly = true; // If this isn't the first journal entry, see if the previous journal entry // was in the same scene to see if we need to include the scene header int oldLocation = -1; if (_index != 0) { // Get the scene number of the prior journal entry Common::String priorEntry = _directory[_journal[_index - 1]._converseNum]; oldLocation = atoi(Common::String(priorEntry.c_str() + 4, priorEntry.c_str() + 6).c_str()); } // Start building journal string Common::String journalString; if (newLocation != oldLocation) { // Add in scene title journalString = "@" + _locations[newLocation - 1] + ":"; // See if title can fit into a single line, or requires splitting on 2 lines int width = screen.stringWidth(journalString.c_str() + 1); if (width > 230) { // Scan backwards from end of title to find a space between a word // where the width is less than the maximum allowed for the line const char *lineP = journalString.c_str() + journalString.size() - 1; while (width > 230 || *lineP != ' ') width -= screen.charWidth(*lineP--); // Split the header into two lines, and add a '@' prefix // to the second line as well journalString = Common::String(journalString.c_str(), lineP) + "\n@" + Common::String(lineP + 1); } // Add a newline at the end of the title journalString += '\n'; } // If Holmes has something to say first, then take care of it if (!replyOnly) { // Handle the grammar journalString += "Holmes "; if (talk[journalEntry._statementNum]._statement.hasSuffix("?")) journalString += "asked "; else journalString += "said to "; switch (talk._talkTo) { case 1: journalString += "me"; break; case 2: journalString += "the Inspector"; break; default: journalString += inv._names[talk._talkTo]; break; } journalString += ", \""; // Add the statement journalString += statement._statement; } // Handle including the reply bool startOfReply = true; bool ctrlSpace = false; bool commentFlag = false; bool commentJustPrinted = false; const char *replyP = statement._reply.c_str(); while (*replyP) { char c = *replyP; // Is it a control character? if (c < 128) { // Nope. Set flag for allowing control coes to insert spaces ctrlSpace = true; // Check for embedded comments if (c == '{' || c == '}') { // Comment characters. If we're starting a comment and there's // already text displayed, add a closing quote if (c == '{' && !startOfReply && !commentJustPrinted) journalString += '"'; // If a reply isn't just being started, and we didn't just end // a comment (which would have added a line), add a carriage return if (!startOfReply && ((!commentJustPrinted && c == '{') || c == '}')) journalString += '"'; // Handle setting or clearing comment state if (c == '{') { commentFlag = true; commentJustPrinted = false; } else { commentFlag = false; commentJustPrinted = true; } } else { if (startOfReply) { if (!replyOnly) { journalString += "\"\n"; if (talk._talkTo == 1) journalString += "I replied, \""; else journalString += "The reply was, \""; } else { if (talk._talkTo == 1) journalString += "I"; else if (talk._talkTo == 2) journalString += "The Inspector"; else journalString += inv._names[talk._talkTo]; const char *strP = replyP + 1; char v; do { v = *strP++; } while (v && v < 128 && v != '.' && v != '!' && v != '?'); if (v == '?') journalString += " asked, \""; else journalString += " said, \""; } startOfReply = false; } // Copy text from the place until either the reply ends, a comment // {} block is started, or a control character is encountered do { journalString += *replyP++; } while (*replyP && *replyP < 128 && *replyP != '{' && *replyP != '}'); // Move pointer back, since the outer for loop will increment it again --replyP; commentJustPrinted = false; } } else if (c == 128) { if (!startOfReply) { if (!commentFlag && !commentJustPrinted) journalString += "\"\n"; journalString += "Then "; commentFlag = false; } else if (!replyOnly) { journalString += "\"\n"; } startOfReply = false; c = *++replyP; if ((c - 1) == 0) journalString += "Holmes"; else if ((c - 1) == 1) journalString += "I"; else if ((c - 1) == 2) journalString += "the Inspector"; else journalString += inv._names[c - 1]; const char *strP = replyP + 1; char v; do { v = *strP++; } while (v && v < 128 && v != '.' && v != '!' && v != '?'); if (v == '?') journalString += " asked, \""; else journalString += " said, \""; } else { // Control code, so move past it and any parameters ++replyP; switch (c) { case 129: // Run canim case 130: // Assign side case 131: // Pause with control case 136: // Pause without control case 157: // Walk to canimation // These commands don't have any param break; case 134: // Change sequence replyP += (replyP[0] & 127) + replyP[2] + 1; break; case 135: // Walk to co-ords case 154: // Move mouse replyP += 3; break; case 139: // Set flag case 143: // If statement ++replyP; break; case 140: // Play voice file case 150: // Play prologue case 153: // Call talk file replyP += 7; break; case 141: // Toggle object case 151: // Put item in inventory case 152: // Set object case 155: // Info line case 158: // Delete item from inventory replyP += *replyP & 127; break; case 149: // Goto scene replyP += 4; break; case 161: // End of line journalString += "\n"; break; default: break; } // Put a space in the output for a control character, unless it's // immediately coming after another control character if (ctrlSpace && c != 130 && c != 161 && !commentJustPrinted) { journalString += " "; ctrlSpace = false; } } } if (!startOfReply && !commentJustPrinted) journalString += '"'; // Finally finished building the journal text. Need to process the text to // word wrap it to fit on-screen. The resulting lines are stored in the // _lines array _lines.clear(); while (!journalString.empty()) { const char *startP = journalString.c_str(); // If the first character is a '@' flagging a title line, then move // past it, so the @ won't be included in the line width calculation if (*startP == '@') ++startP; // Build up chacters until a full line is found int width = 0; const char *endP = startP; while (width < 230 && *endP && *endP != '\n' && (endP - startP) < 79) width += screen.charWidth(*endP++); // If word wrapping, move back to end of prior word if (width >= 230 || (endP - startP) >= 79) { while (*--endP != ' ') ; } // Add in the line _lines.push_back(Common::String(startP, endP)); // Strip line off from string being processed journalString = *endP ? Common::String(endP + 1) : ""; } // Add a blank line at the end of the text as long as text was present if (!startOfReply) { _lines.push_back(""); } else { _lines.clear(); } return _lines.size(); } } // End of namespace Sherlock