aboutsummaryrefslogtreecommitdiff
path: root/engines/startrek/textbox.cpp
diff options
context:
space:
mode:
authorMatthew Stewart2018-07-27 20:34:31 -0400
committerEugene Sandulenko2018-08-09 08:37:30 +0200
commitabf711a4d33b01cc5ef7726cdade8a39a8a5a325 (patch)
treef18ede1da194a862d7bcd5f30e6eb51988b7f2dc /engines/startrek/textbox.cpp
parent8970909318c158b18eb5612f26db1d4db941160f (diff)
downloadscummvm-rg350-abf711a4d33b01cc5ef7726cdade8a39a8a5a325.tar.gz
scummvm-rg350-abf711a4d33b01cc5ef7726cdade8a39a8a5a325.tar.bz2
scummvm-rg350-abf711a4d33b01cc5ef7726cdade8a39a8a5a325.zip
STARTREK: text.cpp -> textbox.cpp
Diffstat (limited to 'engines/startrek/textbox.cpp')
-rw-r--r--engines/startrek/textbox.cpp933
1 files changed, 933 insertions, 0 deletions
diff --git a/engines/startrek/textbox.cpp b/engines/startrek/textbox.cpp
new file mode 100644
index 0000000000..1a05ecc1e8
--- /dev/null
+++ b/engines/startrek/textbox.cpp
@@ -0,0 +1,933 @@
+/* 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/events.h"
+#include "common/stream.h"
+#include "graphics/cursorman.h"
+
+#include "startrek/graphics.h"
+#include "startrek/room.h"
+
+
+namespace StarTrek {
+
+const char *StarTrekEngine::getNextTextLine(const char *text, char *lineOutput, int lineWidth) {
+ *lineOutput = '\0';
+ if (*text == '\0')
+ return nullptr;
+
+ const char *lastSpaceInput = nullptr;
+ char *lastSpaceOutput = nullptr;
+ int charIndex = 0;
+
+ while (charIndex != lineWidth && *text != '\0') {
+ char c = *text;
+
+ if (c == '\n') {
+ *lineOutput = '\0';
+ return text + 1;
+ }
+
+ if (c == ' ') {
+ lastSpaceInput = text;
+ lastSpaceOutput = lineOutput;
+ }
+
+ if (c == '\r') {
+ text++;
+ charIndex--;
+ } else {
+ text++;
+ *(lineOutput++) = c;
+ }
+ charIndex++;
+ }
+
+ if (*text == '\0') {
+ *lineOutput = '\0';
+ return text;
+ }
+ if (*text == ' ') {
+ *lineOutput = '\0';
+ return text + 1;
+ }
+ if (lastSpaceOutput == nullptr) { // Long word couldn't fit on line
+ *lineOutput = '\0';
+ return text;
+ }
+
+ // In the middle of a word; must go back to the start of it
+ *lastSpaceOutput = '\0';
+ return lastSpaceInput + 1;
+}
+
+void StarTrekEngine::drawTextLineToBitmap(const char *text, int textLen, int x, int y, SharedPtr<Bitmap> bitmap) {
+ const int charWidth = 8;
+
+ int textOffset = 0;
+
+ while (textOffset < textLen) {
+ Common::Rect destRect(x, y, x + 8, y + 8);
+ Common::Rect bitmapRect(bitmap->width, bitmap->height);
+
+ if (destRect.intersects(bitmapRect)) {
+ // drawRect = the rectangle within the 8x8 font character that will be drawn
+ // (part of it may be clipped)
+ Common::Rect drawRect;
+ drawRect.left = bitmapRect.left - destRect.left;
+ if (drawRect.left < destRect.left - destRect.left)
+ drawRect.left = destRect.left - destRect.left;
+
+ drawRect.right = bitmapRect.right - destRect.left;
+ if (drawRect.right > destRect.right - destRect.left)
+ drawRect.right = destRect.right - destRect.left;
+
+ drawRect.top = bitmapRect.top - destRect.top;
+ if (drawRect.top < destRect.top - destRect.top)
+ drawRect.top = destRect.top - destRect.top;
+
+ drawRect.bottom = bitmapRect.bottom - destRect.top;
+ if (drawRect.bottom > destRect.bottom - destRect.top)
+ drawRect.bottom = destRect.bottom - destRect.top;
+
+
+ int16 destX = destRect.left - bitmapRect.left;
+ if (destX < bitmapRect.right - bitmapRect.right)
+ destX = bitmapRect.right - bitmapRect.right;
+
+ int16 destY = destRect.top - bitmapRect.top;
+ if (destY < bitmapRect.top - bitmapRect.top)
+ destY = bitmapRect.top - bitmapRect.top;
+
+ int16 srcRowDiff = charWidth - drawRect.width();
+ int16 destRowDiff = bitmapRect.width() - drawRect.width();
+
+ byte *srcPixels = _gfx->getFontGfx(text[textOffset]) + drawRect.top * charWidth + drawRect.left;
+ byte *destPixels = bitmap->pixels + destY * bitmapRect.width() + destX;
+
+ for (int i = 0; i < drawRect.height(); i++) {
+ memcpy(destPixels, srcPixels, drawRect.width());
+ destPixels += destRowDiff + drawRect.width();
+ srcPixels += srcRowDiff + drawRect.width();
+ }
+ }
+
+ x += charWidth;
+ textOffset++;
+ }
+}
+
+String StarTrekEngine::centerTextboxHeader(String headerText) {
+ char text[TEXT_CHARS_PER_LINE + 1];
+ memset(text, ' ', sizeof(text));
+ text[TEXT_CHARS_PER_LINE] = '\0';
+
+ int strlen = headerText.size();
+ strlen = min(strlen, TEXT_CHARS_PER_LINE);
+
+ memcpy(text + (TEXT_CHARS_PER_LINE - strlen) / 2, headerText.c_str(), strlen);
+
+ return Common::String(text);
+}
+
+void StarTrekEngine::getTextboxHeader(String *headerTextOutput, String speakerText, int choiceIndex) {
+ String header = speakerText;
+
+ if (choiceIndex != 0)
+ header += String::format(" choice %d", choiceIndex);
+
+ *headerTextOutput = centerTextboxHeader(header);
+}
+
+String StarTrekEngine::readTextFromRdf(int choiceIndex, uintptr data, String *headerTextOutput) {
+ SharedPtr<Room> room = getRoom();
+
+ int rdfVar = (size_t)data;
+
+ uint16 textOffset = room->readRdfWord(rdfVar + (choiceIndex + 1) * 2);
+
+ if (textOffset == 0)
+ return "";
+
+ if (headerTextOutput != nullptr) {
+ uint16 speakerOffset = room->readRdfWord(rdfVar);
+ if (speakerOffset == 0 || room->_rdfData[speakerOffset] == '\0')
+ *headerTextOutput = "";
+ else {
+ char *speakerText = (char *)&room->_rdfData[speakerOffset];
+ if (room->readRdfWord(rdfVar + 4) != 0) // Check if there's more than one option
+ getTextboxHeader(headerTextOutput, speakerText, choiceIndex + 1);
+ else
+ getTextboxHeader(headerTextOutput, speakerText, 0);
+ }
+ }
+
+ return (char *)&room->_rdfData[textOffset];
+}
+
+void StarTrekEngine::showTextbox(String headerText, const String &mainText, int xoffset, int yoffset, byte textColor, int maxTextLines) {
+ if (!headerText.empty())
+ headerText = centerTextboxHeader(headerText);
+
+ int actionParam = (maxTextLines < 0 ? 0 : maxTextLines);
+
+ if (maxTextLines < 0)
+ maxTextLines = -maxTextLines;
+
+ const char *strings[3];
+
+ if (headerText.empty())
+ strings[0] = nullptr;
+ else
+ strings[0] = headerText.c_str();
+ strings[1] = mainText.c_str();
+ strings[2] = "";
+
+ showText(&StarTrekEngine::readTextFromArray, (uintptr)strings, xoffset, yoffset, textColor, false, maxTextLines, false);
+
+ if (actionParam != 0)
+ addAction(ACTION_TALK, actionParam, 0, 0);
+}
+
+String StarTrekEngine::skipTextAudioPrompt(const String &str) {
+ const char *text = str.c_str();
+
+ if (*text != '#')
+ return str;
+
+ text++;
+ while (*text != '#') {
+ if (*text == '\0')
+ return str;
+ text++;
+ }
+
+ return String(text + 1);
+}
+
+String StarTrekEngine::playTextAudio(const String &str) {
+ const char *text = str.c_str();
+ char soundFile[0x100];
+
+ if (*text != '#')
+ return str;
+
+ int len = 0;
+ text++;
+ while (*text != '#') {
+ if (*text == '\0' || len > 0xfa)
+ return str;
+ soundFile[len++] = *text++;
+ }
+
+ soundFile[len] = '\0';
+ playSpeech(soundFile);
+
+ return String(text + 1);
+}
+
+int StarTrekEngine::showText(TextGetterFunc textGetter, uintptr var, int xoffset, int yoffset, int textColor, bool loopChoices, int maxTextLines, bool rclickCancelsChoice) {
+ int16 tmpTextDisplayMode = _textDisplayMode;
+
+ uint32 ticksUntilClickingEnabled = 8;
+ if (_frameIndex > _textboxVar2 + 1) {
+ ticksUntilClickingEnabled = 0x10;
+ }
+
+ int numChoicesWithNames = 0;
+ int numTextboxLines = 0;
+ int numChoices = 0;
+ String speakerText;
+
+ while (true) {
+ String choiceText = (this->*textGetter)(numChoices, var, &speakerText);
+ if (choiceText.empty())
+ break;
+
+ int lines = getNumTextboxLines(skipTextAudioPrompt(choiceText));
+ if (lines > numTextboxLines)
+ numTextboxLines = lines;
+
+ if (!speakerText.empty()) // FIXME: Technically should check for nullptr
+ numChoicesWithNames++;
+
+ numChoices++;
+ }
+
+ if (maxTextLines == 0 || maxTextLines > MAX_TEXTBOX_LINES)
+ maxTextLines = MAX_TEXTBOX_LINES;
+ if (numTextboxLines > maxTextLines)
+ numTextboxLines = maxTextLines;
+
+ if (numChoicesWithNames != 0 && numChoices != numChoicesWithNames)
+ error("showText: Not all choices have titles.");
+
+ Sprite textboxSprite;
+ SharedPtr<TextBitmap> textBitmap = initTextSprite(&xoffset, &yoffset, textColor, numTextboxLines, numChoicesWithNames, &textboxSprite);
+
+ int choiceIndex = 0;
+ int scrollOffset = 0;
+ if (tmpTextDisplayMode != TEXTDISPLAY_WAIT && tmpTextDisplayMode != TEXTDISPLAY_SUBTITLES
+ && numChoices == 1 && _sfxEnabled && !_sfxWorking)
+ _textboxHasMultipleChoices = false;
+ else
+ _textboxHasMultipleChoices = true;
+
+ if (tmpTextDisplayMode >= TEXTDISPLAY_WAIT && tmpTextDisplayMode <= TEXTDISPLAY_NONE
+ && _sfxEnabled && !_sfxWorking)
+ _textboxVar6 = true;
+ else
+ _textboxVar6 = false;
+
+ int numTextLines;
+ String lineFormattedText = readLineFormattedText(textGetter, var, choiceIndex, textBitmap, numTextboxLines, &numTextLines);
+
+ if (lineFormattedText.empty()) { // Technically should check for nullptr
+ _gfx->delSprite(&textboxSprite);
+
+ // TODO
+ } else {
+ loadMenuButtons("textbtns", xoffset + 0x96, yoffset - 0x11);
+
+ Common::Point oldMousePos = _gfx->getMousePos();
+ SharedPtr<Bitmap> oldMouseBitmap = _gfx->getMouseBitmap();
+
+ _gfx->warpMouse(xoffset + 0xde, yoffset - 0x08);
+ _gfx->setMouseBitmap(_gfx->loadBitmap("pushbtn"));
+
+ bool tmpMouseControllingShip = _mouseControllingShip;
+ _mouseControllingShip = false;
+
+ // Decide which buttons to show
+ uint32 visibleButtons = (1 << TEXTBUTTON_CONFIRM);
+ if (numChoices > 1)
+ visibleButtons |= (1 << TEXTBUTTON_PREVCHOICE) | (1 << TEXTBUTTON_NEXTCHOICE);
+ if (numTextLines > numTextboxLines)
+ visibleButtons |= (1 << TEXTBUTTON_SCROLLUP) | (1 << TEXTBUTTON_SCROLLDOWN);
+ setVisibleMenuButtons(visibleButtons);
+
+ disableMenuButtons(1 << TEXTBUTTON_SCROLLUP); // Disable scroll up
+
+ if (ticksUntilClickingEnabled != 0) // Disable done button
+ disableMenuButtons(1 << TEXTBUTTON_CONFIRM);
+
+ if (!loopChoices) // Disable prev button
+ disableMenuButtons(1 << TEXTBUTTON_PREVCHOICE);
+
+ bool doneShowingText = false;
+
+ // Loop until text is done being displayed
+ while (!doneShowingText) {
+ int textboxReturnCode = handleMenuEvents(ticksUntilClickingEnabled, true);
+
+ if (ticksUntilClickingEnabled != 0)
+ enableMenuButtons(1 << TEXTBUTTON_CONFIRM);
+
+ switch (textboxReturnCode) {
+
+ case MENUEVENT_RCLICK_OFFBUTTON:
+ case MENUEVENT_RCLICK_ONBUTTON:
+ if (ticksUntilClickingEnabled == 0) {
+ doneShowingText = true;
+ if (rclickCancelsChoice)
+ choiceIndex = -1;
+ }
+ break;
+
+ case TEXTBUTTON_CONFIRM:
+ doneShowingText = true;
+ break;
+
+ case TEXTBUTTON_SCROLLUP:
+ scrollOffset -= numTextboxLines;
+ goto readjustScrollUp;
+
+ case TEXTBUTTON_SCROLLDOWN:
+ scrollOffset += numTextboxLines;
+ goto readjustScrollDown;
+
+ case TEXTBUTTON_SCROLLUP_ONELINE:
+ scrollOffset--;
+ goto readjustScrollUp;
+
+ case TEXTBUTTON_SCROLLDOWN_ONELINE:
+ scrollOffset++;
+ goto readjustScrollDown;
+
+ case TEXTBUTTON_GOTO_TOP:
+ scrollOffset = 0;
+ goto readjustScrollUp;
+
+ case TEXTBUTTON_GOTO_BOTTOM:
+ scrollOffset = numTextLines - numTextboxLines;
+ goto readjustScrollDown;
+
+readjustScrollUp:
+ enableMenuButtons(1 << TEXTBUTTON_SCROLLDOWN);
+ if (scrollOffset < 0)
+ scrollOffset = 0;
+ if (scrollOffset == 0)
+ disableMenuButtons(1 << TEXTBUTTON_SCROLLUP);
+ goto readjustScroll;
+
+readjustScrollDown:
+ enableMenuButtons(1 << TEXTBUTTON_SCROLLUP);
+ if (scrollOffset >= numTextLines)
+ scrollOffset -= numTextboxLines;
+ if (scrollOffset > numTextLines - 1)
+ scrollOffset = numTextLines - 1;
+ if (scrollOffset + numTextboxLines >= numTextLines)
+ disableMenuButtons(1 << TEXTBUTTON_SCROLLDOWN);
+ goto readjustScroll;
+
+readjustScroll:
+ textboxSprite.bitmapChanged = true;
+ drawMainText(
+ textBitmap,
+ numTextLines - scrollOffset,
+ numTextboxLines,
+ lineFormattedText.c_str() + scrollOffset * (TEXTBOX_WIDTH - 2),
+ numChoicesWithNames != 0);
+ break;
+
+ case TEXTBUTTON_PREVCHOICE:
+ choiceIndex--;
+ if (!loopChoices && choiceIndex == 0) {
+ disableMenuButtons(1 << TEXTBUTTON_PREVCHOICE);
+ } else {
+ if (choiceIndex < 0)
+ choiceIndex = numChoices - 1;
+ }
+ enableMenuButtons(1 << TEXTBUTTON_NEXTCHOICE);
+ goto reloadText;
+
+ case TEXTBUTTON_NEXTCHOICE:
+ enableMenuButtons(1 << TEXTBUTTON_PREVCHOICE);
+ choiceIndex++;
+ if (!loopChoices && choiceIndex == numChoices - 1) {
+ disableMenuButtons(1 << TEXTBUTTON_NEXTCHOICE);
+ } else {
+ choiceIndex %= numChoices;
+ }
+ goto reloadText;
+
+reloadText:
+ scrollOffset = 0;
+ lineFormattedText = readLineFormattedText(textGetter, var, choiceIndex, textBitmap, numTextboxLines, &numTextLines);
+ if (numTextLines <= numTextboxLines) {
+ setVisibleMenuButtons((1 << TEXTBUTTON_CONFIRM) | (1 << TEXTBUTTON_PREVCHOICE) | (1 << TEXTBUTTON_NEXTCHOICE));
+ } else {
+ setVisibleMenuButtons((1 << TEXTBUTTON_CONFIRM) | (1 << TEXTBUTTON_SCROLLUP) | (1 << TEXTBUTTON_SCROLLDOWN) | (1 << TEXTBUTTON_PREVCHOICE) | (1 << TEXTBUTTON_NEXTCHOICE));
+ }
+ enableMenuButtons(1 << TEXTBUTTON_SCROLLDOWN);
+ disableMenuButtons(1 << TEXTBUTTON_SCROLLUP);
+ textboxSprite.bitmapChanged = true;
+ break;
+
+ case TEXTBUTTON_SPEECH_DONE:
+ if (numChoices == 1)
+ doneShowingText = true;
+ break;
+
+ case MENUEVENT_ENABLEINPUT:
+ case MENUEVENT_LCLICK_OFFBUTTON:
+ default:
+ break;
+ }
+
+ ticksUntilClickingEnabled = 0;
+ }
+
+ _gfx->setMouseBitmap(oldMouseBitmap);
+ _gfx->warpMouse(oldMousePos.x, oldMousePos.y);
+
+ _mouseControllingShip = tmpMouseControllingShip;
+ unloadMenuButtons();
+
+ textboxSprite.dontDrawNextFrame();
+ _gfx->drawAllSprites();
+ _gfx->delSprite(&textboxSprite);
+ }
+
+ _textboxVar2 = _frameIndex;
+ stopPlayingSpeech();
+ return choiceIndex;
+}
+
+int StarTrekEngine::getNumTextboxLines(const String &str) {
+ const char *text = str.c_str();
+ char line[TEXTBOX_WIDTH];
+
+ int lines = 0;
+
+ while (text != nullptr) {
+ text = getNextTextLine(text, line, TEXTBOX_WIDTH - 2);
+ lines++;
+ }
+ return lines - 1;
+}
+
+String StarTrekEngine::putTextIntoLines(const String &_text) {
+ char line[TEXTBOX_WIDTH];
+
+ const char *text = _text.c_str();
+ String output;
+
+ text = getNextTextLine(text, line, TEXTBOX_WIDTH - 2);
+
+ while (text != nullptr) {
+ int len = strlen(line);
+ while (len != TEXTBOX_WIDTH - 2) {
+ line[len++] = ' ';
+ line[len] = '\0';
+ }
+ output += line;
+
+ text = getNextTextLine(text, line, TEXTBOX_WIDTH - 2);
+ }
+
+ return output;
+}
+
+SharedPtr<TextBitmap> StarTrekEngine::initTextSprite(int *xoffsetPtr, int *yoffsetPtr, byte textColor, int numTextLines, bool withHeader, Sprite *sprite) {
+ int linesBeforeTextStart = 2;
+ if (withHeader)
+ linesBeforeTextStart = 4;
+
+ int xoffset = *xoffsetPtr;
+ int yoffset = *yoffsetPtr;
+
+ int textHeight = numTextLines + linesBeforeTextStart;
+
+ SharedPtr<TextBitmap> bitmap(new TextBitmap(TEXTBOX_WIDTH * 8, textHeight * 8));
+
+ *sprite = Sprite();
+ sprite->drawPriority = 15;
+ sprite->drawPriority2 = 8;
+ sprite->bitmap = bitmap;
+ sprite->textColor = textColor;
+
+ memset(bitmap->pixels, ' ', textHeight * TEXTBOX_WIDTH);
+
+ int varC = SCREEN_WIDTH - 1 - xoffset - (bitmap->width + 0x1d) / 2;
+ if (varC < 0)
+ xoffset += varC;
+
+ varC = xoffset - (bitmap->width + 0x1d) / 2;
+ if (varC < 1)
+ xoffset -= varC - 1;
+
+ varC = yoffset - (bitmap->height + 0x11) - 20;
+ if (varC < 0)
+ yoffset -= varC;
+
+ xoffset -= (bitmap->width + 0x1d) / 2;
+ yoffset -= bitmap->height;
+
+ bitmap->pixels[0] = 0x10;
+ memset(&bitmap->pixels[1], 0x11, TEXTBOX_WIDTH - 2);
+ bitmap->pixels[TEXTBOX_WIDTH - 1] = 0x12;
+
+ byte *textAddr = bitmap->pixels + TEXTBOX_WIDTH;
+
+ if (withHeader) {
+ textAddr[0] = 0x13;
+ textAddr[TEXTBOX_WIDTH - 1] = 0x14;
+ textAddr += TEXTBOX_WIDTH;
+
+ textAddr[0] = 0x13;
+ memset(&textAddr[1], 0x19, TEXTBOX_WIDTH - 2);
+ textAddr[TEXTBOX_WIDTH - 1] = 0x14;
+ textAddr += TEXTBOX_WIDTH;
+ }
+
+ for (int line = 0; line < numTextLines; line++) {
+ textAddr[0] = 0x13;
+ textAddr[TEXTBOX_WIDTH - 1] = 0x14;
+ textAddr += TEXTBOX_WIDTH;
+ }
+
+ textAddr[0] = 0x15;
+ memset(&textAddr[1], 0x16, TEXTBOX_WIDTH - 2);
+ textAddr[TEXTBOX_WIDTH - 1] = 0x17;
+
+ _gfx->addSprite(sprite);
+ sprite->drawMode = 3;
+ sprite->pos.x = xoffset;
+ sprite->pos.y = yoffset;
+ sprite->drawPriority = 15;
+
+ *xoffsetPtr = xoffset;
+ *yoffsetPtr = yoffset;
+
+ return bitmap;
+}
+
+void StarTrekEngine::drawMainText(SharedPtr<TextBitmap> bitmap, int numTextLines, int numTextboxLines, const String &_text, bool withHeader) {
+ byte *dest = bitmap->pixels + TEXTBOX_WIDTH + 1; // Start of 2nd row
+ const char *text = _text.c_str();
+
+ if (numTextLines >= numTextboxLines)
+ numTextLines = numTextboxLines;
+
+ if (withHeader)
+ dest += TEXTBOX_WIDTH * 2; // Start of 4th row
+
+ int lineIndex = 0;
+ while (lineIndex != numTextLines) {
+ memcpy(dest, text, TEXTBOX_WIDTH - 2);
+ text += TEXTBOX_WIDTH - 2;
+ dest += TEXTBOX_WIDTH;
+ lineIndex++;
+ }
+
+ // Fill all remaining blank lines
+ while (lineIndex != numTextboxLines) {
+ memset(dest, ' ', TEXTBOX_WIDTH - 2);
+ dest += TEXTBOX_WIDTH;
+ lineIndex++;
+ }
+}
+
+String StarTrekEngine::readLineFormattedText(TextGetterFunc textGetter, uintptr var, int choiceIndex, SharedPtr<TextBitmap> textBitmap, int numTextboxLines, int *numTextLines) {
+ String headerText;
+ String text = (this->*textGetter)(choiceIndex, var, &headerText);
+
+ if (_textDisplayMode == TEXTDISPLAY_NONE && _sfxEnabled && _sfxWorking) {
+ uint32 oldSize = text.size();
+ text = playTextAudio(text);
+ if (oldSize != text.size())
+ _textboxHasMultipleChoices = true;
+ } else if ((_textDisplayMode == TEXTDISPLAY_WAIT || _textDisplayMode == TEXTDISPLAY_SUBTITLES)
+ && _sfxEnabled && _sfxWorking) {
+ text = playTextAudio(text);
+ } else {
+ text = skipTextAudioPrompt(text);
+ }
+
+ if (_textboxHasMultipleChoices) {
+ *numTextLines = getNumTextboxLines(text);
+
+ bool hasHeader = !headerText.empty();
+
+ String lineFormattedText = putTextIntoLines(text);
+ drawMainText(textBitmap, *numTextLines, numTextboxLines, lineFormattedText, hasHeader);
+
+ memcpy(textBitmap->pixels + TEXTBOX_WIDTH + 1, headerText.c_str(), headerText.size());
+
+ return lineFormattedText;
+ } else
+ return nullptr;
+}
+
+String StarTrekEngine::readTextFromArray(int choiceIndex, uintptr data, String *headerTextOutput) {
+ const char **textArray = (const char **)data;
+
+ const char *headerText = textArray[0];
+ const char *mainText = textArray[choiceIndex + 1];
+
+ if (*mainText == '\0')
+ return Common::String(); // Technically should be nullptr...
+
+ if (headerText == nullptr)
+ *headerTextOutput = "";
+ else
+ *headerTextOutput = centerTextboxHeader(headerText);
+ return String(mainText);
+}
+
+String StarTrekEngine::readTextFromArrayWithChoices(int choiceIndex, uintptr data, String *headerTextOutput) {
+ const char **textArray = (const char **)data;
+
+ const char *headerText = textArray[0];
+ const char *mainText = textArray[choiceIndex + 1];
+
+ if (*mainText == '\0')
+ return Common::String(); // Technically should be nullptr...
+
+ if (headerTextOutput != nullptr) {
+ if (headerText == nullptr || headerText[0] == '\0')
+ *headerTextOutput = "";
+ else {
+ if (textArray[2] != nullptr && textArray[2][0] != '\0') // More than one choice
+ getTextboxHeader(headerTextOutput, headerText, choiceIndex + 1);
+ else
+ getTextboxHeader(headerTextOutput, headerText, 0);
+ }
+ }
+ return String(mainText);
+}
+
+Common::String StarTrekEngine::showCodeInputBox() {
+ memset(_textInputBuffer, 0, TEXT_INPUT_BUFFER_SIZE - 1);
+ return showTextInputBox(SCREEN_WIDTH / 2, SCREEN_HEIGHT / 2, "Code:\n ");
+}
+
+void StarTrekEngine::redrawTextInput() {
+ char buf[MAX_TEXT_INPUT_LEN * 2 + 2];
+ memset(buf, 0, MAX_TEXT_INPUT_LEN * 2);
+ strcpy(buf, _textInputBuffer);
+
+ if (_textInputCursorChar != 0)
+ buf[_textInputCursorPos] = _textInputCursorChar;
+
+ memcpy(_textInputBitmap->pixels, _textInputBitmapSkeleton->pixels, _textInputBitmapSkeleton->width * _textInputBitmapSkeleton->height);
+
+ drawTextLineToBitmap(buf, MAX_TEXT_INPUT_LEN, 4, 12, _textInputBitmap);
+ _textInputSprite.bitmapChanged = true;
+ _gfx->drawAllSprites();
+}
+
+void StarTrekEngine::addCharToTextInputBuffer(char c) {
+ Common::String str(_textInputBuffer);
+ while ((int)str.size() < _textInputCursorPos) {
+ str += " ";
+ }
+
+ str.insertChar(c, _textInputCursorPos);
+
+ strncpy(_textInputBuffer, str.c_str(), MAX_TEXT_INPUT_LEN);
+ _textInputBuffer[MAX_TEXT_INPUT_LEN] = '\0';
+}
+
+Common::String StarTrekEngine::showTextInputBox(int16 x, int16 y, const Common::String &headerText) {
+ bool validInput = false;
+
+ _keyboardControlsMouse = false;
+ _textInputCursorPos = 0;
+
+ initTextInputSprite(x, y, headerText);
+
+ bool loop = true;
+
+ while (loop) {
+ TrekEvent event;
+ if (!popNextEvent(&event))
+ continue;
+
+ switch (event.type) {
+ case TREKEVENT_TICK:
+ _gfx->incPaletteFadeLevel();
+ _frameIndex++;
+ _textInputCursorChar = (_frameIndex & 2 ? 1 : 0);
+ redrawTextInput();
+ break;
+
+ case TREKEVENT_LBUTTONDOWN:
+ redrawTextInput();
+ validInput = true;
+ loop = false;
+ break;
+
+ case TREKEVENT_RBUTTONDOWN:
+ loop = false;
+ break;
+
+ case TREKEVENT_KEYDOWN:
+ switch (event.kbd.keycode) {
+ case Common::KEYCODE_BACKSPACE:
+ if (_textInputCursorPos > 0) {
+ _textInputCursorPos--;
+ Common::String str(_textInputBuffer);
+ str.deleteChar(_textInputCursorPos);
+ strcpy(_textInputBuffer, str.c_str());
+ }
+ redrawTextInput();
+ break;
+
+ case Common::KEYCODE_DELETE: { // ENHANCEMENT: Support delete key
+ Common::String str(_textInputBuffer);
+ if (_textInputCursorPos < (int)str.size()) {
+ str.deleteChar(_textInputCursorPos);
+ strcpy(_textInputBuffer, str.c_str());
+ redrawTextInput();
+ }
+ break;
+ }
+
+ case Common::KEYCODE_RETURN:
+ case Common::KEYCODE_KP_ENTER:
+ case Common::KEYCODE_F1:
+ redrawTextInput();
+ loop = false;
+ validInput = true;
+ break;
+
+ case Common::KEYCODE_ESCAPE:
+ case Common::KEYCODE_F2:
+ loop = false;
+ break;
+
+ case Common::KEYCODE_HOME:
+ case Common::KEYCODE_KP7:
+ _textInputCursorPos = 0;
+ break;
+
+ case Common::KEYCODE_LEFT:
+ case Common::KEYCODE_KP4:
+ if (_textInputCursorPos > 0)
+ _textInputCursorPos--;
+ redrawTextInput();
+ break;
+
+ case Common::KEYCODE_RIGHT:
+ case Common::KEYCODE_KP6:
+ if (_textInputCursorPos < MAX_TEXT_INPUT_LEN - 1)
+ _textInputCursorPos++;
+ redrawTextInput();
+ break;
+
+ case Common::KEYCODE_END:
+ case Common::KEYCODE_KP1:
+ _textInputCursorPos = strlen(_textInputBuffer);
+ // BUGFIX: Check that it doesn't exceed the buffer length.
+ // Original game had a bug where you could crash the game by pressing
+ // "end", writing a character, pressing "end" again, etc.
+ if (_textInputCursorPos >= MAX_TEXT_INPUT_LEN)
+ _textInputCursorPos = MAX_TEXT_INPUT_LEN - 1;
+ break;
+
+ default: // Typed any other character
+ if (_gfx->_font->isDisplayableCharacter(event.kbd.ascii)) {
+ addCharToTextInputBuffer(event.kbd.ascii);
+ if (_textInputCursorPos < MAX_TEXT_INPUT_LEN - 1)
+ _textInputCursorPos++;
+ redrawTextInput();
+ }
+ break;
+ }
+ break;
+
+ default:
+ break;
+ }
+ }
+
+ cleanupTextInputSprite();
+ _keyboardControlsMouse = true;
+
+ if (validInput)
+ return _textInputBuffer;
+ else
+ return "";
+}
+
+void StarTrekEngine::initTextInputSprite(int16 textboxX, int16 textboxY, const Common::String &headerText) {
+ int headerLen = headerText.size();
+
+ if (headerLen > 25)
+ headerLen = 25;
+
+ char textBuf[TEXTBOX_WIDTH * 11 + 1];
+ const char *headerPos = headerText.c_str();
+ int row = 0;
+
+ /*
+ // TODO: investigate this (might be unused...)
+ if (word_53100 != 0) {
+ // ...
+ }
+ */
+
+ do {
+ headerPos = getNextTextLine(headerPos, textBuf + row * TEXTBOX_WIDTH, headerLen);
+ row++;
+ } while (headerPos != 0 && row < 11);
+
+ int16 width = headerLen * 8 + 8;
+ int16 height = row * 8 + 8;
+
+ _textInputBitmapSkeleton = SharedPtr<Bitmap>(new Bitmap(width, height));
+ _textInputBitmap = SharedPtr<Bitmap>(new Bitmap(width, height));
+
+ _textInputBitmapSkeleton->xoffset = width / 2;
+ if (textboxX + width / 2 >= SCREEN_WIDTH)
+ _textInputBitmapSkeleton->xoffset += width / 2 + textboxX - (SCREEN_WIDTH - 1);
+ if (textboxX - width / 2 < 0)
+ _textInputBitmapSkeleton->xoffset -= 0 - (textboxX - width / 2);
+
+ _textInputBitmapSkeleton->yoffset = height + 20;
+ memset(_textInputBitmapSkeleton->pixels, 0, width * height);
+
+ // Top border
+ int16 xPos = 1;
+ int16 yPos = 1;
+ while (xPos < width - 1) {
+ _textInputBitmapSkeleton->pixels[yPos * width + xPos] = 0x78;
+ xPos++;
+ }
+
+ // Bottom border
+ xPos = 1;
+ yPos = height - 2;
+ while (xPos < width - 1) {
+ _textInputBitmapSkeleton->pixels[yPos * width + xPos] = 0x78;
+ xPos++;
+ }
+
+ // Left border
+ xPos = 1;
+ yPos = 1;
+ while (yPos < height - 1) {
+ _textInputBitmapSkeleton->pixels[yPos * width + xPos] = 0x78;
+ yPos++;
+ }
+
+ // Right border
+ xPos = width - 2;
+ yPos = 1;
+ while (yPos < height - 1) {
+ _textInputBitmapSkeleton->pixels[yPos * width + xPos] = 0x78;
+ yPos++;
+ }
+
+ // Draw header text
+ for (int r = 0; r < row; r++) {
+ char *text = textBuf + r * TEXTBOX_WIDTH;
+ drawTextLineToBitmap(text, strlen(text), 4, r * 8 + 4, _textInputBitmapSkeleton);
+ }
+
+ // Copy skeleton bitmap to actual used bitmap
+ _textInputBitmap->xoffset = _textInputBitmapSkeleton->xoffset;
+ _textInputBitmap->yoffset = _textInputBitmapSkeleton->yoffset;
+ memcpy(_textInputBitmap->pixels, _textInputBitmapSkeleton->pixels, width * height);
+
+ _gfx->addSprite(&_textInputSprite);
+ _textInputSprite.drawMode = 2;
+ _textInputSprite.field8 = "System";
+ _textInputSprite.bitmap = _textInputBitmap;
+ _textInputSprite.setXYAndPriority(textboxX, textboxY, 15);
+ _textInputSprite.drawPriority2 = 8;
+ _gfx->drawAllSprites();
+}
+
+void StarTrekEngine::cleanupTextInputSprite() {
+ _textInputSprite.dontDrawNextFrame();
+ _gfx->drawAllSprites();
+ _gfx->delSprite(&_textInputSprite);
+
+ _textInputSprite.bitmap.reset();
+ _textInputBitmapSkeleton.reset();
+ _textInputBitmap.reset();
+}
+
+}