From abf711a4d33b01cc5ef7726cdade8a39a8a5a325 Mon Sep 17 00:00:00 2001 From: Matthew Stewart Date: Fri, 27 Jul 2018 20:34:31 -0400 Subject: STARTREK: text.cpp -> textbox.cpp --- engines/startrek/module.mk | 2 +- engines/startrek/startrek.h | 2 +- engines/startrek/text.cpp | 933 ------------------------------------------- engines/startrek/textbox.cpp | 933 +++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 935 insertions(+), 935 deletions(-) delete mode 100644 engines/startrek/text.cpp create mode 100644 engines/startrek/textbox.cpp diff --git a/engines/startrek/module.mk b/engines/startrek/module.mk index 384e07b156..50aa62d735 100644 --- a/engines/startrek/module.mk +++ b/engines/startrek/module.mk @@ -20,7 +20,7 @@ MODULE_OBJS = \ space.o \ sprite.o \ startrek.o \ - text.o \ + textbox.o \ rooms/demon0.o \ rooms/demon1.o \ rooms/demon2.o \ diff --git a/engines/startrek/startrek.h b/engines/startrek/startrek.h index 386abd186f..e8ea7988ab 100644 --- a/engines/startrek/startrek.h +++ b/engines/startrek/startrek.h @@ -462,7 +462,7 @@ private: uint32 _frameStartMillis; - // text.cpp + // textbox.cpp public: /** * Gets one line of text (does not include words that won't fit). diff --git a/engines/startrek/text.cpp b/engines/startrek/text.cpp deleted file mode 100644 index 1a05ecc1e8..0000000000 --- a/engines/startrek/text.cpp +++ /dev/null @@ -1,933 +0,0 @@ -/* 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) { - 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 = 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 = 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 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 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 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 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, 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(new Bitmap(width, height)); - _textInputBitmap = SharedPtr(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(); -} - -} 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) { + 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 = 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 = 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 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 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 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 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, 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(new Bitmap(width, height)); + _textInputBitmap = SharedPtr(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(); +} + +} -- cgit v1.2.3