/* 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 . * */ #include "dgds/dialog.h" #include "common/debug.h" #include "common/rect.h" #include "common/system.h" #include "dgds/dgds.h" #include "dgds/request.h" #include "dgds/scene.h" #include "dgds/font.h" #include "dgds/drawing.h" #include "dgds/debug_util.h" namespace Dgds { int Dialog::_lastSelectedDialogItemNum = 0; Dialog *Dialog::_lastDialogSelectionChangedFor = nullptr; Dialog::Dialog() : _num(0), _bgColor(0), _fontColor(0), _selectionBgCol(0), _selectonFontCol(0), _fontSize(0), _flags(kDlgFlagNone), _frameType(kDlgFramePlain), _time(0), _nextDialogDlgNum(0), _nextDialogFileNum(0), _fileNum(0), _talkDataNum(0), _talkDataHeadNum(0) {} void Dialog::draw(Graphics::ManagedSurface *dst, DialogDrawStage stage) { if (!_state) _state.reset(new DialogState()); switch (_frameType) { case kDlgFramePlain: return drawType1(dst, stage); case kDlgFrameBorder: return drawType2(dst, stage); case kDlgFrameThought: return drawType3(dst, stage); case kDlgFrameRounded: return drawType4(dst, stage); default: error("unexpected frame type %d for dialog %d", _frameType, _num); } } const DgdsFont *Dialog::getDlgTextFont() const { DgdsEngine *engine = DgdsEngine::getInstance(); const FontManager *fontman = engine->getFontMan(); FontManager::FontType fontType = FontManager::kGameDlgFont; if (_fontSize == 1) fontType = FontManager::k8x8Font; else if (_fontSize == 3) fontType = FontManager::k4x5Font; else if (_fontSize == 4 && engine->getGameId() == GID_WILLY) fontType = FontManager::kGameDlgFont; else if (_fontSize == 4 && engine->getGameId() == GID_HOC && engine->getGameLang() == Common::DE_DEU) fontType = FontManager::kGameFont; else if (_fontSize == 5 && engine->getGameId() == GID_HOC) fontType = FontManager::kChinaFont; return fontman->getFont(fontType); } // box with simple frame void Dialog::drawType1(Graphics::ManagedSurface *dst, DialogDrawStage stage) { if (!_state) return; int x = _rect.x; int y = _rect.y; int w = _rect.width; int h = _rect.height; DgdsEngine *engine = DgdsEngine::getInstance(); if (stage == kDlgDrawStageBackground) { dst->frameRect(Common::Rect(x, y, x + w, y + h), _bgColor); if (engine->getGameId() != GID_WILLY) { dst->fillRect(Common::Rect(x + 1, y + 1, x + w - 1, y + h - 1), _fontColor); } else { dst->frameRect(Common::Rect(x + 1, y + 1, x + w - 1, y + h - 1), _fontColor); dst->fillRect(Common::Rect(x + 2, y + 2, x + w - 2, y + h - 2), _bgColor); } } else if (stage == kDlgDrawFindSelectionPointXY) { drawFindSelectionXY(); } else if (stage == kDlgDrawFindSelectionTxtOffset) { drawFindSelectionTxtOffset(); } else { if (engine->getGameId() != GID_WILLY) _state->_loc = DgdsRect(x + 3, y + 3, w - 6, h - 6); else _state->_loc = DgdsRect(x + 5, y + 5, w - 10, h - 10); byte txtCol = (engine->getGameId() == GID_WILLY) ? _fontColor : _bgColor; drawForeground(dst, txtCol, _str); } } void Dialog::drawType2BackgroundDragon(Graphics::ManagedSurface *dst, const Common::String &title) { _state->_loc = DgdsRect(_rect.x + 6, _rect.y + 6, _rect.width - 12, _rect.height - 12); RequestData::fillBackground(dst, _rect.x, _rect.y, _rect.width, _rect.height, 0); RequestData::drawCorners(dst, 11, _rect.x, _rect.y, _rect.width, _rect.height); if (!title.empty()) { // TODO: Maybe should measure the font? _state->_loc.y += 11; _state->_loc.height -= 11; RequestData::drawHeader(dst, _rect.x, _rect.y, _rect.width, 4, title, 0, true, 0, 15); } if (hasFlag(kDlgFlagFlatBg)) { dst->fillRect(_state->_loc.toCommonRect(), 0); } else { RequestData::fillBackground(dst, _state->_loc.x, _state->_loc.y, _state->_loc.width, _state->_loc.height, 6); } RequestData::drawCorners(dst, 19, _state->_loc.x - 2, _state->_loc.y - 2, _state->_loc.width + 4, _state->_loc.height + 4); _state->_loc.y++; _state->_loc.height--; _state->_loc.x += 8; _state->_loc.width -= 16; } void Dialog::drawType2BackgroundChina(Graphics::ManagedSurface *dst, const Common::String &title) { if (title.empty()) { _state->_loc = DgdsRect(_rect.x + 10, _rect.y + 10, _rect.width - 20, _rect.height - 20); RequestData::fillBackground(dst, _rect.x, _rect.y, _rect.width, _rect.height, 0); RequestData::drawCorners(dst, 1, _rect.x, _rect.y, _rect.width, _rect.height); } else { // This is 1 more pixel down than the original, but seems to be needed to get the right spot? _state->_loc = DgdsRect(_rect.x + 6, _rect.y + 17, _rect.width - 12, _rect.height - 24); dst->fillRect(Common::Rect(Common::Point(_rect.x, _rect.y), _rect.width, _rect.height), 0); RequestData::drawCorners(dst, 11, _rect.x, _rect.y, _rect.width, _rect.height); RequestData::drawHeader(dst, _rect.x, _rect.y, _rect.width, 2, title, _fontColor, false, 0, 0); } } void Dialog::drawType2BackgroundBeamish(Graphics::ManagedSurface *dst, const Common::String &title) { byte fillCol = 0; if (DgdsEngine::getInstance()->isAltDlgColors()) fillCol = 20; _state->_loc = DgdsRect(_rect.x + 11, _rect.y + 10, _rect.width - 22, _rect.height - 20); if (title.empty()) { dst->fillRect(Common::Rect(Common::Point(_rect.x + 2, _rect.y + 2), _rect.width - 4, _rect.height - 4), fillCol); uint16 cornerOffset = DgdsEngine::getInstance()->isAltDlgColors() ? 46 : 54; RequestData::drawCorners(dst, cornerOffset, _rect.x, _rect.y, _rect.width, _rect.height); } else { dst->fillRect(Common::Rect(Common::Point(_rect.x + 2, _rect.y + 2), _rect.width - 4, _rect.height - 4), fillCol); RequestData::drawCorners(dst, 46, _rect.x, _rect.y, _rect.width, _rect.height); _state->_loc.y += 15; _state->_loc.height -= 15; RequestData::drawHeader(dst, _rect.x, _rect.y + 5, _rect.width, 2, title, _fontColor, false, 0, 0); } } // box with fancy frame and optional title (everything before ":") void Dialog::drawType2(Graphics::ManagedSurface *dst, DialogDrawStage stage) { if (!_state) return; Common::String title; Common::String txt; // // Colon has to be followed by the first CR to be used as a heading // uint32 colonpos = _str.find(':'); uint32 crpos = _str.find('\r'); bool haveColon = colonpos != Common::String::npos; bool haveCR = crpos != Common::String::npos; if (haveColon && haveCR && crpos == colonpos + 1) { title = _str.substr(0, colonpos); txt = _str.substr(colonpos + 1); // Most have a CR after the colon? trim it to remove a blank line. if (txt.size() && txt[0] == '\r') txt = txt.substr(1); } else { txt = _str; } // // Special case for HoC to update the Shekel count in their description // and Willy Beamish update dollars // // This is how the original games do it too. // DgdsEngine *engine = DgdsEngine::getInstance(); if (_fileNum == 0x5d && _num == 0x32 && engine->getGameId() == GID_HOC) { int16 shekels = engine->getGDSScene()->getGlobal(44); const Common::String numstr = Common::String::format("%3d", shekels); uint32 offset = txt.find("###"); if (offset != Common::String::npos) txt.replace(offset, 3, numstr); } else if (_fileNum == 67 && _num == 9 && engine->getGameId() == GID_WILLY) { int16 cents = engine->getGDSScene()->getGlobal(3); const Common::String numstr = Common::String::format("%3d.%02d", cents / 100, cents % 100); uint32 offset = txt.find("###.##"); if (offset != Common::String::npos) txt.replace(offset, 6, numstr); } if (stage == kDlgDrawStageBackground) { if (engine->getGameId() == GID_DRAGON) drawType2BackgroundDragon(dst, title); else if (engine->getGameId() == GID_HOC) drawType2BackgroundChina(dst, title); else drawType2BackgroundBeamish(dst, title); } else if (stage == kDlgDrawFindSelectionPointXY) { drawFindSelectionXY(); } else if (stage == kDlgDrawFindSelectionTxtOffset) { drawFindSelectionTxtOffset(); } else { drawForeground(dst, _fontColor, txt); } } // Find the last line that will be printed - we don't use empty lines static uint _countPrintedLines(const Common::Array &lines) { uint nprinted = 0; for (uint i = 0; i < lines.size(); i++) { if (!lines[i].empty()) nprinted = i; } return nprinted + 1; } // Comic thought box made up of circles with 2 circles going up to it. // Draw circles with 5/4 more pixels in x because the pixels are not square. void Dialog::drawType3(Graphics::ManagedSurface *dst, DialogDrawStage stage) { if (!_state) return; if (stage == kDlgDrawStageBackground) { uint16 xradius = 9999; uint16 yradius = 40; const int16 usabley = _rect.height - 31; const int16 usablex = _rect.width - 30; for (uint16 testyradius = 40; testyradius != 0; testyradius--) { int16 testxradius = (testyradius * 5) / 4; if ((usablex / testxradius > 2) && (usabley / testyradius > 2)) { testxradius = usablex % testxradius + usabley % testyradius; if (testxradius < xradius) { yradius = testyradius; xradius = testxradius; } } if (testyradius < 20 && xradius != 9999) break; } xradius = (yradius * 5) / 4; const int16 circlesAcross = MAX(1, usablex / xradius - 1); const int16 circlesDown = MAX(1, usabley / yradius - 1); uint16 x = _rect.x + xradius; uint16 y = _rect.y + yradius; bool isbig = _rect.x + _rect.width / 2 > 160; if (isbig) x = x + 30; byte fgcol = 0; byte bgcol = 15; if (hasFlag(kDlgFlagFlatBg)) { bgcol = _bgColor; fgcol = _fontColor; } for (int i = 1; i < circlesDown; i++) { Drawing::filledCircle(x, y, xradius, yradius, dst, fgcol, bgcol); y += yradius; } for (int i = 1; i < circlesAcross; i++) { Drawing::filledCircle(x, y, xradius, yradius, dst, fgcol, bgcol); x += xradius; } for (int i = 1; i < circlesDown; i++) { Drawing::filledCircle(x, y, xradius, yradius, dst, fgcol, bgcol); y -= yradius; } for (int i = 1; i < circlesAcross; i++) { Drawing::filledCircle(x, y, xradius, yradius, dst, fgcol, bgcol); x -= xradius; } uint16 smallCircleX; if (isbig) { Drawing::filledCircle((x - xradius) - 5, y + circlesDown * yradius + 5, 10, 8, dst, fgcol, bgcol); smallCircleX = (x - xradius) - 20; } else { Drawing::filledCircle(x + circlesAcross * xradius + 5, y + circlesDown * yradius + 5, 10, 8, dst, fgcol, bgcol); smallCircleX = x + circlesAcross * xradius + 20; } Drawing::filledCircle(smallCircleX, y + circlesDown * yradius + 25, 5, 4, dst, fgcol, bgcol); int16 yoff = (yradius * 27) / 32; dst->fillRect(Common::Rect(x, y - yoff, x + (circlesAcross - 1) * xradius + 1, y + (circlesDown - 1) * yradius + yoff + 1), bgcol); int16 xoff = (xradius * 27) / 32; dst->fillRect(Common::Rect(x - xoff, y, x + (circlesAcross - 1) * xradius + xoff + 1, y + (circlesDown - 1) * yradius + 1), bgcol); int16 textRectX = x - xradius / 2; int16 textRectY = y - yradius / 2; assert(_state); _state->_loc = DgdsRect(textRectX, textRectY, circlesAcross * xradius , circlesDown * yradius); } else if (stage == kDlgDrawFindSelectionPointXY) { drawFindSelectionXY(); } else if (stage == kDlgDrawFindSelectionTxtOffset) { drawFindSelectionTxtOffset(); } else { drawForeground(dst, _fontColor, _str); } } // ellipse in Dragon, text with no background in HoC void Dialog::drawType4(Graphics::ManagedSurface *dst, DialogDrawStage stage) { if (!_state) return; int x = _rect.x; int y = _rect.y; int w = _rect.width; int h = _rect.height; int midy = (h - 1) / 2; byte fillcolor; byte fillbgcolor; if (!hasFlag(kDlgFlagFlatBg)) { fillcolor = 0; fillbgcolor = 15; } else { fillcolor = _fontColor; fillbgcolor = _bgColor; } const DgdsGameId gameId = DgdsEngine::getInstance()->getGameId(); if (stage == kDlgDrawStageBackground) { //int radius = (midy * 5) / 4; // This is not exactly the same as the original - might need some work to get pixel-perfect // Beamish uses 20x20 dialogs of type 4 to have effects without actually drawing anything. if (gameId != GID_HOC && (w > 22 || h > 22)) { Common::Rect drawRect(x, y, x + w, y + h); dst->drawRoundRect(drawRect, midy, fillbgcolor, true); dst->drawRoundRect(drawRect, midy, fillcolor, false); } } else if (stage == kDlgDrawFindSelectionPointXY) { drawFindSelectionXY(); } else if (stage == kDlgDrawFindSelectionTxtOffset) { drawFindSelectionTxtOffset(); } else { assert(_state); if (gameId != GID_HOC) { _state->_loc = DgdsRect(x + midy, y + 1, w - midy, h - 1); } else { _state->_loc = DgdsRect(x, y, w, h); fillcolor = 25; // ignore the color?? } // WORKAROUND for the Willy Beamish dialogs which are 20x20 - these should not be drawn if (gameId == GID_WILLY && (w <= 22 && h <= 22)) return; drawForeground(dst, fillcolor, _str); } } int _stringWidthIgnoringTrainingSpace(const DgdsFont *font, const Common::String &line) { if (Common::isSpace(line.lastChar())) { // Find end without trailing spaces int i = line.size() - 2; while (i > 0 && Common::isSpace(line[i])) i--; return font->getStringWidth(line.substr(0, i + 1)); } else { return font->getStringWidth(line); } } int _maxWidthIgnoringTrailingSpace(const DgdsFont *font, const Common::Array &lines) { // // The line wrapper returns width including trailing spaces, but for accurate // layout we need to ignore spaces in the string width. // int maxWidth = 0; for (const auto &line : lines) { maxWidth = MAX(_stringWidthIgnoringTrainingSpace(font, line), maxWidth); } return maxWidth; } void Dialog::drawFindSelectionXY() { if (!_state) return; const DgdsFont *font = getDlgTextFont(); // Find the appropriate _lastMouseX/lastMouseY value given the last _strMouseLoc. int x = _state->_loc.x; _state->_lastMouseX = x; int y = _state->_loc.y + 1; _state->_lastMouseY = y; _state->_charWidth = font->getMaxCharWidth(); _state->_charHeight = font->getFontHeight(); if (_state->_strMouseLoc) { Common::Array lines; font->wordWrapText(_str, _state->_loc.width, lines, 0, Graphics::kWordWrapOnExplicitNewLines | Graphics::kWordWrapAllowTrailingWhitespace); uint nlines = _countPrintedLines(lines); int maxWidth = _maxWidthIgnoringTrailingSpace(font, lines); if (hasFlag(kDlgFlagLeftJust)) { x = x + (_state->_loc.width - maxWidth - 1) / 2; _state->_lastMouseX = x; y = y + (_state->_loc.height - ((int)nlines * _state->_charHeight) - 1) / 2; _state->_lastMouseY = y; } if (_state->_strMouseLoc >= (int)_str.size()) _state->_strMouseLoc = _str.size() - 1; // Find the location of the mouse loc in the wrapped string. int totalchars = 0; for (uint lineno = 0; lineno < lines.size(); lineno++) { // +1 char for the space or CR that caused the wrap. int nexttotalchars = totalchars + lines[lineno].size() + 1; if (nexttotalchars >= _state->_strMouseLoc) break; totalchars = nexttotalchars; y += _state->_charHeight; } // now get width of the remaining string to the mouse str offset x += _stringWidthIgnoringTrainingSpace(font, _str.substr(totalchars, _state->_strMouseLoc - totalchars)); // TODO: does this make sense? if (_state->_loc.x + _state->_loc.width < (x + font->getCharWidth(_str[_state->_strMouseLoc]))) { if (_str[_state->_strMouseLoc] < '!') { _state->_charHeight = 0; _state->_charWidth = 0; _state->_lastMouseY = 0; _state->_lastMouseX = 0; return; } x = _state->_loc.x; y += _state->_charHeight; } _state->_lastMouseX = x; _state->_lastMouseY = y; _state->_charWidth = font->getCharWidth(_str[_state->_strMouseLoc]); } } /** * Get offsets into a string for a given set of wrapped lines. * * Font::wordWrapText will wrap the lines on a space or a CR, so each * line's offset is the total chars from the previous line plus 1. * each * * Returns one more value than the number of lines - the last one is * s.size() for convenience. */ static Common::Array _wrappedLineOffsets(const Common::String &s, const Common::Array &lines) { Common::Array ret; int off = 0; for (const Common::String &l : lines) { ret.push_back(off); off += l.size() + 1; } ret.push_back(s.size()); return ret; } void Dialog::drawFindSelectionTxtOffset() { if (!_state) return; // Find the appropriate _strMouseLoc value given the last x/y position. const DgdsFont *font = getDlgTextFont(); int lastMouseX = _state->_lastMouseX; int lastMouseY = _state->_lastMouseY; int lineHeight = font->getFontHeight(); int dlgx = _state->_loc.x; int dlgy = _state->_loc.y; Common::Array lines; font->wordWrapText(_str, _state->_loc.width, lines, 0, Graphics::kWordWrapOnExplicitNewLines | Graphics::kWordWrapAllowTrailingWhitespace); uint numPrintedLines = _countPrintedLines(lines); int maxWidth = _maxWidthIgnoringTrailingSpace(font, lines); if (hasFlag(kDlgFlagLeftJust)) { int textHeight = numPrintedLines * lineHeight; dlgx += (_state->_loc.width - maxWidth - 1) / 2; dlgy += (_state->_loc.height - textHeight - 1) / 2; } const Common::Array lineOffs = _wrappedLineOffsets(_str, lines); uint lineno; uint totalchars = 0; for (lineno = 0; lineno < lines.size() && dlgy + lineHeight < lastMouseY; lineno++) { totalchars = lineOffs[lineno + 1]; dlgy += lineHeight; } if (lineno < lines.size()) { const Common::String &line = lines[lineno]; for (uint charno = 0; charno < line.size(); charno++) { int charwidth = font->getCharWidth(line[charno]); if (lastMouseX <= dlgx + charwidth) { _state->_strMouseLoc = totalchars + charno; return; } dlgx += charwidth; } // Mouse is off the end of the line totalchars += line.size() + 1; _state->_strMouseLoc = totalchars; return; } _state->_strMouseLoc = _str.size(); return; } void Dialog::drawForeground(Graphics::ManagedSurface *dst, uint16 fontcol, const Common::String &txt) { // This is where we actually draw the text. // For now do the simplest wrapping, no highlighting. assert(_state); Common::StringArray lines; const DgdsFont *font = getDlgTextFont(); const int h = font->getFontHeight(); font->wordWrapText(txt, _state->_loc.width, lines, 0, Graphics::kWordWrapOnExplicitNewLines | Graphics::kWordWrapAllowTrailingWhitespace); uint numPrintedLines = _countPrintedLines(lines); int ystart = _state->_loc.y + (_state->_loc.height - (int)numPrintedLines * h) / 2; int x = _state->_loc.x; int highlightStart = INT_MAX; int highlightEnd = INT_MAX; if (_state->_selectedAction) { // find the txt in the full dlg string, as action offsets include the heading int txtoffset = _str.find(txt); highlightStart = (int)_state->_selectedAction->strStart - txtoffset; highlightEnd = (int)_state->_selectedAction->strEnd - txtoffset; } const Common::Array lineOffs = _wrappedLineOffsets(txt, lines); Graphics::TextAlign align; int xwidth; if (hasFlag(kDlgFlagLeftJust)) { int maxlen = 0; // each line left-aligned, but overall block is still centered for (const auto &line : lines) maxlen = MAX(maxlen, _stringWidthIgnoringTrainingSpace(font, line)); x += (_state->_loc.width - maxlen) / 2; align = Graphics::kTextAlignLeft; xwidth = maxlen; } else { align = Graphics::kTextAlignCenter; xwidth = _state->_loc.width; } for (uint i = 0; i < lines.size(); i++) { // Draw every line unhighlighted then highlight bits as needed font->drawString(dst, lines[i], x, ystart + i * h, xwidth, fontcol, align); if (highlightStart < lineOffs[i + 1] && highlightEnd > lineOffs[i]) { // Highlight on this line. Redraw whatever part is highlighted. int lineLen = (int)lines[i].size(); int lineHighlightStart = MAX(highlightStart - lineOffs[i], 0); int lineHighlightEnd = MIN(highlightEnd - lineOffs[i], lineLen); int highlightXOff = lineHighlightStart ? font->getStringWidth(lines[i].substr(0, lineHighlightStart)) : 0; Common::String highlightString = lines[i].substr(lineHighlightStart, lineHighlightEnd - lineHighlightStart); font->drawString(dst, highlightString, x + highlightXOff, ystart + i * h, xwidth, _selectonFontCol, align); } } } void Dialog::setFlag(DialogFlags flg) { _flags = static_cast(_flags | flg); } void Dialog::clearFlag(DialogFlags flg) { _flags = static_cast(_flags & ~flg); } void Dialog::flipFlag(DialogFlags flg) { _flags = static_cast(_flags ^ flg); } bool Dialog::hasFlag(DialogFlags flg) const { return _flags & flg; } void Dialog::clear() { clearFlag(kDlgFlagHiFinished); clearFlag(kDlgFlagRedrawSelectedActionChanged); clearFlag(kDlgFlagHi10); clearFlag(kDlgFlagHi20); clearFlag(kDlgFlagHi40); clearFlag(kDlgFlagVisible); _state.reset(); } void Dialog::updateSelectedAction(int delta) { if (!_state) return; if (_lastDialogSelectionChangedFor != this) { _lastDialogSelectionChangedFor = this; _lastSelectedDialogItemNum = 0; } if (_state->_selectedAction) { for (uint i = 0; i < _action.size(); i++) { if (_state->_selectedAction == &_action[i]) { _lastSelectedDialogItemNum = i; break; } } } _lastSelectedDialogItemNum += delta; if (!_action.empty()) { while (_lastSelectedDialogItemNum < 0) _lastSelectedDialogItemNum += _action.size(); _lastSelectedDialogItemNum = _lastSelectedDialogItemNum % _action.size(); } int mouseX = _state->_loc.x + _state->_loc.width; int mouseY = _state->_loc.y + _state->_loc.height - 2; if (_action.size() > 1) { _state->_strMouseLoc = _action[_lastSelectedDialogItemNum].strStart; draw(nullptr, kDlgDrawFindSelectionPointXY); // Move the mouse over the selected item mouseY = _state->_lastMouseY + _state->_charHeight / 2; } if (_action.size() > 1 || !delta) { debug(1, "Dialog %d: update mouse to %d, %d (mouseloc %d, selnum %d)", _num, mouseX, mouseY, _state->_strMouseLoc, _lastSelectedDialogItemNum); g_system->warpMouse(mouseX, mouseY); } } struct DialogAction *Dialog::pickAction(bool isClosing, bool isForceClose) { DgdsEngine *engine = DgdsEngine::getInstance(); if (!isForceClose && isClosing) { if (_action.empty()) return nullptr; else return &_action[engine->getRandom().getRandomNumber(_action.size() - 1)]; } assert(_state); const Common::Point lastMouse = engine->getLastMouse(); if (_state->_loc.x <= lastMouse.x && _state->_loc.x + _state->_loc.width >= lastMouse.x && _state->_loc.y <= lastMouse.y && _state->_loc.y + _state->_loc.height >= lastMouse.y) { _state->_lastMouseX = lastMouse.x; _state->_lastMouseY = lastMouse.y; draw(nullptr, kDlgDrawFindSelectionTxtOffset); char underMouse; if (_state->_strMouseLoc >= 0 && _state->_strMouseLoc < (int)_str.size()) underMouse = _str[_state->_strMouseLoc]; else underMouse = '\0'; for (auto &action : _action) { if ((action.strStart <= _state->_strMouseLoc && _state->_strMouseLoc <= action.strEnd) || (_state->_strMouseLoc == action.strEnd + 1 && underMouse == '\r' && _str[action.strEnd] != '\r')) { return &action; } } } // Note: maybe not in original, but if we are closing and // there is only one action, always do that action. if (isClosing && _action.size() == 1) return &_action[0]; return nullptr; } Common::String Dialog::dump(const Common::String &indent) const { Common::String str = Common::String::format( "%sDialog"; return str; } Common::Error Dialog::syncState(Common::Serializer &s) { s.syncAsUint32LE(_flags); bool hasState = _state.get() != nullptr; s.syncAsByte(hasState); if (hasState) { if (!_state) _state.reset(new DialogState()); _state->syncState(s); } else { _state.reset(); } return Common::kNoError; } Common::String DialogState::dump(const Common::String &indent) const { return Common::String::format("%sDialogState", indent.c_str(), _hideTime, _loc.dump("").c_str(), _lastMouseX, _lastMouseY, _charWidth, _charHeight, _strMouseLoc, (void *)_selectedAction); } Common::Error DialogState::syncState(Common::Serializer &s) { s.syncAsUint32LE(_hideTime); s.syncAsSint16LE(_lastMouseX); s.syncAsSint16LE(_lastMouseY); s.syncAsUint16LE(_charWidth); s.syncAsUint16LE(_charHeight); s.syncAsUint32LE(_strMouseLoc); s.syncAsUint16LE(_loc.x); s.syncAsUint16LE(_loc.y); s.syncAsUint16LE(_loc.width); s.syncAsUint16LE(_loc.height); return Common::kNoError; } Common::String DialogAction::dump(const Common::String &indent) const { Common::String str = Common::String::format("%sDialogueAction