/* 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 "common/debug.h" #include "common/endian.h" #include "common/file.h" #include "common/rect.h" #include "audio/mixer.h" #include "graphics/surface.h" #include "dgds/dgds.h" #include "dgds/font.h" #include "dgds/image.h" #include "dgds/includes.h" #include "dgds/request.h" #include "dgds/resource.h" namespace Dgds { static const byte DragonButtonColors[] = { 0x73, 0xF0, 0x7B, 0xDF, 0x5F, 0x5F, 0x7E, 0x27, 0x16, 0x73, 0x27, 0x16, 0xDF }; static const byte DragonSliderColors[] = { 0x7B, 0x4D, 0xF4, 0x54, 0xDF, 0x74, 0x58 }; static const byte DragonEGAButtonColors[] = { 0x7, 0x8, 0x7, 0x0, 0xF, 0x7, 0xC, 0x4, 0x0, 0xF, 0xF, 0xC, 0x4 }; static const byte DragonEGASliderColors[] { 0x7, 0xF, 0x8, 0x7, 0x0, 0x7, 0x7 }; static const byte DragonHeaderTxtColor = 0; static const byte DragonHeaderTopColor = 0; static const byte DragonHeaderBottomColor = 15; static const byte DragonBackgroundColor = 7; static const byte ChinaBackgroundColor = 23; static const byte ChinaHeaderTxtColor = 25; static const byte ChinaHeaderTopColor = 16; static const byte ChinaHeaderBottomColor = 20; static const byte ChinaButtonColorsOn[] = { 0x10, 0x11, 0x10, 0x10, 0x10, 0x06, 0x14, 0x1A }; static const byte ChinaButtonColorsOff[] = { 0x10, 0x14, 0x06, 0x18, 0x10, 0x11, 0x14, 0x13 }; static const byte ChinaEGAButtonColorsOn[] = { 8, 15, 14, 14, 14, 6, 0, 8 }; static const byte ChinaEGAButtonColorsOff[] = { 6, 0, 8, 6, 14, 7, 0, 0 }; static const byte ChinaSliderColors[] = { 0x1A, 0x15, 0x10, 0x06, 0x18 }; static const byte WillyBackgroundColor = 16; static const byte WillyButtonColor = 20; static const byte WillyHeaderTxtColor = 0; static const byte MenuBackgroundColors[] { 0x71, 0x71, 0x71, 0x71, 0x71, 0x7B, 0x71, 0x7B, 0x7B, 0x7B, 0x7B, 0x7B, 0x7B, 0x7B, 0x7B, 0x7B, 0x7A, 0x7B, 0x7A, 0x7A, 0x7A, 0x7A, 0x7A, 0x7A, 0x7A, 0x7A, 0x7A, 0x46, 0x7A, 0x46, 0x46, 0x46, 0x46, 0x46, 0x46, 0x46, 0x46, 0x46, 0x58, 0x46, 0x58, 0x58, 0x58, 0x58, 0x58, 0x58, 0x58, 0x58, 0x58, 0x52, 0x58, 0x52, 0x52, 0x52, 0x52, 0x52, 0x52, 0x52, 0x52, 0x52, 0x59, 0x52, 0x59, 0x59, 0x59, 0x59, 0x59, 0x59, 0x59, 0x59, 0x59, 0x5C, 0x59, 0x5C, 0x5C, 0x5C, 0x5C, 0x5C, 0x5C, 0x5C, 0x5C, 0x5C, 0x0F, 0x5C, 0x0F, 0x0F, 0x0F, 0x0F, 0x0F, 0x0F, 0x0F, 0x5C, 0x0F, 0x5C, 0x5C, 0x5C, 0x5C, 0x5C, 0x5C, 0x5C, 0x5C, 0x5C, 0x59, 0x5C, 0x59, 0x59, 0x59, 0x59, 0x59, 0x59, 0x59, 0x59, 0x59, 0x52, 0x59, 0x52, 0x52, 0x52, 0x52, 0x52, 0x52, 0x52, 0x52, 0x52, 0x58, 0x52, 0x58, 0x58, 0x58, 0x58, 0x58, 0x58, 0x58, 0x58, 0x58, 0x46, 0x58, 0x46, 0x46, 0x46, 0x46, 0x46, 0x46, 0x46, 0x46, 0x46, 0x7A, 0x46, 0x7A, 0x7A, 0x7A, 0x7A, 0x7A, 0x7A, 0x7A, 0x7A, 0x7A, 0x7B, 0x7A, 0x7B, 0x7B, 0x7B, 0x7B, 0x7B, 0x7B, 0x7B, 0x7B, 0x7B, 0x71, 0x7B, 0x71, 0x71, 0x71, 0x71, 0x71, 0x71 }; RequestParser::RequestParser(ResourceManager *resman, Decompressor *decompressor) : DgdsParser(resman, decompressor) { } bool RequestParser::parseGADChunk(RequestData &data, DgdsChunkReader &chunk, int num) { Common::SeekableReadStream *str = chunk.getContent(); uint16 numGadgets = str->readUint16LE(); data._gadgets.resize(numGadgets); // Note: The original has some logic about loading single gadgets // here, is only ever called with "num" of -1 (load all gadgets), // so maybe just skip it? if (num != -1) error("Request::parseGADChunk: Implement handling of num other than -1"); for (Common::SharedPtr &gptr : data._gadgets) { uint16 vals[12]; for (int i = 0; i < 12; i++) vals[i] = str->readUint16LE(); GadgetType gadgetType = static_cast(vals[5]); if (num == -1 || num == vals[0]) { if (gadgetType == kGadgetText) gptr.reset(new TextAreaGadget()); else if (gadgetType == kGadgetSlider) gptr.reset(new SliderGadget()); else if (gadgetType == kGadgetImage) gptr.reset(new ImageGadget()); else if (gadgetType == kGadgetButton) gptr.reset(new ButtonGadget()); else gptr.reset(new Gadget()); } if (gptr) { gptr->_gadgetNo = vals[0]; gptr->_x = vals[1]; gptr->_y = vals[2]; gptr->_width = vals[3]; gptr->_height = vals[4]; gptr->_gadgetType = gadgetType; gptr->_flags2 = vals[6]; gptr->_flags3 = vals[7]; gptr->_fontNo = vals[8]; gptr->_col1 = vals[9]; gptr->_col2 = vals[10]; gptr->_col3 = vals[11]; gptr->_parentX = data._rect.x; gptr->_parentY = data._rect.y; } uint16 type1 = str->readUint16LE(); if (type1 == 1) { Common::String s = str->readString(); if (gptr) gptr->_sval1S = s; } else { uint16 i = str->readUint16LE(); if (gptr) gptr->_sval1I = i; } if (gptr) gptr->_sval1Type = type1; uint16 type2 = str->readUint16LE(); if (type2 == 1) { Common::String s = str->readString(); if (gptr) gptr->_sval2S = s; } else { uint16 i = str->readUint16LE(); if (gptr) gptr->_sval2I = i; } if (gptr) gptr->_sval2Type = type2; uint16 val = str->readUint16LE(); if (gptr) { gptr->_field20_0x28 = val; gptr->_field21_0x2a = val >> 0xf; } switch (gadgetType) { case kGadgetText: { uint16 i1 = str->readUint16LE(); uint16 i2 = str->readUint16LE(); if (gptr) { TextAreaGadget *g1 = static_cast(gptr.get()); g1->_textGadget_i1 = i1; g1->_bufLen = i2; } break; } case kGadgetSlider: { uint16 i1 = str->readUint16LE(); uint16 i2 = str->readUint16LE(); uint16 i3 = str->readUint16LE(); uint16 i4 = str->readUint16LE(); if (gptr) { SliderGadget *g2 = static_cast(gptr.get()); g2->_gadget2_i1 = i1; g2->_gadget2_i2 = i2; g2->_gadget2_i3 = i3; g2->_gadget2_i4 = i4; } break; } case kGadgetButton: { Common::String s = str->readString(); if (gptr) gptr->_buttonName = s; break; } case kGadgetImage: { uint16 i1 = str->readUint16LE(); uint16 i2 = str->readUint16LE(); if (gptr) { ImageGadget *g8 = static_cast(gptr.get()); g8->_xStep = i1; g8->_yStep = i2; } break; } default: break; } } return str->err(); } bool RequestParser::parseREQChunk(RequestData &data, DgdsChunkReader &chunk, int num) { Common::SeekableReadStream *str = chunk.getContent(); uint16 chunkNum = str->readUint16LE(); // Slight HACK - only Willy Beamish has a different number for the main menu if (chunkNum == kMenuMainBeamish) chunkNum = kMenuMain; // Note: The original has some logic about loading single request blocks // here, is only ever called with "num" of -1 (load all), // so maybe just skip it? if (num != -1) error("Request::parseGADChunk: Implement handling of num other than -1"); data._fileNum = chunkNum; data._rect.x = str->readUint16LE(); data._rect.y = str->readUint16LE(); data._rect.width = str->readUint16LE(); data._rect.height = str->readUint16LE(); data._col1 = str->readUint16LE(); data._col2 = str->readUint16LE(); data._flags = str->readUint16LE(); uint16 numTextItems = str->readUint16LE(); data._textItemList.resize(numTextItems); for (int i = 0; i < numTextItems; i++) { TextItem &dst = data._textItemList[i]; dst._x = str->readUint16LE(); dst._y = str->readUint16LE(); for (int j = 0; j < 2; j++) { dst._vals[j] = str->readUint16LE(); } dst._txt = str->readString(); } uint16 numFillAreas = str->readUint16LE(); data._fillAreaList.resize(numFillAreas); for (int i = 0; i < numFillAreas; i++) { RequestFillArea &dst = data._fillAreaList[i]; dst._x = str->readUint16LE(); dst._y = str->readUint16LE(); dst._width = str->readUint16LE(); dst._height = str->readUint16LE(); dst._col1 = str->readUint16LE(); dst._col2 = str->readUint16LE(); } return str->err(); } bool RequestParser::handleChunk(DgdsChunkReader &chunk, ParserData *data) { REQFileData &rfdata = *static_cast(data); // The game supports loading a particular item, but always passes -1? int num = -1; if (chunk.isContainer()) { // TAG: contains tags for the request data, all content if (chunk.getId() == ID_TAG) chunk.skipContent(); return false; // continue parsing } if (chunk.getId() == ID_REQ) { rfdata._requests.resize(rfdata._requests.size() + 1); parseREQChunk(rfdata._requests.back(), chunk, num); } else if (chunk.getId() == ID_GAD) { if (rfdata._requests.empty()) error("GAD chunk before any REQ chunks in Request file %s", _filename.c_str()); parseGADChunk(rfdata._requests.back(), chunk, num); } return chunk.getContent()->err(); } Common::String Gadget::dump() const { char buf1[6], buf2[6]; const char *sval1 = _sval1S.c_str(); const char *sval2 = _sval2S.c_str(); if (_sval1Type != 1) { sval1 = buf1; snprintf(buf1, 6, "%d", _sval1I); } if (_sval2Type != 1) { sval2 = buf2; snprintf(buf2, 6, "%d", _sval2I); } return Common::String::format( "%s", _gadgetType == kGadgetButton ? "ButtonGadget" : "Gadget", _gadgetNo, _x, _y, _width, _height, _gadgetType, _flags2, _flags3, sval1, sval2, _buttonName.c_str(), _parentX, _parentY); } void Gadget::draw(Graphics::ManagedSurface *dst) const {} Common::Point Gadget::topLeft() const { return Common::Point(_x + _parentX, _y + _parentY); } Common::Point Gadget::midPoint() const { return topLeft() + Common::Point(_width / 2, _height / 2); } bool Gadget::containsPoint(const Common::Point &pt) { Common::Point tl = topLeft(); Common::Rect gadgetRect(tl, _width, _height - 1); return gadgetRect.contains(pt); } void Gadget::setVisible(bool visible) { if (visible) _flags3 &= ~0x40; else _flags3 |= 0x40; } byte ButtonGadget::drawDragonBg(Graphics::ManagedSurface *dst, bool enabled) const { Common::Point pt = topLeft(); int16 x = pt.x; int16 y = pt.y; int16 right = x + _width; int16 x2 = right - 1; int16 bottom = (y + _height) - 1; const byte *colors = DgdsEngine::getInstance()->isEGA() ? DragonEGAButtonColors : DragonButtonColors; byte fill = colors[0]; dst->hLine(x, y, x2, fill); dst->hLine(x + 2, y + 2, right - 3, fill); dst->hLine(x + 1, bottom - 2, x + 1, fill); dst->hLine(right - 2, bottom - 2, right - 2, fill); dst->hLine(x + 1, bottom - 1, right - 2, fill); fill = colors[1]; dst->vLine(x, y + 1, bottom, fill); dst->vLine(x2, y + 1, bottom, fill); dst->vLine(x + 2, y + 3, bottom - 2, fill); dst->vLine(right - 3, y + 3, bottom - 2, fill); dst->hLine(x + 3,bottom - 2, right - 4, fill); fill = colors[2]; dst->vLine(x + 1, y + 2, bottom - 3, fill); dst->vLine(right - 2, y + 2, bottom - 3, fill); dst->hLine(x + 1, bottom, right - 2, colors[3]); dst->hLine(x + 1, y + 1, right - 2, colors[4]); int colOffset; if (enabled) { colOffset = 5; } else { colOffset = 9; } dst->hLine(x + 3, y + 3, right - 4, colors[colOffset + 1]); // TODO: This is done with a different call in the game.. is there some reason for that? dst->fillRect(Common::Rect(x + 3, y + 4, x + 3 + _width - 6, y + 4 + _height - 8), colors[colOffset + 2]); dst->hLine(x + 3, bottom - 3, right - 4, colors[colOffset + 3]); return colors[colOffset]; } byte ButtonGadget::drawChinaBg(Graphics::ManagedSurface *dst, bool enabled) const { Common::Point pt = topLeft(); int16 x = pt.x; int16 x2 = x + _width - 1; int16 y = pt.y; int16 y2 = y + _height - 1; int cnum = 0; const byte *colors = DgdsEngine::getInstance()->isEGA() ? (enabled ? ChinaEGAButtonColorsOn : ChinaEGAButtonColorsOff) : (enabled ? ChinaButtonColorsOn : ChinaButtonColorsOff); for (int i = 0; i < 7; i++) { byte drawCol = colors[cnum]; cnum++; if (i < 3) { dst->hLine(x + i + 1, y + i, x2 - i, drawCol); dst->vLine(x2 - i, y + i + 1, y2 - i, drawCol); } else if (i < 4) { int16 rheight, rwidth; if (_height + -6 < 3) rheight = 2; else rheight = _height + -6; if (_width + -6 < 3) rwidth = 2; else rwidth = _width + -6; dst->fillRect(Common::Rect(Common::Point(x + 3, y + 3), rwidth, rheight), drawCol); } else { int16 x2_ = 6 - i; dst->vLine(x + x2_, y + x2_, y2 - x2_, drawCol); dst->hLine(x + x2_, y2 - x2_, (x2 - x2_) + -1, drawCol); } } return colors[7]; } byte ButtonGadget::drawWillyBg(Graphics::ManagedSurface *dst, bool enabled) const { Common::Point pt = topLeft(); uint16 cornerFrame = enabled ? 8 : 16; RequestData::drawCorners(dst, cornerFrame, pt.x, pt.y, _width, _height); int16 fillHeight = enabled ? _height - 8 : _height - 6; if (_width > 16 && fillHeight > 0) dst->fillRect(Common::Rect(Common::Point(pt.x + 8, pt.y + 3), _width - 16, fillHeight), WillyButtonColor); return 0; } void ButtonGadget::draw(Graphics::ManagedSurface *dst) const { DgdsGameId gameId = DgdsEngine::getInstance()->getGameId(); bool enabled = !(_flags3 & 9); byte textCol; if (gameId == GID_DRAGON) textCol = drawDragonBg(dst, enabled); else if (gameId == GID_HOC) textCol = drawChinaBg(dst, enabled); else textCol = drawWillyBg(dst, enabled); Common::Point pt = topLeft(); int16 x = pt.x; int16 y = pt.y; if (!_buttonName.empty()) { const DgdsFont *font = RequestData::getMenuFont(); Common::String name = _buttonName; int fontHeight = font->getFontHeight(); //bool twoline; int yoffset; uint32 linebreak = name.find('&'); Common::String line1, line2; if (linebreak != Common::String::npos) { //twoline = true; name.setChar(' ', linebreak); yoffset = _height + 1 - fontHeight * 2; line1 = _buttonName.substr(0, linebreak); line2 = _buttonName.substr(linebreak + 1); } else { //twoline = false; yoffset = _height - fontHeight; line1 = _buttonName; } yoffset = y + yoffset / 2; if (gameId == GID_WILLY) yoffset -= 2; int lineWidth = font->getStringWidth(line1); font->drawString(dst, line1, x + (_width - lineWidth) / 2 + 1, yoffset + 2, lineWidth, textCol); if (linebreak != Common::String::npos) { lineWidth = font->getStringWidth(line2); font->drawString(dst, line2, x + (_width - lineWidth) / 2 + 1, yoffset + fontHeight, lineWidth, textCol); } } else if (gameId == GID_WILLY && _height == 32) { drawWillyBmpButtons(dst); } } enum WillyMenuIcons { kWillyArrowRight = 33, kWillyCD = 34, kWillyRewind = 35, kWillyStop = 36, kWillyQuestionMark = 37, kWillyBam = 38, kWillyMusicNote = 39, kWillyHelpText = 40, kWillySwoosh = 42, kWillyPlay = 43, kWillyNoBam = 44, kWillyNoMusicNote = 45, }; void ButtonGadget::drawCenteredBmpIcon(Graphics::ManagedSurface *dst, int16 cornerNum) const { const Image *uiCorners = RequestData::getCorners(); const Common::Rect screen(SCREEN_WIDTH, SCREEN_HEIGHT); const Common::Point tl = topLeft(); int16 x = tl.x + (_width - uiCorners->width(cornerNum)) / 2; int16 y = tl.y + (_height - uiCorners->height(cornerNum)) / 2; uiCorners->drawBitmap(cornerNum, x, y, screen, *dst); } void ButtonGadget::drawWillyLoadSaveIcon(Graphics::ManagedSurface *dst, bool isLoad) const { const Image *uiCorners = RequestData::getCorners(); const Common::Rect screen(SCREEN_WIDTH, SCREEN_HEIGHT); const Common::Point tl = topLeft(); int16 cdIconWidth = uiCorners->width(kWillyCD); int16 arrowIconWidth = uiCorners->width(kWillyArrowRight); int16 x = tl.x + (_width - cdIconWidth - arrowIconWidth) / 2; int16 cdy = tl.y + (_height - uiCorners->height(kWillyCD)) / 2; int16 arrowy = tl.y + (_height - uiCorners->height(kWillyArrowRight)) / 2; uiCorners->drawBitmap(kWillyCD, x, cdy, screen, *dst); uiCorners->drawBitmap(kWillyArrowRight, x + cdIconWidth, arrowy, screen, *dst, isLoad ? kImageFlipNone : kImageFlipH); } void ButtonGadget::drawWillyBmpButtons(Graphics::ManagedSurface *dst) const { switch (_gadgetNo) { case 110: // SAVE (CD icon, arrow flipped) drawWillyLoadSaveIcon(dst, false); break; case 111: // LOAD (CD icon, arrow) drawWillyLoadSaveIcon(dst, true); break; case 112: // RESTART (rewind icon) drawCenteredBmpIcon(dst, kWillyRewind); break; case 113: // QUIT (stop icon) drawCenteredBmpIcon(dst, kWillyStop); break; case 114: // HELP (questionmark icon) drawCenteredBmpIcon(dst, kWillyQuestionMark); break; case 115: { // SFX (bam icon) bool sfxOff = DgdsEngine::getInstance()->_mixer->isSoundTypeMuted(Audio::Mixer::kSFXSoundType); drawCenteredBmpIcon(dst, sfxOff ? kWillyNoBam : kWillyBam); break; } case 116: { // MUSIC (musical note icon) bool musicOff = DgdsEngine::getInstance()->_mixer->isSoundTypeMuted(Audio::Mixer::kMusicSoundType); drawCenteredBmpIcon(dst, musicOff ? kWillyNoMusicNote : kWillyMusicNote); break; } case 120: // HELP (questionmark and text icon) drawCenteredBmpIcon(dst, kWillyHelpText); break; case 118: // CREDITS (swoosh icon) drawCenteredBmpIcon(dst, kWillySwoosh); break; case 119: // PLAY (play icon) drawCenteredBmpIcon(dst, kWillyPlay); break; default: break; } } void ButtonGadget::toggle(bool enable) { if (!enable) _flags3 |= 9; // 0x1001 else _flags3 &= 6; // 0x0110 } Common::String TextAreaGadget::dump() const { const Common::String base = Gadget::dump(); return Common::String::format("TextArea<%s, %d buflen %d>", base.c_str(), _textGadget_i1, _bufLen); } void TextAreaGadget::draw(Graphics::ManagedSurface *dst) const { const DgdsFont *font = RequestData::getMenuFont(); font->drawString(dst, _buttonName, _x + _parentX, _y + _parentY, 0, 0); } Common::String SliderGadget::dump() const { const Common::String base = Gadget::dump(); return Common::String::format("Slider<%s, %d %d %d %d>", base.c_str(), _gadget2_i1, _gadget2_i2, _gadget2_i3, _gadget2_i4); } // Slider labels and title are hard-coded in game, not part of data files. static const char *_sliderTitleForGadget(uint16 num, Common::Language language) { if (language == Common::EN_ANY) { switch (num) { case 0x7B: return "DIFFICULTY"; case 0x7D: return "TEXT SPEED"; case 0x83: return "DETAIL LEVEL"; case 0x98: return "MOUSE SPEED"; case 0x9C: return "BUTTON THRESHOLD"; default: return "SLIDER"; } } else if (language == Common::DE_DEU) { switch (num) { case 0x7B: return "SCHWIERIGKEITSGRAD"; case 0x7D: return "TEXT-VERWEILDAUER"; case 0x83: return "DETAILS"; case 0x98: return "GESCHWINDIGKEIT"; case 0x9C: return "TASTENEMPFINDLICHKEIT"; default: return "REGLER"; } } else { error("Unsupported language %d", language); } } static const char *_sliderLabelsForGadget(uint16 num, Common::Language language) { if (language == Common::EN_ANY) { switch (num) { case 0x7B: return "EASY HARD"; case 0x7D: return "SLOW FAST"; case 0x83: return "LOW HIGH"; case 0x98: return "SLOW FAST"; case 0x9C: return "LONG SHORT"; default: return "MIN MAX"; } } else if (language == Common::DE_DEU) { return "- +"; } else { error("Unsupported language %d", language); } } static const int SLIDER_HANDLE_FRAME = 28; void SliderGadget::drawDragonBg(Graphics::ManagedSurface *dst) const { int16 x = _x + _parentX; int16 y = _y + _parentY; int16 x2 = x + _width; int16 y2 = (y + _height) - 1; int16 y1 = y - 1; static const byte *colors = DgdsEngine::getInstance()->isEGA() ? DragonEGASliderColors : DragonSliderColors; dst->vLine(x - 2, y - 1, y2, colors[0]); dst->vLine(x - 1, y1, y2, colors[1]); dst->hLine(x, y2, x2 - 1, colors[1]); dst->hLine(x, y1, x2, colors[2]); dst->vLine(x2, y1, y2, colors[2]); dst->vLine(x2 + 1, y1, y2, colors[3]); dst->hLine(x, y, x2 - 1, colors[4]); dst->vLine(x2 - 1, y + 1, (y + _height) - 2, colors[4]); // This is not exactly what happens in the original, but gets the same result Common::Rect fillrect = Common::Rect(x, y + 1, x + _width - 1, y + _height - 1); dst->fillRect(fillrect, colors[5]); fillrect.grow(-1); dst->fillRect(fillrect, colors[6]); } void SliderGadget::drawChinaBg(Graphics::ManagedSurface *dst) const { int16 x = _x + _parentX; int16 y = _y + _parentY; int16 y2 = y + 8; int16 x2 = x + _width + 2; // left 1st dst->vLine(x - 2, y - 1, y2 + 1, ChinaSliderColors[0]); // left 2nd dst->vLine(x - 1, y, y2, ChinaSliderColors[1]); // left 3rd dst->vLine(x, y + 1, y2 - 1, ChinaSliderColors[2]); // top 1st dst->hLine(x - 1, y - 1, x2,ChinaSliderColors[2]); // top 2nd dst->hLine(x, y, x2 - 1, ChinaSliderColors[0]); // top 3rd dst->hLine(x + 1, y + 1, x2 - 2, ChinaSliderColors[3]); // right 1st dst->vLine(x2 - 1, y + 1, y2 - 1, ChinaSliderColors[3]); // right 2nd dst->vLine(x2, y, y2, ChinaSliderColors[0]); // right 3rd dst->vLine(x2 + 1, y - 1, y2 + 1, ChinaSliderColors[2]); // bottom 1st dst->hLine(x + 1, y2 - 1, x2 - 2, ChinaSliderColors[2]); // bottom 2nd dst->hLine(x, y2, x2 - 1, ChinaSliderColors[1]); // bottom 3rd dst->hLine(x - 1, y2 + 1, x2, ChinaSliderColors[0]); Common::Rect fillrect = Common::Rect(x + 1, y + 2, x2 - 1, y2 - 1); dst->fillRect(fillrect, ChinaSliderColors[4]); } void SliderGadget::draw(Graphics::ManagedSurface *dst) const { const DgdsFont *font = RequestData::getMenuFont(); Common::Language language = DgdsEngine::getInstance()->getGameLang(); int16 x = _x + _parentX; int16 y = _y + _parentY; int16 titley = (y - font->getFontHeight()) + 1; const char *title = _sliderTitleForGadget(_gadgetNo, language); const char *labels = _sliderLabelsForGadget(_gadgetNo, language); int16 titleWidth = font->getStringWidth(title); DgdsGameId gameId = DgdsEngine::getInstance()->getGameId(); byte textCol = (gameId == GID_DRAGON) ? 0 : 0x13; int16 labelYOff = (gameId == GID_DRAGON) ? 7 : 11; font->drawString(dst, title, x + (_width - titleWidth) / 2, titley, titleWidth, textCol); int16 labelWidth = font->getStringWidth(labels); font->drawString(dst, labels, x + (_width - labelWidth) / 2, y + labelYOff, labelWidth, textCol); if (gameId == GID_DRAGON) drawDragonBg(dst); else drawChinaBg(dst); // Draw the slider control in the right spot const Image *uiCorners = RequestData::getCorners(); uiCorners->drawBitmap(SLIDER_HANDLE_FRAME, x + _handleX, y, Common::Rect(0, 0, SCREEN_WIDTH, SCREEN_HEIGHT), *dst); } SliderGadget::SliderGadget() : _lock(false), _steps(0), _gadget2_i1(0), _gadget2_i2(0), _gadget2_i3(0), _gadget2_i4(0), _handleX(0) { } int16 SliderGadget::getHandleWidth() const { const Image *uiCorners = RequestData::getCorners(); int16 handleWidth = uiCorners->width(SLIDER_HANDLE_FRAME); return handleWidth - 2; } int16 SliderGadget::getUsableWidth() const { return _width + 4 - getHandleWidth(); } void SliderGadget::onDrag(const Common::Point &mousePt) { const Common::Point topLeftPt = topLeft(); const Common::Point relMouse = mousePt - topLeftPt; // match middle of handle to mouse point int16 handleWidth = getHandleWidth(); _handleX = CLIP(relMouse.x - handleWidth / 2, 0, (int)getUsableWidth()); } int16 SliderGadget::onDragFinish(const Common::Point &mousePt) { onDrag(mousePt); int16 newVal = getValue(); if (_lock) setValue(newVal); return newVal; } int16 SliderGadget::getValue() { int16 stepSize = getUsableWidth() / (_steps - 1); // Find the closest step point to the left end of the handle int16 closestStep = (_handleX + stepSize / 2) / stepSize; return CLIP(closestStep, (int16)0, _steps); } void SliderGadget::setValue(int16 val) { // if val is steps-1, slider x should be at.. int16 usableWidth = getUsableWidth(); if (val == _steps - 1) _handleX = usableWidth; else _handleX = (usableWidth * val) / (_steps - 1); } int16 SliderGadget::onClick(const Common::Point &mousePt) { const Common::Point topLeftPt = topLeft(); const Common::Point relMouse = mousePt - topLeftPt; // A click should move the slider to the next step in the direction of the click. int16 handleMiddle = _handleX + getHandleWidth() / 2; // round up step size to ensure we move far enough.. int16 val = getValue(); int16 newVal = val; if (relMouse.x > handleMiddle) newVal++; else newVal--; debug(1, "clicked on slider %d, move val from %d -> %d", _gadgetNo, val, newVal); newVal = CLIP((int)newVal, 0, _steps - 1); setValue(newVal); return newVal; } Common::String ImageGadget::dump() const { const Common::String base = Gadget::dump(); return Common::String::format("Image<%s, xStep %d yStep %d>", base.c_str(), _xStep, _yStep); } static void _drawFrame(Graphics::ManagedSurface *dst, int16 x, int16 y, int16 w, int16 h, byte col1, byte col2) { const int xmax = x + w - 1; const int ymax = y + h - 1; bool filled = true; if (filled) { for (int yy = y; yy < ymax; yy++) { for (int xx = x; xx < xmax; xx = xx + 1) { dst->setPixel(xx, yy, (byte)dst->getPixel(xx, yy) ^ col2); } } } for (int yy = y; yy <= ymax; yy++) { dst->setPixel(x, yy, (byte)dst->getPixel(x, yy) ^ col1); dst->setPixel(xmax, yy, (byte)dst->getPixel(xmax, yy) ^ col1); } for (int xx = x; xx < xmax; xx++) { dst->setPixel(xx, y, (byte)dst->getPixel(xx, y) ^ col1); dst->setPixel(xx, ymax, (byte)dst->getPixel(xx, ymax) ^ col1); } } void ImageGadget::draw(Graphics::ManagedSurface *dst) const { int xstep = _xStep; int ystep = _yStep; if (!xstep || !ystep) return; const DgdsGameId gameId = DgdsEngine::getInstance()->getGameId(); int xoff = _x + _parentX; int yoff = _y + _parentY; int fillHeight = _height; if (gameId == GID_WILLY) fillHeight++; Common::Rect drawRect(Common::Point(xoff, yoff), _width, fillHeight); dst->fillRect(drawRect, _col1); // Note: not quite the same as the original logic here, but gets the same result. _drawFrame(dst, xoff, yoff, _width, _height, _sval1I, _sval1I); // NOTE: This only done in inventory in originals, but no other // Request uses the ImageGadget. if (gameId == GID_DRAGON) RequestData::drawCorners(dst, 19, xoff - 2, yoff - 2, _width + 4, _height + 4); else if (gameId == GID_HOC) RequestData::drawCorners(dst, 19, xoff - 4, yoff - 4, _width + 8, _height + 8); else if (gameId == GID_WILLY) RequestData::drawCorners(dst, 25, xoff - 4, yoff - 4, _width + 8, _height + 8); } Common::String RequestData::dump() const { Common::String ret = Common::String::format("RequestData\n", t._txt.c_str(), t._x, t._y, t._vals[0], t._vals[1]); for (const auto &f : _fillAreaList) ret += Common::String::format(" FillArea\n", f._x, f._y, f._width, f._height, f._col1, f._col2); for (const auto &g : _gadgets) ret += Common::String::format(" %s\n", g->dump().c_str()); ret += ">"; return ret; } void RequestData::drawBg(Graphics::ManagedSurface *dst) const { int slidery = 0; for (const auto &gadget : _gadgets) { const SliderGadget *slider = dynamic_cast(gadget.get()); if (slider) { slidery = MAX(slidery, slider->_y + slider->_height); } } Common::String header; if (!_textItemList.empty()) header = _textItemList[0]._txt.substr(1); bool isDragon = DgdsEngine::getInstance()->getGameId() == GID_DRAGON; if (slidery && isDragon) drawBackgroundWithSliderArea(dst, slidery, header); else drawBackgroundNoSliders(dst, header); } void RequestData::drawInvType(Graphics::ManagedSurface *dst) { if (_flags & 0x40) return; drawBackgroundNoSliders(dst, ""); for (const auto &fillArea : _fillAreaList) { Common::Rect r(Common::Point(_rect.x + fillArea._x, _rect.y + fillArea._y), fillArea._width, fillArea._height); dst->fillRect(r, fillArea._col1); } for (const auto &textItem : _textItemList) { if (!textItem._txt.empty() && textItem._txt != " ") error("TODO: RequestData::drawInvType: Implement support for drawing text item."); } for (auto &gadget : _gadgets) gadget->_flags3 |= 0x100; for (auto &gadget : _gadgets) { if (!(gadget->_flags3 & 0x40)) { gadget->draw(dst); } } _flags |= 4; } /*static*/ const DgdsFont *RequestData::getMenuFont() { return DgdsEngine::getInstance()->getFontMan()->getFont(FontManager::kGameFont); } /*static*/ const Image *RequestData::getCorners() { return DgdsEngine::getInstance()->getUICorners().get(); } /*static*/ void RequestData::drawCorners(Graphics::ManagedSurface *dst, uint16 startNum, uint16 x, uint16 y, uint16 width, uint16 height) { const Image *uiCorners = RequestData::getCorners(); assert(uiCorners->loadedFrameCount() > startNum + 7); const Common::Array> &cframes = uiCorners->getFrames(); const Common::SharedPtr *corners = cframes.data() + startNum; // // Corners images are: // 0 1 2 3 4 5 6 7 // TL T TR L R BL B BR // // // The T, L, R, and B blocks are repeated as many times as is needed to make the // size of menu/button we need. // // The last repeated one is bottom/right-aligned so that it doesn't overlap with // the corner (this is visible eg in Willy Beamish menu buttons) // for (int xoff = x + corners[0]->w; xoff < (x + width) - corners[2]->w; xoff += corners[1]->w) { int xdraw = MIN(xoff, (x + width) - corners[2]->w - corners[1]->w); dst->transBlitFrom(*corners[1], Common::Point(xdraw, y)); } for (int xoff = x + corners[5]->w; xoff < (x + width) - corners[7]->w; xoff += corners[6]->w) { int xdraw = MIN(xoff, (x + width) - corners[7]->w - corners[6]->w); dst->transBlitFrom(*corners[6], Common::Point(xdraw, (y + height) - corners[6]->h)); } for (int yoff = y + corners[0]->h; yoff < (y + height) - corners[5]->h; yoff += corners[3]->h) { int ydraw = MIN(yoff, (y + height) - corners[5]->h - corners[3]->h); dst->transBlitFrom(*corners[3], Common::Point(x, ydraw)); } for (int yoff = y + corners[2]->h; yoff < (y + height) - corners[7]->h; yoff += corners[4]->h) { int ydraw = MIN(yoff, (y + height) - corners[7]->h - corners[4]->h); dst->transBlitFrom(*corners[4], Common::Point((x + width) - corners[4]->w, ydraw)); } dst->transBlitFrom(*corners[0], Common::Point(x, y)); dst->transBlitFrom(*corners[2], Common::Point((x + width) - corners[2]->w, y)); dst->transBlitFrom(*corners[5], Common::Point(x, (y + height) - corners[5]->h)); dst->transBlitFrom(*corners[7], Common::Point((x + width) - corners[7]->w, (y + height) - corners[7]->h)); } /*static*/ void RequestData::drawHeader(Graphics::ManagedSurface *dst, int16 x, int16 y, int16 width, int16 yoffset, const Common::String &header, byte fontCol, bool drawBox, byte boxTopColor, byte boxBottomColor) { if (!header.empty()) { const DgdsFont *font = getMenuFont(); int hwidth = font->getStringWidth(header); int hheight = font->getFontHeight(); int hleft = x + (width - hwidth) / 2; int hright = hleft + hwidth + 3; int htop = y + yoffset; int hbottom = htop + hheight; font->drawString(dst, header, hleft + 1, htop + 2, hwidth, fontCol); if (drawBox) { dst->hLine(hleft - 3, htop, hright, boxTopColor); dst->vLine(hright, htop + 1, hbottom, boxTopColor); dst->vLine(hleft - 3, htop + 1, hbottom, boxBottomColor); dst->hLine(hleft - 2, hbottom, hleft + hwidth + 2, boxBottomColor); } } } void RequestData::drawBackgroundWithSliderArea(Graphics::ManagedSurface *dst, int16 sliderHeight, const Common::String &header) const { int16 x = _rect.x; int16 y = _rect.y; int16 width = _rect.width; int16 height = _rect.height; uint16 sliderBgHeight = sliderHeight + 18; fillBackground(dst, x, y, width, sliderBgHeight, 0); fillBackground(dst, x + 8, y + sliderBgHeight, width - 16, height - sliderBgHeight, 8 - sliderBgHeight); fillBackground(dst, x + 9, y + 8, width - 18, sliderHeight + 2, 8); fillBackground(dst, x + 17, y + 8 + sliderHeight + 2, width - 34, height - sliderBgHeight, 32 - sliderBgHeight); const Image *uiCorners = RequestData::getCorners(); assert(uiCorners->loadedFrameCount() >= 11); const Common::Array> &corners = uiCorners->getFrames(); for (int xoff = x + corners[0]->w; xoff < (x + width) - corners[3]->w; xoff += corners[2]->w) { dst->transBlitFrom(*corners[2], Common::Point(xoff, y)); } for (int xoff = x + 8 + corners[6]->w; xoff < (x + 8 + width - 16) - corners[8]->w; xoff += corners[7]->w) { dst->transBlitFrom(*corners[7], Common::Point(xoff, (y + height) - corners[7]->h)); } for (int yoff = y + corners[3]->h; yoff < (y + sliderBgHeight) - corners[10]->h; yoff += corners[5]->h) { dst->transBlitFrom(*corners[5], Common::Point((x + width) - corners[5]->w, yoff)); } for (int yoff = y + corners[1]->h; yoff < (y + sliderBgHeight) - corners[9]->h; yoff += corners[4]->h) { dst->transBlitFrom(*corners[4], Common::Point(x, yoff)); } for (int yoff = y + sliderBgHeight; yoff < (y + height) - corners[6]->h; yoff += corners[4]->h) { dst->transBlitFrom(*corners[4], Common::Point(x + 8, yoff)); } for (int yoff = y + sliderBgHeight; yoff < (y + height) - corners[8]->h; yoff += corners[5]->h) { dst->transBlitFrom(*corners[5], Common::Point((x + 8 + width - 16) - corners[5]->w, yoff)); } dst->transBlitFrom(*corners[1], Common::Point(x, y)); dst->transBlitFrom(*corners[3], Common::Point((x + width) - corners[3]->w, y)); dst->transBlitFrom(*corners[6], Common::Point(x + 8, (y + height) - corners[6]->h)); dst->transBlitFrom(*corners[8], Common::Point((x + width - 8) - corners[8]->w, (y + height) - corners[8]->h)); dst->transBlitFrom(*corners[9], Common::Point(x, (y + sliderBgHeight) - corners[9]->h)); dst->transBlitFrom(*corners[10], Common::Point((x + width) - corners[10]->w, (y + sliderBgHeight) - corners[10]->h)); if (DgdsEngine::getInstance()->getGameId() == GID_DRAGON) drawHeader(dst, x, y, width, 9, header, DragonHeaderTxtColor, true, DragonHeaderTopColor, DragonHeaderBottomColor); else drawHeader(dst, x, y + 4, width, 9, header, ChinaHeaderTxtColor, true, ChinaHeaderTopColor, ChinaHeaderBottomColor); } void RequestData::drawBackgroundNoSliders(Graphics::ManagedSurface *dst, const Common::String &header) const { DgdsGameId gameId = DgdsEngine::getInstance()->getGameId(); if (_rect.width == 0 || _rect.height == 0) { warning("drawBackgroundNoSliders: empty rect"); return; } if (gameId != GID_WILLY) fillBackground(dst, _rect.x, _rect.y, _rect.width, _rect.height, 0); else fillBackground(dst, _rect.x + 5, _rect.y + 5, _rect.width - 10, _rect.height - 10, 0); uint16 cornerOffset = (gameId == GID_DRAGON ? 11 : (gameId == GID_HOC ? 1 : 0)); drawCorners(dst, cornerOffset, _rect.x, _rect.y, _rect.width, _rect.height); if (gameId == GID_DRAGON) drawHeader(dst, _rect.x, _rect.y, _rect.width, 4, header, DragonHeaderTxtColor, true, DragonHeaderTopColor, DragonHeaderBottomColor); else if (gameId == GID_HOC) drawHeader(dst, _rect.x, _rect.y + 4, _rect.width, 4, header, ChinaHeaderTxtColor, true, ChinaHeaderTopColor, ChinaHeaderBottomColor); else { // WILLY drawHeader(dst, _rect.x, _rect.y + 4, _rect.width, 4, header, WillyHeaderTxtColor, false, 0, 0); } } /*static*/ void RequestData::fillBackground(Graphics::ManagedSurface *dst, uint16 x, uint16 y, uint16 width, uint16 height, int16 startoffset) { DgdsEngine *engine = DgdsEngine::getInstance(); if (engine->getGameId() == GID_DRAGON && engine->getDetailLevel() == kDgdsDetailHigh && !engine->isEGA()) { Graphics::Surface area = dst->getSubArea(Common::Rect(Common::Point(x, y), width, height)); while (startoffset < 0) startoffset += ARRAYSIZE(MenuBackgroundColors); startoffset = startoffset % ARRAYSIZE(MenuBackgroundColors); int coloffset = startoffset; for (int xoff = 0; xoff < width; xoff++) { area.drawLine(xoff, 0, xoff + height, height, MenuBackgroundColors[coloffset]); coloffset++; if (coloffset >= ARRAYSIZE(MenuBackgroundColors)) coloffset = 0; } // TODO: Game positions mouse in middle of menu here? coloffset = startoffset; for (int yoff = 0; yoff < height; yoff++) { area.drawLine(0, yoff, height, yoff + height, MenuBackgroundColors[coloffset]); coloffset--; if (coloffset < 0) coloffset = ARRAYSIZE(MenuBackgroundColors) - 1; } } else { byte bgCol = DragonBackgroundColor; if (engine->getGameId() == GID_HOC) bgCol = ChinaBackgroundColor; else if (engine->getGameId() == GID_WILLY) bgCol = WillyBackgroundColor; dst->fillRect(Common::Rect(Common::Point(x, y), width, height), bgCol); } } Gadget *RequestData::findGadgetByNumWithFlags3Not0x40(int16 num) { for (auto &gadget : _gadgets) { if (gadget->_gadgetNo == num && (gadget->_flags3 & 0x40) == 0) return gadget.get(); } return nullptr; } Common::String REQFileData::dump() const { Common::String ret("REQFileData<\n"); for (const auto &req : _requests) { ret += req.dump().c_str(); ret += "\n"; } ret += ">"; return ret; } } // End of namespace Dgds