Files
scummvm/engines/dgds/request.cpp
T
Matthew Duggan 395469773b DGDS: Ignore space as well as empty str in inventory objects
Most versions of the game have empty strings in the inventory request data, but
the DE version of Heart of China has a single space. Treat it the same as empty
string. This fixes #15841.
2025-04-04 20:05:32 +11:00

1142 lines
37 KiB
C++

/* 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 <http://www.gnu.org/licenses/>.
*
*/
#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<Gadget> &gptr : data._gadgets) {
uint16 vals[12];
for (int i = 0; i < 12; i++)
vals[i] = str->readUint16LE();
GadgetType gadgetType = static_cast<GadgetType>(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<TextAreaGadget *>(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<SliderGadget *>(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<ImageGadget *>(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<REQFileData *>(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<num %d pos (%d,%d) sz (%d,%d), typ %d, flgs %04x %04x svals %s, %s, '%s', parent (%d,%d)>",
_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<file %d %s c1 %d c2 %d flg %d\n",
_fileNum, _rect.dump("").c_str(), _col1, _col2, _flags);
for (const auto &t : _textItemList)
ret += Common::String::format(" TextItem<'%s' pos (%d,%d) %d %d>\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<x %d y %d w %d h %d c1 %d c2 %d>\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<const SliderGadget *>(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<Common::SharedPtr<Graphics::ManagedSurface>> &cframes = uiCorners->getFrames();
const Common::SharedPtr<Graphics::ManagedSurface> *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<Common::SharedPtr<Graphics::ManagedSurface>> &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