mirror of
https://github.com/libretro/scummvm.git
synced 2024-12-13 12:39:56 +00:00
1142 lines
25 KiB
C++
1142 lines
25 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/system.h"
|
|
#include "common/file.h"
|
|
#include "common/textconsole.h"
|
|
#include "graphics/paletteman.h"
|
|
#include "graphics/primitives.h"
|
|
#include "engines/util.h"
|
|
|
|
#include "parallaction/input.h"
|
|
#include "parallaction/parallaction.h"
|
|
|
|
|
|
namespace Parallaction {
|
|
|
|
// this is the size of the receiving buffer for unpacked frames,
|
|
// since BRA uses some insanely big animations (the largest is
|
|
// part0/ani/dino.ani).
|
|
#define MAXIMUM_UNPACKED_BITMAP_SIZE 641*401
|
|
|
|
|
|
#define LABEL_TRANSPARENT_COLOR 0xFF
|
|
|
|
void halfbritePixel(int x, int y, int color, void *data) {
|
|
Graphics::Surface *surf = (Graphics::Surface *)data;
|
|
byte *pixel = (byte *)surf->getBasePtr(x, y);
|
|
*pixel &= ~0x20;
|
|
}
|
|
|
|
void drawCircleLine(int xCenter, int yCenter, int x, int y, int color, void (*plotProc)(int, int, int, void *), void *data){
|
|
Graphics::drawLine(xCenter + x, yCenter + y, xCenter - x, yCenter + y, color, plotProc, data);
|
|
Graphics::drawLine(xCenter + x, yCenter - y, xCenter - x, yCenter - y, color, plotProc, data);
|
|
Graphics::drawLine(xCenter + y, yCenter + x, xCenter - y, yCenter + x, color, plotProc, data);
|
|
Graphics::drawLine(xCenter + y, yCenter - x, xCenter - y, yCenter - x, color, plotProc, data);
|
|
}
|
|
|
|
void drawCircle(int xCenter, int yCenter, int radius, int color, void (*plotProc)(int, int, int, void *), void *data) {
|
|
int x = 0;
|
|
int y = radius;
|
|
int p = 1 - radius;
|
|
|
|
/* Plot first set of points */
|
|
drawCircleLine(xCenter, yCenter, x, y, color, plotProc, data);
|
|
|
|
while (x < y) {
|
|
x++;
|
|
if (p < 0)
|
|
p += 2*x + 1;
|
|
else {
|
|
y--;
|
|
p += 2 * (x-y) + 1;
|
|
}
|
|
drawCircleLine(xCenter, yCenter, x, y, color, plotProc, data);
|
|
}
|
|
}
|
|
|
|
|
|
|
|
|
|
Palette::Palette() {
|
|
|
|
int gameType = g_vm->getGameType();
|
|
|
|
if (gameType == GType_Nippon) {
|
|
_colors = 32;
|
|
_hb = (g_vm->getPlatform() == Common::kPlatformAmiga);
|
|
} else
|
|
if (gameType == GType_BRA) {
|
|
_colors = 256;
|
|
_hb = false;
|
|
} else
|
|
error("can't create palette for id = '%i'", gameType);
|
|
|
|
_size = _colors * 3;
|
|
|
|
makeBlack();
|
|
}
|
|
|
|
Palette::Palette(const Palette &pal) {
|
|
clone(pal);
|
|
}
|
|
|
|
void Palette::clone(const Palette &pal) {
|
|
_colors = pal._colors;
|
|
_hb = pal._hb;
|
|
_size = pal._size;
|
|
memcpy(_data, pal._data, _size);
|
|
}
|
|
|
|
|
|
void Palette::makeBlack() {
|
|
memset(_data, 0, _size);
|
|
}
|
|
|
|
void Palette::setEntry(uint index, int red, int green, int blue) {
|
|
assert(index < _colors);
|
|
|
|
if (red >= 0)
|
|
_data[index*3] = red & 0xFF;
|
|
|
|
if (green >= 0)
|
|
_data[index*3+1] = green & 0xFF;
|
|
|
|
if (blue >= 0)
|
|
_data[index*3+2] = blue & 0xFF;
|
|
}
|
|
|
|
void Palette::getEntry(uint index, int &red, int &green, int &blue) {
|
|
assert(index < _colors);
|
|
red = _data[index*3];
|
|
green = _data[index*3+1];
|
|
blue = _data[index*3+2];
|
|
}
|
|
|
|
void Palette::makeGrayscale() {
|
|
byte v;
|
|
for (uint16 i = 0; i < _colors; i++) {
|
|
v = MAX(_data[i*3+1], _data[i*3+2]);
|
|
v = MAX(v, _data[i*3]);
|
|
setEntry(i, v, v, v);
|
|
}
|
|
}
|
|
|
|
void Palette::fadeTo(const Palette& target, uint step) {
|
|
|
|
if (step == 0)
|
|
return;
|
|
|
|
for (uint16 i = 0; i < _size; i++) {
|
|
if (_data[i] == target._data[i]) continue;
|
|
|
|
if (_data[i] < target._data[i])
|
|
_data[i] = CLIP(_data[i] + (int)step, (int)0, (int)target._data[i]);
|
|
else
|
|
_data[i] = CLIP(_data[i] - (int)step, (int)target._data[i], (int)255);
|
|
}
|
|
|
|
return;
|
|
}
|
|
|
|
uint Palette::fillRGB(byte *rgb) {
|
|
|
|
byte r, g, b;
|
|
byte *hbPal = rgb + _colors * 3;
|
|
|
|
for (uint32 i = 0; i < _colors; i++) {
|
|
r = (_data[i*3] << 2) | (_data[i*3] >> 4);
|
|
g = (_data[i*3+1] << 2) | (_data[i*3+1] >> 4);
|
|
b = (_data[i*3+2] << 2) | (_data[i*3+2] >> 4);
|
|
|
|
rgb[i*3] = r;
|
|
rgb[i*3+1] = g;
|
|
rgb[i*3+2] = b;
|
|
|
|
if (_hb) {
|
|
hbPal[i*3] = r >> 1;
|
|
hbPal[i*3+1] = g >> 1;
|
|
hbPal[i*3+2] = b >> 1;
|
|
}
|
|
|
|
}
|
|
|
|
return ((_hb) ? 2 : 1) * _colors;
|
|
}
|
|
|
|
void Palette::rotate(uint first, uint last, bool forward) {
|
|
|
|
byte tmp[3];
|
|
|
|
if (forward) { // forward
|
|
|
|
tmp[0] = _data[first * 3];
|
|
tmp[1] = _data[first * 3 + 1];
|
|
tmp[2] = _data[first * 3 + 2];
|
|
|
|
memmove(_data+first*3, _data+(first+1)*3, (last - first)*3);
|
|
|
|
_data[last * 3] = tmp[0];
|
|
_data[last * 3 + 1] = tmp[1];
|
|
_data[last * 3 + 2] = tmp[2];
|
|
|
|
} else { // backward
|
|
|
|
tmp[0] = _data[last * 3];
|
|
tmp[1] = _data[last * 3 + 1];
|
|
tmp[2] = _data[last * 3 + 2];
|
|
|
|
memmove(_data+(first+1)*3, _data+first*3, (last - first)*3);
|
|
|
|
_data[first * 3] = tmp[0];
|
|
_data[first * 3 + 1] = tmp[1];
|
|
_data[first * 3 + 2] = tmp[2];
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
void Gfx::setPalette(Palette &pal) {
|
|
byte sysPal[256*3];
|
|
|
|
uint n = pal.fillRGB(sysPal);
|
|
_vm->_system->getPaletteManager()->setPalette(sysPal, 0, n);
|
|
}
|
|
|
|
void Gfx::setBlackPalette() {
|
|
Palette pal;
|
|
setPalette(pal);
|
|
}
|
|
|
|
|
|
|
|
|
|
void Gfx::animatePalette() {
|
|
|
|
// avoid forcing setPalette when not needed
|
|
bool done = false;
|
|
|
|
PaletteFxRange *range;
|
|
for (uint16 i = 0; i < 4; i++) {
|
|
range = &_backgroundInfo->ranges[i];
|
|
|
|
if ((range->_flags & 1) == 0) continue; // animated palette
|
|
range->_timer += range->_step * 2; // update timer
|
|
|
|
if (range->_timer < 0x4000) continue; // check timeout
|
|
|
|
range->_timer = 0; // reset timer
|
|
|
|
_palette.rotate(range->_first, range->_last, (range->_flags & 2) != 0);
|
|
|
|
done = true;
|
|
}
|
|
|
|
if (done) {
|
|
setPalette(_palette);
|
|
}
|
|
|
|
return;
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
void Gfx::setHalfbriteMode(bool enable) {
|
|
if (_vm->getPlatform() != Common::kPlatformAmiga) return;
|
|
if (enable == _halfbrite) return;
|
|
|
|
_halfbrite = !_halfbrite;
|
|
}
|
|
|
|
#define HALFBRITE_CIRCLE_RADIUS 48
|
|
void Gfx::setProjectorPos(int x, int y) {
|
|
_hbCircleRadius = HALFBRITE_CIRCLE_RADIUS;
|
|
_hbCirclePos.x = x + _hbCircleRadius;
|
|
_hbCirclePos.y = y + _hbCircleRadius;
|
|
}
|
|
|
|
void Gfx::setProjectorProgram(int16 *data) {
|
|
if (_nextProjectorPos == nullptr) {
|
|
_nextProjectorPos = data;
|
|
}
|
|
}
|
|
|
|
void Gfx::drawInventory() {
|
|
/*
|
|
if ((_engineFlags & kEngineInventory) == 0) {
|
|
return;
|
|
}
|
|
*/
|
|
if (_vm->_input->_inputMode != Input::kInputModeInventory) {
|
|
return;
|
|
}
|
|
|
|
Common::Rect r;
|
|
_vm->_inventoryRenderer->getRect(r);
|
|
byte *data = _vm->_inventoryRenderer->getData();
|
|
|
|
copyRectToScreen(data, r.width(), r.left, r.top, r.width(), r.height());
|
|
}
|
|
|
|
void Gfx::drawList(Graphics::Surface &surface, GfxObjArray &list) {
|
|
if (list.size() == 0) {
|
|
return;
|
|
}
|
|
|
|
for (uint i = 0; i < list.size(); i++) {
|
|
drawGfxObject(list[i], surface);
|
|
}
|
|
}
|
|
|
|
void Gfx::copyRectToScreen(const byte *buf, int pitch, int x, int y, int w, int h) {
|
|
if (_doubleBuffering) {
|
|
if (_overlayMode) {
|
|
x += _scrollPosX;
|
|
y += _scrollPosY;
|
|
}
|
|
|
|
byte *dst = (byte *)_backBuffer.getBasePtr(x, y);
|
|
for (int i = 0; i < h; i++) {
|
|
memcpy(dst, buf, w);
|
|
buf += pitch;
|
|
dst += _backBuffer.pitch;
|
|
}
|
|
} else {
|
|
_vm->_system->copyRectToScreen(buf, pitch, x, y, w, h);
|
|
}
|
|
}
|
|
|
|
void Gfx::clearScreen() {
|
|
if (_doubleBuffering) {
|
|
if (_backBuffer.getPixels()) {
|
|
Common::Rect r(_backBuffer.w, _backBuffer.h);
|
|
_backBuffer.fillRect(r, 0);
|
|
}
|
|
} else {
|
|
_vm->_system->fillScreen(0);
|
|
}
|
|
}
|
|
|
|
Graphics::Surface *Gfx::lockScreen() {
|
|
if (_doubleBuffering) {
|
|
return &_backBuffer;
|
|
}
|
|
return _vm->_system->lockScreen();
|
|
}
|
|
|
|
void Gfx::unlockScreen() {
|
|
if (_doubleBuffering) {
|
|
return;
|
|
}
|
|
_vm->_system->unlockScreen();
|
|
}
|
|
|
|
void Gfx::updateScreenIntern() {
|
|
if (_doubleBuffering) {
|
|
byte *data = (byte *)_backBuffer.getBasePtr(_scrollPosX, _scrollPosY);
|
|
_vm->_system->copyRectToScreen(data, _backBuffer.pitch, 0, 0, _vm->_screenWidth, _vm->_screenHeight);
|
|
}
|
|
|
|
_vm->_system->updateScreen();
|
|
}
|
|
|
|
void Gfx::getScrollPos(Common::Point &p) {
|
|
p.x = _scrollPosX;
|
|
p.y = _scrollPosY;
|
|
}
|
|
|
|
void Gfx::setScrollPosX(int scrollX) {
|
|
_scrollPosX = CLIP(scrollX, _minScrollX, _maxScrollX);
|
|
}
|
|
|
|
void Gfx::setScrollPosY(int scrollY) {
|
|
_scrollPosY = CLIP(scrollY, _minScrollY, _maxScrollY);
|
|
}
|
|
|
|
void Gfx::initiateScroll(int deltaX, int deltaY) {
|
|
if (deltaX != 0) {
|
|
_requestedHScrollDir = deltaX > 0 ? 1 : -1;
|
|
deltaX *= _requestedHScrollDir;
|
|
_requestedHScrollSteps = ((deltaX+31)/32) / _requestedHScrollDir;
|
|
}
|
|
|
|
if (deltaY != 0) {
|
|
_requestedVScrollDir = deltaY > 0 ? 1 : -1;
|
|
deltaY *= _requestedVScrollDir;
|
|
_requestedVScrollSteps = ((deltaY+7)/8) / _requestedVScrollDir;
|
|
}
|
|
}
|
|
|
|
void Gfx::scroll() {
|
|
int32 x = _scrollPosX, y = _scrollPosY;
|
|
|
|
if (_requestedHScrollSteps) {
|
|
x += 32*_requestedHScrollDir; // scroll 32 pixels at a time
|
|
_requestedHScrollSteps--;
|
|
}
|
|
|
|
if (_requestedVScrollSteps) {
|
|
y += 8*_requestedVScrollDir; // scroll 8 pixel at a time
|
|
_requestedVScrollSteps--;
|
|
}
|
|
|
|
setScrollPosX(x);
|
|
setScrollPosY(y);
|
|
}
|
|
|
|
void Gfx::beginFrame() {
|
|
resetSceneDrawList();
|
|
scroll();
|
|
}
|
|
|
|
void Gfx::updateScreen() {
|
|
|
|
// the scene is calculated in game coordinates, so no translation
|
|
// is needed
|
|
_overlayMode = false;
|
|
|
|
bool skipBackground = (_backgroundInfo->bg.getPixels() == nullptr); // don't render frame if background is missing
|
|
|
|
if (!skipBackground) {
|
|
// background may not cover the whole screen, so adjust bulk update size
|
|
uint w = _backgroundInfo->width;
|
|
uint h = _backgroundInfo->height;
|
|
byte *backgroundData = (byte *)_backgroundInfo->bg.getPixels();
|
|
uint16 backgroundPitch = _backgroundInfo->bg.pitch;
|
|
copyRectToScreen(backgroundData, backgroundPitch, _backgroundInfo->_x, _backgroundInfo->_y, w, h);
|
|
}
|
|
|
|
sortScene();
|
|
Graphics::Surface *surf = lockScreen();
|
|
// draws animations frames and other game items
|
|
drawList(*surf, _sceneObjects);
|
|
|
|
// special effects
|
|
applyHalfbriteEffect_NS(*surf);
|
|
|
|
// draws inventory, labels and dialogue items
|
|
drawOverlay(*surf);
|
|
unlockScreen();
|
|
|
|
updateScreenIntern();
|
|
}
|
|
|
|
void Gfx::applyHalfbriteEffect_NS(Graphics::Surface &surf) {
|
|
if (!_halfbrite) {
|
|
return;
|
|
}
|
|
|
|
byte *buf = (byte *)surf.getPixels();
|
|
for (int i = 0; i < surf.w*surf.h; i++) {
|
|
*buf++ |= 0x20;
|
|
}
|
|
|
|
if (_nextProjectorPos) {
|
|
int16 x = *_nextProjectorPos;
|
|
int16 y = *(_nextProjectorPos + 1);
|
|
if (x != -1 && y != -1) {
|
|
_nextProjectorPos += 2;
|
|
setProjectorPos(x, y);
|
|
}
|
|
}
|
|
if (_hbCircleRadius > 0) {
|
|
drawCircle(_hbCirclePos.x, _hbCirclePos.y, _hbCircleRadius, 0, &halfbritePixel, &surf);
|
|
}
|
|
}
|
|
|
|
void Gfx::drawOverlay(Graphics::Surface &surf) {
|
|
// the following items are handled in screen coordinates, so they need translation before
|
|
// being drawn
|
|
_overlayMode = true;
|
|
|
|
drawInventory();
|
|
|
|
updateFloatingLabel();
|
|
|
|
drawList(surf, _items);
|
|
drawList(surf, _balloons);
|
|
drawList(surf, _labels);
|
|
}
|
|
|
|
//
|
|
// graphic primitives
|
|
//
|
|
|
|
|
|
void Gfx::patchBackground(Graphics::Surface &surf, int16 x, int16 y, bool mask) {
|
|
|
|
Common::Rect r(surf.w, surf.h);
|
|
r.moveTo(x, y);
|
|
|
|
uint16 z = (mask) ? _backgroundInfo->getMaskLayer(y) : LAYER_FOREGROUND;
|
|
blt(r, (byte *)surf.getPixels(), &_backgroundInfo->bg, z, 100, 0);
|
|
}
|
|
|
|
void Gfx::fillBackground(const Common::Rect& r, byte color) {
|
|
_backgroundInfo->bg.fillRect(r, color);
|
|
}
|
|
|
|
void Gfx::invertBackground(const Common::Rect& r) {
|
|
|
|
byte *d = (byte *)_backgroundInfo->bg.getBasePtr(r.left, r.top);
|
|
|
|
for (int i = 0; i < r.height(); i++) {
|
|
for (int j = 0; j < r.width(); j++) {
|
|
*d ^= 0x1F;
|
|
d++;
|
|
}
|
|
|
|
d += (_backgroundInfo->bg.pitch - r.width());
|
|
}
|
|
|
|
}
|
|
|
|
|
|
void setupLabelSurface(Graphics::Surface &surf, uint w, uint h) {
|
|
surf.create(w, h, Graphics::PixelFormat::createFormatCLUT8());
|
|
surf.fillRect(Common::Rect(w,h), LABEL_TRANSPARENT_COLOR);
|
|
}
|
|
|
|
GfxObj *Gfx::renderFloatingLabel(Font *font, char *text) {
|
|
|
|
Graphics::Surface *cnv = new Graphics::Surface;
|
|
|
|
uint w, h;
|
|
if (_vm->getPlatform() == Common::kPlatformAmiga) {
|
|
w = font->getStringWidth(text) + 16;
|
|
h = font->height() + 2;
|
|
|
|
setupLabelSurface(*cnv, w, h);
|
|
|
|
font->setColor((_gameType == GType_BRA) ? 0 : 7);
|
|
font->drawString(cnv, 1, 0, text);
|
|
font->drawString(cnv, 1, 2, text);
|
|
font->drawString(cnv, 0, 1, text);
|
|
font->drawString(cnv, 2, 1, text);
|
|
font->setColor((_gameType == GType_BRA) ? 11 : 1);
|
|
font->drawString(cnv, 1, 1, text);
|
|
} else {
|
|
w = font->getStringWidth(text);
|
|
h = font->height();
|
|
|
|
setupLabelSurface(*cnv, w, h);
|
|
|
|
drawText(font, cnv, 0, 0, text, 0);
|
|
}
|
|
|
|
GfxObj *obj = new GfxObj(kGfxObjTypeLabel, new SurfaceToFrames(cnv), "floatingLabel");
|
|
obj->transparentKey = LABEL_TRANSPARENT_COLOR;
|
|
obj->layer = LAYER_FOREGROUND;
|
|
|
|
return obj;
|
|
}
|
|
|
|
void Gfx::showFloatingLabel(GfxObj *label) {
|
|
hideFloatingLabel();
|
|
|
|
if (label) {
|
|
label->x = -1000;
|
|
label->y = -1000;
|
|
label->setFlags(kGfxObjVisible);
|
|
|
|
_floatingLabel = label;
|
|
_labels.push_back(label);
|
|
}
|
|
}
|
|
|
|
void Gfx::hideFloatingLabel() {
|
|
if (_floatingLabel != nullptr) {
|
|
_floatingLabel->clearFlags(kGfxObjVisible);
|
|
}
|
|
_floatingLabel = nullptr;
|
|
}
|
|
|
|
|
|
void Gfx::updateFloatingLabel() {
|
|
if (_floatingLabel == nullptr) {
|
|
return;
|
|
}
|
|
|
|
struct FloatingLabelTraits {
|
|
Common::Point _offsetWithItem;
|
|
Common::Point _offsetWithoutItem;
|
|
int _minX;
|
|
int _minY;
|
|
int _maxX;
|
|
int _maxY;
|
|
} *traits;
|
|
|
|
Common::Rect r;
|
|
_floatingLabel->getRect(0, r);
|
|
|
|
FloatingLabelTraits traits_NS = {
|
|
Common::Point(16 - r.width()/2, 34),
|
|
Common::Point(8 - r.width()/2, 21),
|
|
0, 0, _vm->_screenWidth - r.width(), 190
|
|
};
|
|
|
|
// FIXME: _maxY for BRA is not constant (390), but depends on _vm->_subtitleY
|
|
FloatingLabelTraits traits_BR = {
|
|
Common::Point(34 - r.width()/2, 70),
|
|
Common::Point(16 - r.width()/2, 37),
|
|
0, 0, _vm->_screenWidth - r.width(), 390
|
|
};
|
|
|
|
if (_gameType == GType_Nippon) {
|
|
traits = &traits_NS;
|
|
} else {
|
|
traits = &traits_BR;
|
|
}
|
|
|
|
Common::Point cursor;
|
|
_vm->_input->getCursorPos(cursor);
|
|
Common::Point offset = (_vm->_input->_activeItem._id) ? traits->_offsetWithItem : traits->_offsetWithoutItem;
|
|
|
|
_floatingLabel->x = CLIP(cursor.x + offset.x, traits->_minX, traits->_maxX);
|
|
_floatingLabel->y = CLIP(cursor.y + offset.y, traits->_minY, traits->_maxY);
|
|
}
|
|
|
|
|
|
|
|
|
|
GfxObj *Gfx::createLabel(Font *font, const char *text, byte color) {
|
|
Graphics::Surface *cnv = new Graphics::Surface;
|
|
|
|
uint w, h;
|
|
|
|
if (_vm->getPlatform() == Common::kPlatformAmiga) {
|
|
w = font->getStringWidth(text) + 2;
|
|
h = font->height() + 2;
|
|
|
|
setupLabelSurface(*cnv, w, h);
|
|
|
|
drawText(font, cnv, 0, 2, text, 0);
|
|
drawText(font, cnv, 2, 0, text, color);
|
|
} else {
|
|
w = font->getStringWidth(text);
|
|
h = font->height();
|
|
|
|
setupLabelSurface(*cnv, w, h);
|
|
|
|
drawText(font, cnv, 0, 0, text, color);
|
|
}
|
|
|
|
GfxObj *obj = new GfxObj(kGfxObjTypeLabel, new SurfaceToFrames(cnv), "label");
|
|
obj->transparentKey = LABEL_TRANSPARENT_COLOR;
|
|
obj->layer = LAYER_FOREGROUND;
|
|
|
|
return obj;
|
|
}
|
|
|
|
void Gfx::showLabel(GfxObj *label, int16 x, int16 y) {
|
|
if (!label) {
|
|
return;
|
|
}
|
|
|
|
label->setFlags(kGfxObjVisible);
|
|
|
|
Common::Rect r;
|
|
label->getRect(0, r);
|
|
|
|
if (x == CENTER_LABEL_HORIZONTAL) {
|
|
x = CLIP<int16>((_backgroundInfo->width - r.width()) / 2, 0, _backgroundInfo->width/2);
|
|
}
|
|
|
|
if (y == CENTER_LABEL_VERTICAL) {
|
|
y = CLIP<int16>((_vm->_screenHeight - r.height()) / 2, 0, _vm->_screenHeight/2);
|
|
}
|
|
|
|
label->x = x;
|
|
label->y = y;
|
|
|
|
_labels.push_back(label);
|
|
}
|
|
|
|
void Gfx::hideLabel(GfxObj *label) {
|
|
if (label) {
|
|
label->clearFlags(kGfxObjVisible);
|
|
unregisterLabel(label);
|
|
}
|
|
}
|
|
|
|
void Gfx::freeLabels() {
|
|
_labels.clear();
|
|
_floatingLabel = nullptr;
|
|
}
|
|
|
|
void Gfx::unregisterLabel(GfxObj *label) {
|
|
for (uint i = 0; i < _labels.size(); i++) {
|
|
if (_labels[i] == label) {
|
|
_labels.remove_at(i);
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
void Gfx::copyRect(const Common::Rect &r, Graphics::Surface &src, Graphics::Surface &dst) {
|
|
|
|
byte *s = (byte *)src.getBasePtr(r.left, r.top);
|
|
byte *d = (byte *)dst.getPixels();
|
|
|
|
for (uint16 i = 0; i < r.height(); i++) {
|
|
memcpy(d, s, r.width());
|
|
|
|
s += src.pitch;
|
|
d += dst.pitch;
|
|
}
|
|
|
|
return;
|
|
}
|
|
|
|
void Gfx::grabBackground(const Common::Rect& r, Graphics::Surface &dst) {
|
|
copyRect(r, _backgroundInfo->bg, dst);
|
|
}
|
|
|
|
|
|
Gfx::Gfx(Parallaction* vm) :
|
|
_vm(vm), _disk(vm->_disk), _backgroundInfo(nullptr),
|
|
_scrollPosX(0), _scrollPosY(0),_minScrollX(0), _maxScrollX(0),
|
|
_minScrollY(0), _maxScrollY(0),
|
|
_requestedHScrollSteps(0), _requestedVScrollSteps(0),
|
|
_requestedHScrollDir(0), _requestedVScrollDir(0) {
|
|
|
|
_gameType = _vm->getGameType();
|
|
_doubleBuffering = _gameType != GType_Nippon;
|
|
|
|
initGraphics(_vm->_screenWidth, _vm->_screenHeight);
|
|
|
|
setPalette(_palette);
|
|
|
|
_floatingLabel = nullptr;
|
|
|
|
_backgroundInfo = nullptr;
|
|
|
|
_halfbrite = false;
|
|
_nextProjectorPos = nullptr;
|
|
_hbCircleRadius = 0;
|
|
|
|
_overlayMode = false;
|
|
|
|
_unpackedBitmap = new byte[MAXIMUM_UNPACKED_BITMAP_SIZE];
|
|
assert(_unpackedBitmap);
|
|
|
|
if ((_gameType == GType_BRA) && (_vm->getPlatform() == Common::kPlatformDOS)) {
|
|
// this loads the backup palette needed by the PC version of BRA (see setBackground()).
|
|
BackgroundInfo paletteInfo;
|
|
_disk->loadSlide(paletteInfo, "pointer");
|
|
_backupPal.clone(paletteInfo.palette);
|
|
}
|
|
|
|
resetSceneDrawList();
|
|
|
|
return;
|
|
}
|
|
|
|
Gfx::~Gfx() {
|
|
|
|
_backBuffer.free();
|
|
|
|
delete _backgroundInfo;
|
|
|
|
freeLabels();
|
|
|
|
delete[] _unpackedBitmap;
|
|
|
|
return;
|
|
}
|
|
|
|
|
|
|
|
int Gfx::setItem(GfxObj* frames, uint16 x, uint16 y, byte transparentColor) {
|
|
int id = _items.size();
|
|
|
|
frames->x = x;
|
|
frames->y = y;
|
|
frames->transparentKey = transparentColor;
|
|
frames->layer = LAYER_FOREGROUND;
|
|
frames->setFlags(kGfxObjVisible);
|
|
|
|
_items.insert_at(id, frames);
|
|
|
|
setItemFrame(id, 0);
|
|
|
|
return id;
|
|
}
|
|
|
|
void Gfx::setItemFrame(uint item, uint16 f) {
|
|
_items[item]->frame = f;
|
|
}
|
|
|
|
|
|
GfxObj* Gfx::registerBalloon(Frames *frames, const char *text) {
|
|
|
|
GfxObj *obj = new GfxObj(kGfxObjTypeBalloon, frames, text);
|
|
|
|
obj->layer = LAYER_FOREGROUND;
|
|
obj->frame = 0;
|
|
obj->setFlags(kGfxObjVisible);
|
|
|
|
_balloons.push_back(obj);
|
|
|
|
return obj;
|
|
}
|
|
|
|
void Gfx::freeDialogueObjects() {
|
|
_items.clear();
|
|
|
|
_vm->_balloonMan->reset();
|
|
|
|
for (uint i = 0; i < _balloons.size(); i++) {
|
|
delete _balloons[i];
|
|
}
|
|
_balloons.clear();
|
|
}
|
|
|
|
void Gfx::setBackground(uint type, BackgroundInfo *info) {
|
|
if (!info) {
|
|
warning("Gfx::setBackground() called with a null BackgroundInfo");
|
|
return;
|
|
}
|
|
|
|
_hbCircleRadius = 0;
|
|
_nextProjectorPos = nullptr;
|
|
|
|
delete _backgroundInfo;
|
|
_backgroundInfo = info;
|
|
|
|
if (type == kBackgroundLocation) {
|
|
// The PC version of BRA needs the entries 20-31 of the palette to be constant, but
|
|
// the background resource files are screwed up. The right colors come from an unused
|
|
// bitmap (pointer.bmp). Nothing is known about the Amiga version so far.
|
|
if ((_gameType == GType_BRA) && (_vm->getPlatform() == Common::kPlatformDOS)) {
|
|
int r, g, b;
|
|
for (uint i = 16; i < 32; i++) {
|
|
_backupPal.getEntry(i, r, g, b);
|
|
_backgroundInfo->palette.setEntry(i, r, g, b);
|
|
}
|
|
}
|
|
|
|
setPalette(_backgroundInfo->palette);
|
|
_palette.clone(_backgroundInfo->palette);
|
|
} else {
|
|
for (uint i = 0; i < 6; i++)
|
|
_backgroundInfo->ranges[i]._flags = 0; // disable palette cycling for slides
|
|
setPalette(_backgroundInfo->palette);
|
|
}
|
|
|
|
_backgroundInfo->finalizeMask();
|
|
_backgroundInfo->finalizePath();
|
|
|
|
if (_gameType == GType_BRA) {
|
|
int width = MAX(info->width, (int)_vm->_screenWidth);
|
|
int height = MAX(info->height, (int)_vm->_screenHeight);
|
|
|
|
if (width != _backBuffer.w || height != _backBuffer.h) {
|
|
_backBuffer.create(width, height, Graphics::PixelFormat::createFormatCLUT8());
|
|
}
|
|
}
|
|
|
|
_minScrollX = 0;
|
|
_maxScrollX = MAX<int>(0, _backgroundInfo->width - _vm->_screenWidth);
|
|
_minScrollY = 0;
|
|
_maxScrollY = MAX<int>(0, _backgroundInfo->height - _vm->_screenHeight);
|
|
}
|
|
|
|
|
|
BackgroundInfo::BackgroundInfo() : _x(0), _y(0), width(0), height(0), _mask(nullptr), _path(nullptr) {
|
|
layers[0] = layers[1] = layers[2] = layers[3] = 0;
|
|
memset(ranges, 0, sizeof(ranges));
|
|
}
|
|
|
|
BackgroundInfo::~BackgroundInfo() {
|
|
bg.free();
|
|
clearMaskData();
|
|
clearPathData();
|
|
}
|
|
|
|
bool BackgroundInfo::hasMask() {
|
|
return _mask != nullptr;
|
|
}
|
|
|
|
void BackgroundInfo::clearMaskData() {
|
|
// free mask data
|
|
MaskPatches::iterator it = _maskPatches.begin();
|
|
for ( ; it != _maskPatches.end(); ++it) {
|
|
delete *it;
|
|
}
|
|
_maskPatches.clear();
|
|
delete _mask;
|
|
_mask = nullptr;
|
|
_maskBackup.free();
|
|
}
|
|
|
|
void BackgroundInfo::finalizeMask() {
|
|
if (_mask) {
|
|
if (_maskPatches.size() > 0) {
|
|
// since no more patches can be added after finalization,
|
|
// avoid creating the backup if there is none
|
|
_maskBackup.clone(*_mask);
|
|
}
|
|
} else {
|
|
clearMaskData();
|
|
}
|
|
}
|
|
|
|
uint BackgroundInfo::addMaskPatch(MaskBuffer *patch) {
|
|
uint id = _maskPatches.size();
|
|
_maskPatches.push_back(patch);
|
|
return id;
|
|
}
|
|
|
|
void BackgroundInfo::toggleMaskPatch(uint id, int x, int y, bool apply) {
|
|
if (!hasMask()) {
|
|
return;
|
|
}
|
|
if (id >= _maskPatches.size()) {
|
|
return;
|
|
}
|
|
MaskBuffer *patch = _maskPatches[id];
|
|
if (apply) {
|
|
_mask->bltOr(x, y, *patch, 0, 0, patch->w, patch->h);
|
|
} else {
|
|
_mask->bltCopy(x, y, _maskBackup, x, y, patch->w, patch->h);
|
|
}
|
|
}
|
|
|
|
uint16 BackgroundInfo::getMaskLayer(uint16 z) const {
|
|
for (uint16 i = 0; i < 3; i++) {
|
|
if (layers[i+1] > z) return i;
|
|
}
|
|
return LAYER_FOREGROUND;
|
|
}
|
|
|
|
void BackgroundInfo::setPaletteRange(int index, const PaletteFxRange& range) {
|
|
assert(index < 6);
|
|
memcpy(&ranges[index], &range, sizeof(PaletteFxRange));
|
|
}
|
|
|
|
bool BackgroundInfo::hasPath() {
|
|
return _path != nullptr;
|
|
}
|
|
|
|
void BackgroundInfo::clearPathData() {
|
|
// free mask data
|
|
PathPatches::iterator it = _pathPatches.begin();
|
|
for ( ; it != _pathPatches.end(); ++it) {
|
|
delete *it;
|
|
}
|
|
_pathPatches.clear();
|
|
delete _path;
|
|
_path = nullptr;
|
|
_pathBackup.free();
|
|
}
|
|
|
|
void BackgroundInfo::finalizePath() {
|
|
if (_path) {
|
|
if (_pathPatches.size() > 0) {
|
|
// since no more patches can be added after finalization,
|
|
// avoid creating the backup if there is none
|
|
_pathBackup.clone(*_path);
|
|
}
|
|
} else {
|
|
clearPathData();
|
|
}
|
|
}
|
|
|
|
uint BackgroundInfo::addPathPatch(PathBuffer *patch) {
|
|
uint id = _pathPatches.size();
|
|
_pathPatches.push_back(patch);
|
|
return id;
|
|
}
|
|
|
|
void BackgroundInfo::togglePathPatch(uint id, int x, int y, bool apply) {
|
|
if (!hasPath()) {
|
|
return;
|
|
}
|
|
if (id >= _pathPatches.size()) {
|
|
return;
|
|
}
|
|
PathBuffer *patch = _pathPatches[id];
|
|
if (apply) {
|
|
_path->bltCopy(x, y, *patch, 0, 0, patch->w, patch->h);
|
|
} else {
|
|
_path->bltCopy(x, y, _pathBackup, x, y, patch->w, patch->h);
|
|
}
|
|
}
|
|
|
|
MaskBuffer::MaskBuffer() : w(0), internalWidth(0), h(0), size(0), data(nullptr), bigEndian(true) {
|
|
}
|
|
|
|
MaskBuffer::~MaskBuffer() {
|
|
free();
|
|
}
|
|
|
|
byte* MaskBuffer::getPtr(uint16 x, uint16 y) const {
|
|
return data + (x >> 2) + y * internalWidth;
|
|
}
|
|
|
|
void MaskBuffer::clone(const MaskBuffer &buf) {
|
|
if (!buf.data)
|
|
return;
|
|
|
|
create(buf.w, buf.h);
|
|
bigEndian = buf.bigEndian;
|
|
memcpy(data, buf.data, size);
|
|
}
|
|
|
|
void MaskBuffer::create(uint16 width, uint16 height) {
|
|
free();
|
|
|
|
w = width;
|
|
internalWidth = w >> 2;
|
|
h = height;
|
|
size = (internalWidth * h);
|
|
data = (byte *)calloc(size, 1);
|
|
}
|
|
|
|
void MaskBuffer::free() {
|
|
::free(data);
|
|
data = nullptr;
|
|
w = 0;
|
|
h = 0;
|
|
internalWidth = 0;
|
|
size = 0;
|
|
}
|
|
|
|
byte MaskBuffer::getValue(uint16 x, uint16 y) const {
|
|
byte m = data[(x >> 2) + y * internalWidth];
|
|
uint n;
|
|
if (bigEndian) {
|
|
n = (x & 3) << 1;
|
|
} else {
|
|
n = (3 - (x & 3)) << 1;
|
|
}
|
|
return (m >> n) & 3;
|
|
}
|
|
|
|
void MaskBuffer::bltOr(uint16 dx, uint16 dy, const MaskBuffer &src, uint16 sx, uint16 sy, uint width, uint height) {
|
|
assert((width <= w) && (width <= src.w) && (height <= h) && (height <= src.h));
|
|
|
|
byte *s = src.getPtr(sx, sy);
|
|
byte *d = getPtr(dx, dy);
|
|
|
|
// this code assumes buffers are aligned on 4-pixels boundaries, as the original does
|
|
uint16 linewidth = width >> 2;
|
|
for (uint16 i = 0; i < height; i++) {
|
|
for (uint16 j = 0; j < linewidth; j++) {
|
|
*d++ |= *s++;
|
|
}
|
|
d += internalWidth - linewidth;
|
|
s += src.internalWidth - linewidth;
|
|
}
|
|
}
|
|
|
|
void MaskBuffer::bltCopy(uint16 dx, uint16 dy, const MaskBuffer &src, uint16 sx, uint16 sy, uint width, uint height) {
|
|
assert((width <= w) && (width <= src.w) && (height <= h) && (height <= src.h));
|
|
|
|
byte *s = src.getPtr(sx, sy);
|
|
byte *d = getPtr(dx, dy);
|
|
|
|
// this code assumes buffers are aligned on 4-pixels boundaries, as the original does
|
|
for (uint16 i = 0; i < height; i++) {
|
|
memcpy(d, s, (width >> 2));
|
|
d += internalWidth;
|
|
s += src.internalWidth;
|
|
}
|
|
}
|
|
|
|
|
|
|
|
PathBuffer::PathBuffer() : w(0), internalWidth(0), h(0), size(0), data(nullptr), bigEndian(true) {
|
|
}
|
|
|
|
PathBuffer::~PathBuffer() {
|
|
free();
|
|
}
|
|
|
|
void PathBuffer::clone(const PathBuffer &buf) {
|
|
if (!buf.data)
|
|
return;
|
|
|
|
create(buf.w, buf.h);
|
|
bigEndian = buf.bigEndian;
|
|
memcpy(data, buf.data, size);
|
|
}
|
|
|
|
void PathBuffer::create(uint16 width, uint16 height) {
|
|
free();
|
|
|
|
w = width;
|
|
internalWidth = w >> 3;
|
|
h = height;
|
|
size = (internalWidth * h);
|
|
data = (byte *)calloc(size, 1);
|
|
}
|
|
|
|
void PathBuffer::free() {
|
|
::free(data);
|
|
data = nullptr;
|
|
w = 0;
|
|
h = 0;
|
|
internalWidth = 0;
|
|
size = 0;
|
|
}
|
|
|
|
byte PathBuffer::getValue(uint16 x, uint16 y) const {
|
|
byte m = 0;
|
|
if (data) {
|
|
uint index = (x >> 3) + y * internalWidth;
|
|
if (index < size)
|
|
m = data[index];
|
|
else
|
|
warning("PathBuffer::getValue(x: %d, y: %d) outside of data buffer of size %d", x, y, size);
|
|
} else
|
|
warning("PathBuffer::getValue() attempted to use NULL data buffer");
|
|
uint bit = bigEndian ? (x & 7) : (7 - (x & 7));
|
|
return ((1 << bit) & m) >> bit;
|
|
}
|
|
|
|
byte* PathBuffer::getPtr(uint16 x, uint16 y) const {
|
|
return data + (x >> 3) + y * internalWidth;
|
|
}
|
|
|
|
void PathBuffer::bltCopy(uint16 dx, uint16 dy, const PathBuffer &src, uint16 sx, uint16 sy, uint width, uint height) {
|
|
assert((width <= w) && (width <= src.w) && (height <= h) && (height <= src.h));
|
|
|
|
byte *s = src.getPtr(sx, sy);
|
|
byte *d = getPtr(dx, dy);
|
|
|
|
// this code assumes buffers are aligned on 4-pixels boundaries, as the original does
|
|
for (uint16 i = 0; i < height; i++) {
|
|
memcpy(d, s, (width >> 3));
|
|
d += internalWidth;
|
|
s += src.internalWidth;
|
|
}
|
|
}
|
|
|
|
} // namespace Parallaction
|