mirror of
https://github.com/scummvm/scummvm.git
synced 2026-06-20 05:45:29 +00:00
Currently, buffers get allocated that do not get freed. Apparently, the reason is that it is not well known when it is actually safe to do that. This here should hopefully fix the situation. Or at least give some inspiration on how to fix it...
4770 lines
132 KiB
C++
4770 lines
132 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 "scumm/actor.h"
|
|
#include "scumm/charset.h"
|
|
#ifdef ENABLE_HE
|
|
#include "scumm/he/intern_he.h"
|
|
#endif
|
|
#include "scumm/resource.h"
|
|
#include "scumm/scumm_v0.h"
|
|
#include "scumm/scumm_v5.h"
|
|
#include "scumm/scumm_v6.h"
|
|
#include "scumm/usage_bits.h"
|
|
#include "scumm/he/wiz_he.h"
|
|
#include "scumm/util.h"
|
|
|
|
#ifdef USE_ARM_GFX_ASM
|
|
|
|
#ifndef IPHONE
|
|
#define asmDrawStripToScreen _asmDrawStripToScreen
|
|
#define asmCopy8Col _asmCopy8Col
|
|
#endif
|
|
|
|
extern "C" void asmDrawStripToScreen(int height, int width, void const* text, void const* src, byte* dst,
|
|
int vsPitch, int vmScreenWidth, int textSurfacePitch);
|
|
extern "C" void asmCopy8Col(byte* dst, int dstPitch, const byte* src, int height, uint8 bitDepth);
|
|
#endif /* USE_ARM_GFX_ASM */
|
|
|
|
namespace Scumm {
|
|
|
|
static void blit(byte *dst, int dstPitch, const byte *src, int srcPitch, int w, int h, uint8 bitDepth);
|
|
static void fill(byte *dst, int dstPitch, uint16 color, int w, int h, uint8 bitDepth);
|
|
#ifndef USE_ARM_GFX_ASM
|
|
static void copy8Col(byte *dst, int dstPitch, const byte *src, int height, uint8 bitDepth);
|
|
#endif
|
|
static void clear8Col(byte *dst, int dstPitch, int height, uint8 bitDepth);
|
|
|
|
struct StripTable {
|
|
int offsets[160];
|
|
int run[160];
|
|
int color[160];
|
|
int zoffsets[120]; // FIXME: Why only 120 here?
|
|
int zrun[120]; // FIXME: Why only 120 here?
|
|
};
|
|
|
|
static const byte bitMasks[9] = { 0x00, 0x01, 0x03, 0x07, 0x0F, 0x1F, 0x3F, 0x7F, 0xFF };
|
|
|
|
enum {
|
|
kNoDelay = 0,
|
|
// This should actually be 3 in all games using it;
|
|
// every one of those games seems to accumulate some
|
|
// kind of internal random delay while performing
|
|
// the screen effect (like sound interrupts running,
|
|
// forcing the SCUMM timer to a lower frequency).
|
|
// I have added an extra quarter frame to emulate that.
|
|
kPictureDelay = 4,
|
|
kC64Delay = 6
|
|
};
|
|
|
|
#define NUM_SHAKE_POSITIONS 8
|
|
static const int8 shake_positions[NUM_SHAKE_POSITIONS] = {
|
|
0, 1, 2, 1, 0, 2, 3, 1
|
|
};
|
|
|
|
/**
|
|
* The following structs define four basic fades/transitions used by
|
|
* transitionEffect(), each looking differently to the user.
|
|
* Note that the stripTables contain strip numbers, and they assume
|
|
* that the screen has 40 vertical strips (i.e. 320 pixel), and 25 horizontal
|
|
* strips (i.e. 200 pixel). There is a hack in transitionEffect that
|
|
* makes it work correctly in games which have a different screen height
|
|
* (for example, 240 pixel), but nothing is done regarding the width.
|
|
* The number of iterations is hardcoded from version 3 up to 7.
|
|
* Version 8 doesn't have any of these effects at all.
|
|
*/
|
|
struct TransitionEffect {
|
|
byte numOfIterations;
|
|
int8 deltaTable[16]; // four times l / t / r / b
|
|
byte stripTable[16]; // ditto
|
|
};
|
|
|
|
static const TransitionEffect transitionEffects[6] = {
|
|
// Iris effect (looks like an opening/closing camera iris)
|
|
{
|
|
13, // Number of iterations
|
|
{
|
|
1, 1, -1, 1,
|
|
-1, 1, -1, -1,
|
|
1, -1, -1, -1,
|
|
1, 1, 1, -1
|
|
},
|
|
{
|
|
0, 0, 39, 0,
|
|
39, 0, 39, 24,
|
|
0, 24, 39, 24,
|
|
0, 0, 0, 24
|
|
}
|
|
},
|
|
|
|
// Box wipe (a box expands from the upper-left corner to the lower-right corner)
|
|
{
|
|
25, // Number of iterations
|
|
{
|
|
0, 1, 2, 1,
|
|
2, 0, 2, 1,
|
|
2, 0, 2, 1,
|
|
0, 0, 0, 0
|
|
},
|
|
{
|
|
0, 0, 0, 0,
|
|
0, 0, 0, 0,
|
|
1, 0, 1, 0,
|
|
255, 0, 0, 0
|
|
}
|
|
},
|
|
|
|
// Box wipe (a box expands from the lower-right corner to the upper-left corner)
|
|
{
|
|
25, // Number of iterations
|
|
{
|
|
-2, -1, 0, -1,
|
|
-2, -1, -2, 0,
|
|
-2, -1, -2, 0,
|
|
0, 0, 0, 0
|
|
},
|
|
{
|
|
39, 24, 39, 24,
|
|
39, 24, 39, 24,
|
|
38, 24, 38, 24,
|
|
255, 0, 0, 0
|
|
}
|
|
},
|
|
|
|
// Inverse box wipe
|
|
{
|
|
25, // Number of iterations
|
|
{
|
|
0, -1, -2, -1,
|
|
-2, 0, -2, -1,
|
|
-2, 0, -2, -1,
|
|
0, 0, 0, 0
|
|
},
|
|
{
|
|
0, 24, 39, 24,
|
|
39, 0, 39, 24,
|
|
38, 0, 38, 24,
|
|
255, 0, 0, 0
|
|
}
|
|
},
|
|
|
|
// Inverse iris effect, specially tailored for V1/V2 games
|
|
{
|
|
9, // Number of iterations
|
|
{
|
|
-1, -1, 1, -1,
|
|
-1, 1, 1, 1,
|
|
-1, -1, -1, 1,
|
|
1, -1, 1, 1
|
|
},
|
|
{
|
|
7, 7, 32, 7,
|
|
7, 8, 32, 8,
|
|
7, 8, 7, 8,
|
|
32, 7, 32, 8
|
|
}
|
|
},
|
|
|
|
// Horizontal wipe (a box expands from left to right side). For MM NES
|
|
{
|
|
16, // Number of iterations
|
|
{
|
|
2, 0, 2, 0,
|
|
2, 0, 2, 0,
|
|
0, 0, 0, 0,
|
|
0, 0, 0, 0
|
|
},
|
|
{
|
|
0, 0, 0, 15,
|
|
1, 0, 1, 15,
|
|
255, 0, 0, 0,
|
|
255, 0, 0, 0
|
|
}
|
|
}
|
|
|
|
};
|
|
|
|
|
|
Gdi::Gdi(ScummEngine *vm) : _vm(vm) {
|
|
_numZBuffer = 0;
|
|
memset(_imgBufOffs, 0, sizeof(_imgBufOffs));
|
|
_numStrips = 0;
|
|
|
|
_paletteMod = 0;
|
|
_roomPalette = vm->_roomPalette;
|
|
_transparentColor = 255;
|
|
_decomp_shr = 0;
|
|
_decomp_mask = 0;
|
|
_vertStripNextInc = 0;
|
|
_zbufferDisabled = false;
|
|
_objectMode = false;
|
|
_distaff = false;
|
|
}
|
|
|
|
Gdi::~Gdi() {
|
|
}
|
|
|
|
GdiHE::GdiHE(ScummEngine *vm) : Gdi(vm), _tmskPtr(nullptr) {
|
|
}
|
|
|
|
|
|
GdiNES::GdiNES(ScummEngine *vm) : Gdi(vm) {
|
|
memset(&_NES, 0, sizeof(_NES));
|
|
}
|
|
|
|
#ifdef USE_RGB_COLOR
|
|
GdiPCEngine::GdiPCEngine(ScummEngine *vm) : Gdi(vm) {
|
|
memset(&_PCE, 0, sizeof(_PCE));
|
|
}
|
|
|
|
GdiPCEngine::~GdiPCEngine() {
|
|
free(_PCE.roomTiles);
|
|
free(_PCE.staffTiles);
|
|
free(_PCE.masks);
|
|
}
|
|
#endif
|
|
|
|
GdiV1::GdiV1(ScummEngine *vm) : Gdi(vm) {
|
|
memset(&_V1, 0, sizeof(_V1));
|
|
}
|
|
|
|
void GdiV1::setRenderModeColorMap(const byte *map) {
|
|
_colorMap = map;
|
|
}
|
|
|
|
byte GdiV1::remapColorToRenderMode(byte col) const {
|
|
assert(_colorMap);
|
|
return _colorMap[col];
|
|
}
|
|
|
|
GdiV2::GdiV2(ScummEngine *vm) : Gdi(vm) {
|
|
_roomStrips = nullptr;
|
|
}
|
|
|
|
GdiV2::~GdiV2() {
|
|
free(_roomStrips);
|
|
}
|
|
|
|
#ifdef USE_RGB_COLOR
|
|
GdiHE16bit::GdiHE16bit(ScummEngine *vm) : GdiHE(vm) {
|
|
}
|
|
#endif
|
|
|
|
void Gdi::init() {
|
|
_numStrips = _vm->_screenWidth / 8;
|
|
|
|
// Increase the number of screen strips by one; needed for smooth scrolling
|
|
if (_vm->_game.version >= 7) {
|
|
// We now have mostly working smooth scrolling code in place for V7+ games
|
|
// (i.e. The Dig, Full Throttle and COMI). It seems to work very well so far.
|
|
//
|
|
// To understand how we achieve smooth scrolling, first note that with it, the
|
|
// virtual screen strips don't match the display screen strips anymore. To
|
|
// overcome that problem, we simply use a screen pitch that is 8 pixel wider
|
|
// than the actual screen width, and always draw one strip more than needed to
|
|
// the backbuf (thus we have to treat the right border separately).
|
|
_numStrips += 1;
|
|
}
|
|
}
|
|
|
|
void Gdi::roomChanged(byte *roomptr) {
|
|
}
|
|
|
|
void GdiNES::roomChanged(byte *roomptr) {
|
|
decodeNESGfx(roomptr);
|
|
}
|
|
|
|
#ifdef USE_RGB_COLOR
|
|
void GdiPCEngine::roomChanged(byte *roomptr) {
|
|
decodePCEngineGfx(roomptr);
|
|
}
|
|
#endif
|
|
|
|
void Gdi::loadTiles(byte *roomptr) {
|
|
}
|
|
|
|
#ifdef USE_RGB_COLOR
|
|
void GdiPCEngine::loadTiles(byte *roomptr) {
|
|
decodePCEngineTileData(_vm->findResourceData(MKTAG('T','I','L','E'), roomptr));
|
|
}
|
|
#endif
|
|
|
|
void GdiV1::roomChanged(byte *roomptr) {
|
|
for (int i = 0; i < 4; i++){
|
|
_V1.colors[i] = roomptr[6 + i];
|
|
}
|
|
decodeV1Gfx(roomptr + READ_LE_UINT16(roomptr + 10), _V1.charMap, 2048);
|
|
decodeV1Gfx(roomptr + READ_LE_UINT16(roomptr + 12), _V1.picMap, roomptr[4] * roomptr[5]);
|
|
decodeV1Gfx(roomptr + READ_LE_UINT16(roomptr + 14), _V1.colorMap, roomptr[4] * roomptr[5]);
|
|
decodeV1Gfx(roomptr + READ_LE_UINT16(roomptr + 16), _V1.maskMap, roomptr[4] * roomptr[5]);
|
|
|
|
// Read the mask data. The 16bit length value seems to always be 8 too big.
|
|
// See bug #3458 for details on this.
|
|
const byte *maskPtr = roomptr + READ_LE_UINT16(roomptr + 18);
|
|
decodeV1Gfx(maskPtr + 2, _V1.maskChar, READ_LE_UINT16(maskPtr) - 8);
|
|
_objectMode = true;
|
|
}
|
|
|
|
void GdiV2::roomChanged(byte *roomptr) {
|
|
_roomStrips = generateStripTable(roomptr + READ_LE_UINT16(roomptr + 0x0A),
|
|
_vm->_roomWidth, _vm->_roomHeight, _roomStrips);
|
|
}
|
|
|
|
#pragma mark -
|
|
#pragma mark --- Virtual Screens ---
|
|
#pragma mark -
|
|
|
|
|
|
void ScummEngine::initScreens(int b, int h) {
|
|
int i;
|
|
int adj = 0;
|
|
|
|
for (i = 0; i < 4; i++) {
|
|
_res->nukeResource(rtBuffer, i + 1);
|
|
_res->nukeResource(rtBuffer, i + 5);
|
|
}
|
|
|
|
#ifndef DISABLE_TOWNS_DUAL_LAYER_MODE
|
|
if (_townsScreen) {
|
|
if (!_townsClearLayerFlag && (h - b != _virtscr[kMainVirtScreen].h))
|
|
_townsScreen->clearLayer(0);
|
|
|
|
if (_game.id != GID_MONKEY) {
|
|
_textSurface.fillRect(Common::Rect(0, 0, _textSurface.w * _textSurfaceMultiplier, _textSurface.h * _textSurfaceMultiplier), 0);
|
|
_townsScreen->clearLayer(1);
|
|
}
|
|
}
|
|
#endif
|
|
|
|
if (_macScreen) {
|
|
_macScreen->fillRect(Common::Rect(_macScreen->w, _macScreen->h), 0);
|
|
clearTextSurface();
|
|
}
|
|
|
|
if (_game.version >= 7) {
|
|
initVirtScreen(kBannerVirtScreen, (_screenHeight / 2) - 10, _screenWidth, 13, false, false);
|
|
} else if (_game.platform == Common::kPlatformFMTowns) {
|
|
// HACK: The original only ever renders in 640x480 mode. The banners' top and bottom borders are exactly one unscaled pixel high. This will
|
|
// still allow the text to fit in nicely. It does not work with scaled (2 pixel height) borders, though. So we add two extra pixels...
|
|
int bannerHeight = (_textSurfaceMultiplier == 1) ? 12 : 20;
|
|
initVirtScreen(kBannerVirtScreen, (b + adj + h) / 2 - bannerHeight / _textSurfaceMultiplier, _screenWidth * _textSurfaceMultiplier, bannerHeight, false, false);
|
|
} else {
|
|
initVirtScreen(kBannerVirtScreen, 80, _screenWidth, 12, false, false);
|
|
}
|
|
|
|
if ((_game.platform == Common::kPlatformNES) && (h != _screenHeight)) {
|
|
// This is a hack to shift the whole screen downwards to match the original.
|
|
// Otherwise we would have to do lots of coordinate adjustments all over
|
|
// the code.
|
|
adj = 16;
|
|
initVirtScreen(kBannerVirtScreen, 0, _screenWidth, adj, false, false);
|
|
}
|
|
|
|
initVirtScreen(kMainVirtScreen, b + adj, _screenWidth, h - b, true, true);
|
|
initVirtScreen(kTextVirtScreen, adj, _screenWidth, b, false, false);
|
|
initVirtScreen(kVerbVirtScreen, h + adj, _screenWidth, _screenHeight - h - adj, false, false);
|
|
_screenB = b;
|
|
_screenH = h;
|
|
|
|
_gdi->init();
|
|
|
|
#ifndef DISABLE_TOWNS_DUAL_LAYER_MODE
|
|
// The right most visible strip for FM-Towns smooth scrolling
|
|
_scrollFeedStrips[0] = _gdi->_numStrips - 1;
|
|
#endif
|
|
}
|
|
|
|
void ScummEngine::initVirtScreen(VirtScreenNumber slot, int top, int width, int height, bool twobufs,
|
|
bool scrollable) {
|
|
VirtScreen *vs = &_virtscr[slot];
|
|
int size;
|
|
|
|
assert(height >= 0);
|
|
assert((int)slot >= 0 && (int)slot < 4);
|
|
|
|
if (_game.version >= 7) {
|
|
if (slot == kMainVirtScreen && (_roomHeight != 0))
|
|
height = _roomHeight;
|
|
}
|
|
|
|
vs->number = slot;
|
|
vs->w = width;
|
|
vs->topline = top;
|
|
vs->h = height;
|
|
vs->hasTwoBuffers = twobufs;
|
|
vs->xstart = 0;
|
|
vs->backBuf = nullptr;
|
|
if (_game.features & GF_16BIT_COLOR)
|
|
vs->format = Graphics::PixelFormat(2, 5, 5, 5, 0, 10, 5, 0, 0);
|
|
else
|
|
vs->format = Graphics::PixelFormat::createFormatCLUT8();
|
|
vs->pitch = width * vs->format.bytesPerPixel;
|
|
|
|
if (_game.version >= 7) {
|
|
// Increase the pitch by one; needed to accommodate the extra screen
|
|
// strip which we use to implement smooth scrolling. See Gdi::init().
|
|
vs->pitch += 8;
|
|
}
|
|
|
|
size = vs->pitch * vs->h;
|
|
if (scrollable) {
|
|
// Allow enough spaces so that rooms can be up to 4 resp. 8 screens
|
|
// wide. To achieve horizontal scrolling, SCUMM uses a neat trick:
|
|
// only the offset into the screen buffer (xstart) is changed. That way
|
|
// very little of the screen has to be redrawn, and we have a very low
|
|
// memory overhead (namely for every pixel we want to scroll, we need
|
|
// one additional byte in the buffer).
|
|
if (_game.version >= 7) {
|
|
size += vs->pitch * 8;
|
|
} else {
|
|
size += vs->pitch * 4;
|
|
}
|
|
}
|
|
|
|
_res->createResource(rtBuffer, slot + 1, size);
|
|
vs->setPixels(getResourceAddress(rtBuffer, slot + 1));
|
|
if (_game.platform == Common::kPlatformNES)
|
|
memset(vs->getBasePtr(0, 0), 0x1d, size); // reset background (MM NES)
|
|
else
|
|
memset(vs->getBasePtr(0, 0), 0, size); // reset background
|
|
|
|
if (twobufs) {
|
|
vs->backBuf = _res->createResource(rtBuffer, slot + 5, size);
|
|
}
|
|
|
|
if (slot != 3) {
|
|
vs->setDirtyRange(0, height);
|
|
}
|
|
}
|
|
|
|
VirtScreen *ScummEngine::findVirtScreen(int y) {
|
|
VirtScreen *vs = _virtscr;
|
|
int i;
|
|
|
|
if (_forceBannerVirtScreen)
|
|
return &vs[3];
|
|
|
|
for (i = 0; i < 3; i++, vs++) {
|
|
if (y >= vs->topline && y < vs->topline + vs->h) {
|
|
return vs;
|
|
}
|
|
}
|
|
return nullptr;
|
|
}
|
|
|
|
void ScummEngine::markRectAsDirty(VirtScreenNumber virt, int left, int right, int top, int bottom, int dirtybit) {
|
|
VirtScreen *vs = &_virtscr[virt];
|
|
int lp, rp;
|
|
|
|
if (left > right || top > bottom)
|
|
return;
|
|
if (top > vs->h || bottom < 0)
|
|
return;
|
|
|
|
if (top < 0)
|
|
top = 0;
|
|
if (bottom > vs->h)
|
|
bottom = vs->h;
|
|
|
|
if (virt == kMainVirtScreen && dirtybit) {
|
|
|
|
lp = left / 8 + _screenStartStrip;
|
|
if (lp < 0)
|
|
lp = 0;
|
|
|
|
rp = (right + vs->xstart) / 8;
|
|
if (_game.version >= 7) {
|
|
if (rp > 409)
|
|
rp = 409;
|
|
} else {
|
|
if (rp >= 200)
|
|
rp = 200;
|
|
}
|
|
for (; lp <= rp; lp++)
|
|
setGfxUsageBit(lp, dirtybit);
|
|
}
|
|
|
|
// The following code used to be in the separate method setVirtscreenDirty
|
|
lp = left / 8;
|
|
rp = right / 8;
|
|
|
|
if ((lp >= _gdi->_numStrips) || (rp < 0))
|
|
return;
|
|
if (lp < 0)
|
|
lp = 0;
|
|
if (rp >= _gdi->_numStrips)
|
|
rp = _gdi->_numStrips - 1;
|
|
|
|
while (lp <= rp) {
|
|
if (top < vs->tdirty[lp])
|
|
vs->tdirty[lp] = top;
|
|
if (bottom > vs->bdirty[lp])
|
|
vs->bdirty[lp] = bottom;
|
|
lp++;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Update all dirty screen areas. This method blits all of the internal engine
|
|
* graphics to the actual display, as needed. In addition, the 'shaking'
|
|
* code in the backend is controlled from here.
|
|
*/
|
|
void ScummEngine::drawDirtyScreenParts() {
|
|
// Update verbs
|
|
updateDirtyScreen(kVerbVirtScreen);
|
|
|
|
// Update the conversation area (at the top of the screen)
|
|
updateDirtyScreen(kTextVirtScreen);
|
|
|
|
// Update game area ("stage")
|
|
if (camera._last.x != camera._cur.x || (_game.version >= 7 && (camera._cur.y != camera._last.y))) {
|
|
// Camera moved: redraw everything
|
|
VirtScreen *vs = &_virtscr[kMainVirtScreen];
|
|
drawStripToScreen(vs, 0, vs->w, 0, vs->h);
|
|
vs->setDirtyRange(vs->h, 0);
|
|
} else {
|
|
updateDirtyScreen(kMainVirtScreen);
|
|
}
|
|
}
|
|
|
|
void ScummEngine_v6::drawDirtyScreenParts() {
|
|
// For the Full Throttle credits to work properly, the blast
|
|
// texts have to be drawn before the blast objects. Unless
|
|
// someone can think of a better way to achieve this effect.
|
|
|
|
if (_game.version >= 7 && VAR(VAR_BLAST_ABOVE_TEXT) == 1) {
|
|
drawBlastTexts();
|
|
drawBlastObjects();
|
|
if (_game.version == 8) {
|
|
// Does this case ever happen? We need to draw the
|
|
// actor over the blast object, so we're forced to
|
|
// also draw it over the subtitles.
|
|
processUpperActors();
|
|
}
|
|
} else {
|
|
drawBlastObjects();
|
|
if (_game.version == 8) {
|
|
// Do this before drawing blast texts. Subtitles go on
|
|
// top of the CoMI verb coin, e.g. when Murray is
|
|
// talking to himself early in the game.
|
|
processUpperActors();
|
|
}
|
|
drawBlastTexts();
|
|
}
|
|
|
|
// Call the original method.
|
|
ScummEngine::drawDirtyScreenParts();
|
|
|
|
// Remove all blasted objects/text again, except
|
|
// for v7-8 which do that at a later time.
|
|
if (_game.version < 7) {
|
|
removeBlastTexts();
|
|
removeBlastObjects();
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Blit the dirty data from the given VirtScreen to the display. If the camera moved,
|
|
* a full blit is done, otherwise only the visible dirty areas are updated.
|
|
*/
|
|
void ScummEngine::updateDirtyScreen(VirtScreenNumber slot) {
|
|
VirtScreen *vs = &_virtscr[slot];
|
|
|
|
// Do nothing for unused virtual screens
|
|
if (vs->h == 0)
|
|
return;
|
|
|
|
int i;
|
|
int w = 8;
|
|
int start = 0;
|
|
|
|
for (i = 0; i < _gdi->_numStrips; i++) {
|
|
if (vs->bdirty[i]) {
|
|
const int top = vs->tdirty[i];
|
|
const int bottom = vs->bdirty[i];
|
|
vs->tdirty[i] = vs->h;
|
|
vs->bdirty[i] = 0;
|
|
if (i != (_gdi->_numStrips - 1) && vs->bdirty[i + 1] == bottom && vs->tdirty[i + 1] == top) {
|
|
// Simple optimizations: if two or more neighboring strips
|
|
// form one bigger rectangle, coalesce them.
|
|
w += 8;
|
|
continue;
|
|
}
|
|
#ifndef DISABLE_TOWNS_DUAL_LAYER_MODE
|
|
if (_game.platform == Common::kPlatformFMTowns && vs->number == kBannerVirtScreen) {
|
|
int scl = _textSurfaceMultiplier;
|
|
towns_drawStripToScreen(vs, start * 8 * scl, (vs->topline + top) * scl, start * 8 * scl, top * scl, w * scl, bottom - top);
|
|
} else
|
|
#endif
|
|
drawStripToScreen(vs, start * 8, w, top, bottom);
|
|
w = 8;
|
|
}
|
|
start = i + 1;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Blit the specified rectangle from the given virtual screen to the display.
|
|
* Note: t and b are in *virtual screen* coordinates, while x is relative to
|
|
* the *real screen*. This is due to the way tdirty/vdirty work: they are
|
|
* arrays which map 'strips' (sections of the real screen) to dirty areas as
|
|
* specified by top/bottom coordinate in the virtual screen.
|
|
*/
|
|
void ScummEngine::drawStripToScreen(VirtScreen *vs, int x, int width, int top, int bottom) {
|
|
|
|
// Short-circuit if nothing has to be drawn
|
|
if (bottom <= top || top >= vs->h)
|
|
return;
|
|
|
|
// Some paranoia checks
|
|
assert(top >= 0 && bottom <= vs->h);
|
|
assert(x >= 0 && width <= vs->pitch);
|
|
assert(_textSurface.getPixels());
|
|
|
|
// Some extra vertical alignment for certain render modes. It matters for MI1EGA. The dithering patterns require the alignment,
|
|
// otherwise there will be visible glitches. It can be found in the original interpreters.
|
|
int align = (_game.version > 2 && (_renderMode == Common::kRenderCGA || _renderMode == Common::kRenderHercG || _renderMode == Common::kRenderHercA)) ? 4 : (_enableEGADithering ? 2 : 1);
|
|
top &= ~(align - 1);
|
|
if (bottom & (align - 1))
|
|
bottom = (bottom + align) & ~(align - 1);
|
|
|
|
// Perform some clipping
|
|
if (width > vs->w - x)
|
|
width = vs->w - x;
|
|
if (top < _screenTop)
|
|
top = _screenTop;
|
|
if (bottom > _screenTop + _screenHeight)
|
|
bottom = _screenTop + _screenHeight;
|
|
|
|
// Convert the vertical coordinates to real screen coords
|
|
int y = vs->topline + top - _screenTop;
|
|
int height = bottom - top;
|
|
|
|
if (width <= 0 || height <= 0)
|
|
return;
|
|
|
|
if (_macScreen) {
|
|
mac_drawStripToScreen(vs, top, x, y, width, height);
|
|
return;
|
|
}
|
|
|
|
const void *src = vs->getPixels(x, top);
|
|
int m = _textSurfaceMultiplier;
|
|
int vsPitch;
|
|
int pitch = vs->pitch;
|
|
vsPitch = vs->pitch - width * vs->format.bytesPerPixel;
|
|
|
|
// In MM NES If we're repainting the entire screen, just make everything black
|
|
if ((_game.platform == Common::kPlatformNES) && width == 256 && height == 240) {
|
|
_system->fillScreen(0x1d);
|
|
return;
|
|
}
|
|
|
|
if (_game.version < 7) {
|
|
// For The Dig, FT and COMI, we just blit everything to the screen at once.
|
|
// For older games, things are more complicated. First off, we need to
|
|
// deal with the _textSurface, which needs to be composited over the
|
|
// screen contents. Secondly, a rendering mode might be active, which
|
|
// means a filter has to be applied.
|
|
|
|
// Compute pointer to the text surface
|
|
assert(_compositeBuf);
|
|
const void *text = _textSurface.getBasePtr(x * m, y * m);
|
|
|
|
// The values x, width, etc. are all multiples of 8 at this point,
|
|
// so loop unrolloing might be a good idea...
|
|
assert(IS_ALIGNED(text, 4));
|
|
assert(0 == (width & 3));
|
|
|
|
#ifndef DISABLE_TOWNS_DUAL_LAYER_MODE
|
|
if (_game.platform == Common::kPlatformFMTowns) {
|
|
towns_drawStripToScreen(vs, x, y, x, top, width, height);
|
|
return;
|
|
} else
|
|
#endif
|
|
// Compose the text over the game graphics
|
|
if (_outputPixelFormat.bytesPerPixel == 2) {
|
|
const byte *srcPtr = (const byte *)src;
|
|
const byte *textPtr = (byte *)_textSurface.getBasePtr(x * m, y * m);
|
|
byte *dstPtr = _compositeBuf;
|
|
|
|
for (int h = 0; h < height * m; ++h) {
|
|
for (int w = 0; w < width * m; ++w) {
|
|
uint16 tmp = *textPtr++;
|
|
if (tmp == CHARSET_MASK_TRANSPARENCY) {
|
|
tmp = READ_UINT16(srcPtr);
|
|
WRITE_UINT16(dstPtr, tmp); dstPtr += 2;
|
|
} else if (_game.heversion != 0) {
|
|
error ("16Bit Color HE Game using old charset");
|
|
} else {
|
|
WRITE_UINT16(dstPtr, _16BitPalette[tmp]); dstPtr += 2;
|
|
}
|
|
srcPtr += vs->format.bytesPerPixel;
|
|
}
|
|
srcPtr += vsPitch;
|
|
textPtr += _textSurface.pitch - width * m;
|
|
}
|
|
} else {
|
|
#ifdef USE_ARM_GFX_ASM
|
|
asmDrawStripToScreen(height, width, text, src, _compositeBuf, vs->pitch, width, _textSurface.pitch);
|
|
#else
|
|
// We blit four pixels at a time, for improved performance.
|
|
const uint32 *src32 = (const uint32 *)src;
|
|
uint32 *dst32 = (uint32 *)_compositeBuf;
|
|
|
|
vsPitch >>= 2;
|
|
|
|
const uint32 *text32 = (const uint32 *)text;
|
|
const int textPitch = (_textSurface.pitch - width * m) >> 2;
|
|
for (int h = height * m; h > 0; --h) {
|
|
for (int w = width * m; w > 0; w -= 4) {
|
|
uint32 temp = *text32++;
|
|
|
|
// Generate a byte mask for those text pixels (bytes) with
|
|
// value CHARSET_MASK_TRANSPARENCY. In the end, each byte
|
|
// in mask will be either equal to 0x00 or 0xFF.
|
|
// Doing it this way avoids branches and bytewise operations,
|
|
// at the cost of readability ;).
|
|
uint32 mask = temp ^ CHARSET_MASK_TRANSPARENCY_32;
|
|
mask = (((mask & 0x7f7f7f7f) + 0x7f7f7f7f) | mask) & 0x80808080;
|
|
mask = ((mask >> 7) + 0x7f7f7f7f) ^ 0x80808080;
|
|
|
|
// The following line is equivalent to this code:
|
|
// *dst32++ = (*src32++ & mask) | (temp & ~mask);
|
|
// However, some compilers can generate somewhat better
|
|
// machine code for this equivalent statement:
|
|
*dst32++ = ((temp ^ *src32++) & mask) ^ temp;
|
|
}
|
|
src32 += vsPitch;
|
|
text32 += textPitch;
|
|
}
|
|
#endif
|
|
}
|
|
src = _compositeBuf;
|
|
pitch = width * vs->format.bytesPerPixel;
|
|
|
|
if (_game.platform == Common::kPlatformNES) {
|
|
// HACK: This is dirty hack which renders narrow NES rooms centered
|
|
// NES can address negative number strips and that poses problem for
|
|
// our code. So instead of adding zillions of fixes and potentially
|
|
// breaking other games, we shift it right at the rendering stage.
|
|
if (((_NESStartStrip > 0) && (vs->number == kMainVirtScreen)) || (vs->number == kTextVirtScreen)) {
|
|
x += 16;
|
|
while (x + width >= _screenWidth)
|
|
width -= 16;
|
|
if (width <= 0)
|
|
return;
|
|
|
|
// HACK: In this way we won't get a screen with dirty side strips when
|
|
// loading a narrow room in a full screen room.
|
|
if (width == 224 && height == 240 && x == 16) {
|
|
char blackbuf[16 * 240];
|
|
memset(blackbuf, 0, 16 * 240); // Prepare a buffer 16px wide and 240px high, to fit on a lateral strip
|
|
|
|
width = 240; // Fix right strip
|
|
_system->copyRectToScreen(blackbuf, 16, 0, 0, 16, 240); // Fix left strip
|
|
}
|
|
}
|
|
} else if (_useCJKMode && m == 2) {
|
|
pitch *= m;
|
|
x *= m;
|
|
y *= m;
|
|
width *= m;
|
|
height *= m;
|
|
} else if (_enableEGADithering) {
|
|
// EGA mode for certain VGA versions (MI2, LOOM Talkie)
|
|
src = ditherVGAtoEGA(pitch, x, y, width, height);
|
|
} else if (_game.platform == Common::kPlatformDOS && _game.version < 5) {
|
|
// CGA and Hercules modes for MM/ZAK v1/v2, INDY3, LOOM, MI1EGA
|
|
src = postProcessDOSGraphics(vs, pitch, x, y, width, height);
|
|
}
|
|
}
|
|
|
|
// Finally blit the whole thing to the screen
|
|
_system->copyRectToScreen(src, pitch, x, y, width, height);
|
|
}
|
|
|
|
const byte *ScummEngine::postProcessDOSGraphics(VirtScreen *vs, int &pitch, int &x, int &y, int &width, int &height) const {
|
|
static const byte v2VrbColMap[] = { 0x0, 0x5, 0x5, 0x5, 0xA, 0xA, 0xA, 0xF, 0xF, 0x5, 0x5, 0x5, 0xA, 0xA, 0xF, 0xF };
|
|
static const byte v2TxtColMap[] = { 0x0, 0xF, 0xA, 0x5, 0xA, 0x5, 0x5, 0xF, 0xA, 0xA, 0xA, 0xA, 0xA, 0x5, 0x5, 0xF };
|
|
static const byte mmv1VrbColMap[] = { 0x0, 0x5, 0x5, 0x5, 0xA, 0xA, 0xA, 0xF, 0xA, 0x5, 0x5, 0x5, 0xA, 0xA, 0xA, 0xF };
|
|
static const byte v2MainColMap[] = { 0x0, 0x4, 0x1, 0x5, 0x8, 0xA, 0x2, 0xF, 0xC, 0x7, 0xD, 0x5, 0xE, 0xB, 0xD, 0xF };
|
|
|
|
static const byte v3MainColMap[] = {
|
|
0x0, 0x4, 0x1, 0x5, 0x8, 0xA, 0x2, 0x3, 0xC, 0x7, 0xD, 0x5, 0xF, 0xB, 0x5, 0xF, 0x0, 0x1, 0x4, 0x5, 0x2, 0xA, 0x8, 0xC, 0x3, 0xD, 0x5, 0x5, 0xF, 0xE, 0x5, 0xF
|
|
};
|
|
|
|
static const byte v4MainColMap[] = {
|
|
0x0, 0x4, 0x1, 0x5, 0x2, 0xA, 0x2, 0x3, 0x0, 0x5, 0x5, 0x7, 0xF, 0xE, 0x5, 0xF, 0x0, 0x1, 0x4, 0x5, 0x8, 0xA, 0x8, 0xC, 0x0, 0x7, 0x5, 0xD, 0xF, 0xB, 0x5, 0xF,
|
|
0x0, 0x4, 0x1, 0x5, 0x2, 0xA, 0x2, 0x3, 0x0, 0x5, 0x5, 0x7, 0xF, 0xE, 0x5, 0xF, 0x0, 0x1, 0x4, 0x5, 0x8, 0xA, 0x8, 0xC, 0x0, 0xD, 0x5, 0xD, 0xF, 0xB, 0x5, 0xF
|
|
};
|
|
|
|
static const byte hrcTableV4[32] = {
|
|
0x00, 0x08, 0xAA, 0xBB, 0x55, 0x66, 0x99, 0x7F, 0x11, 0x55, 0x77, 0xEE, 0xAA, 0xEE, 0xFF, 0xFF,
|
|
0x00, 0x80, 0xAA, 0xDD, 0x00, 0x99, 0x66, 0xF7, 0x44, 0xAA, 0xDD, 0x77, 0xFF, 0xBB, 0xBB, 0xFF
|
|
};
|
|
|
|
static const byte *mainColMap[] = { nullptr, nullptr, v2MainColMap, v3MainColMap, v4MainColMap };
|
|
|
|
byte tmpTxtColMap[16];
|
|
for (uint8 i = 0; i < ARRAYSIZE(tmpTxtColMap); ++i)
|
|
tmpTxtColMap[i] = mmv1VrbColMap[_gdi->remapColorToRenderMode(i)];
|
|
|
|
byte *res = _compositeBuf;
|
|
byte *dst = _compositeBuf;
|
|
const byte *src = res;
|
|
bool renderHerc = (_renderMode == Common::kRenderHercA || _renderMode == Common::kRenderHercG);
|
|
bool renderV1 = (_game.version == 1);
|
|
bool renderV3 = _game.version > 2;
|
|
|
|
if (!renderV1 && !renderHerc && _renderMode != Common::kRenderCGA)
|
|
return res;
|
|
|
|
const byte *colMap = (_game.id == GID_ZAK || _game.version == 2) ? ((vs->number == kVerbVirtScreen || renderHerc) ? v2VrbColMap : v2TxtColMap) : (vs->number == kVerbVirtScreen ? mmv1VrbColMap : tmpTxtColMap);
|
|
const byte *colMap2 = mainColMap[_game.version];
|
|
|
|
// For LOOM and INDY3, CGA gets dithered as 2x2 squares, for MI1EGA as 2x4 squares. Odd lines have the colors swapped, so there will be checkered patterns.
|
|
uint8 lnMod = (_game.version > 3 && !renderHerc) ? 0x40 : 0x20;
|
|
uint8 lnIdx = renderV3 ? ((y & ((lnMod >> 4) - 1)) << 4) : 0;
|
|
|
|
if (_renderMode == Common::kRenderCGA || _renderMode == Common::kRenderCGAComp) {
|
|
if (renderV3 || vs->number == kMainVirtScreen) {
|
|
for (int h = height; h; --h) {
|
|
for (int w = width >> 1; w; --w) {
|
|
byte c = renderV1 ? *src : (colMap2[src[0] + lnIdx] & 0x0C) | (colMap2[src[1] + lnIdx] & 0x03);
|
|
*dst++ = (c >> 2) & 3;
|
|
*dst++ = c & 3;
|
|
src += 2;
|
|
}
|
|
if (renderV3)
|
|
lnIdx = (lnIdx + 0x10) % lnMod;
|
|
}
|
|
} else {
|
|
for (int h = height; h; --h) {
|
|
for (int w = width >> 1; w; --w) {
|
|
*dst++ = (colMap[*src++] >> 2) & 3;
|
|
*dst++ = colMap[*src++] & 3;
|
|
}
|
|
}
|
|
}
|
|
|
|
} else if (renderHerc || _renderMode == Common::kRenderCGA_BW) {
|
|
// The monochrome rendering is very similiar for Hercules and CGA b/w. For Hercules we have to do some corrections to fit into the 350 pixels height.
|
|
// For Hercules V1/2, the text and verb vs are rendered in normal height, only the main vs gets scaled by leaving out every other line. Hercules V4
|
|
// instead scales everything in a 4-to-7 lines ratio. And for all versions, we center the image horizontally within the 720 pixels width.
|
|
// For CGA b/w the origial resolution is 640x200, so we just scale that to our 640x400 by repeating each line.
|
|
pitch = renderHerc ? kHercWidth : (_screenWidth << 1);
|
|
dst = res = _hercCGAScaleBuf;
|
|
int pitch1 = (pitch - width) << 1;
|
|
|
|
if (renderV3) {
|
|
// This is for MI1EGA Hercules only
|
|
pitch1 = pitch - (width << 1);
|
|
int height2 = height >> 2;
|
|
height = height2 * 7;
|
|
y = (y << 1) - (y >> 2);
|
|
|
|
for (int h1 = height2; h1; --h1) {
|
|
lnIdx = 0; // The 7-lines pattern always starts from the beginning. Which works fine, since the strips get vertically aligned for Hercules and CGA.
|
|
for (int h2 = 7; h2; --h2) {
|
|
for (int w = width >> 2; w; --w) {
|
|
byte c = (hrcTableV4[src[0] + lnIdx] & 0xC0) | (hrcTableV4[src[1] + lnIdx] & 0x30) | (hrcTableV4[src[2] + lnIdx] & 0x0C) | (hrcTableV4[src[3] + lnIdx] & 0x03);
|
|
for (int i = 7; i >= 0; --i)
|
|
*dst++ = (c >> i) & 1;
|
|
src += 4;
|
|
}
|
|
dst += pitch1;
|
|
if (lnIdx ^= 0x10)
|
|
src -= width;
|
|
}
|
|
src += width;
|
|
}
|
|
|
|
} else if (vs->number == kMainVirtScreen) {
|
|
// V1/2 Hercules and CGA b/w mode
|
|
uint32 *dst2 = (uint32*)(dst + pitch);
|
|
int pitch2 = pitch1 >> 2;
|
|
int height2 = height;
|
|
|
|
if (renderHerc) {
|
|
y = (y - vs->topline) * 2 + vs->topline;
|
|
height = MIN<int>(height << 1, kHercHeight - y);
|
|
height2 = height >> 1;
|
|
}
|
|
|
|
for (int h = height2; h; --h) {
|
|
for (int w = width >> 1; w; --w) {
|
|
const uint32 *s = (const uint32*)dst;
|
|
byte c = renderV1 ? *src : (colMap2[src[0]] & 0x0C) | (colMap2[src[1]] & 0x03);
|
|
*dst++ = (c >> 3) & 1;
|
|
*dst++ = (c >> 2) & 1;
|
|
*dst++ = (c >> 1) & 1;
|
|
*dst++ = c & 1;
|
|
*dst2++ = renderHerc ? 0 : *s;
|
|
src += 2;
|
|
}
|
|
dst += pitch1;
|
|
dst2 += pitch2;
|
|
}
|
|
|
|
} else {
|
|
// V1/2 Hercules and CGA b/w mode
|
|
if (renderHerc) {
|
|
pitch1 = kHercWidth - (width << 1);
|
|
y -= vs->topline;
|
|
if (vs->number == kVerbVirtScreen) {
|
|
y += vs->topline * 2 - 16;
|
|
height = MIN<int>(height, kHercHeight - y);
|
|
}
|
|
}
|
|
|
|
uint16 *dst2 = (uint16*)(dst + pitch);
|
|
int pitch2 = pitch1 >> 1;
|
|
|
|
for (int h = height; h; --h) {
|
|
for (int w = width; w; --w) {
|
|
const uint16 *s = (const uint16*)dst;
|
|
uint8 col = colMap[*src++];
|
|
*dst++ = (col >> 1) & 1;
|
|
*dst++ = col & 1;
|
|
if (!renderHerc)
|
|
*dst2++ = *s;
|
|
}
|
|
dst += pitch1;
|
|
dst2 += pitch2;
|
|
}
|
|
}
|
|
|
|
x <<= 1;
|
|
width <<= 1;
|
|
|
|
if (renderHerc) {
|
|
x += 40;
|
|
} else {
|
|
y <<= 1;
|
|
height <<= 1;
|
|
}
|
|
|
|
} else if (renderV1 && vs->number == kTextVirtScreen) {
|
|
// For EGA, the only colors that need remapping are for the kTextVirtScreen.
|
|
// ZAKv1 is the only game that is affected by this. The original interpreter
|
|
// will also apply this mapping in VGA mode (as we do) but not in MCGA mode.
|
|
// So, should we ever decide to offer a separate MCGA render mode, then we
|
|
// should skip this for that mode...
|
|
for (uint8 i = 0; i < ARRAYSIZE(tmpTxtColMap); ++i)
|
|
tmpTxtColMap[i] = _gdi->remapColorToRenderMode(i);
|
|
for (int h = height; h; --h) {
|
|
for (int w = width; w; --w)
|
|
*dst++ = tmpTxtColMap[*src++];
|
|
}
|
|
}
|
|
|
|
return res;
|
|
}
|
|
|
|
const byte *ScummEngine::ditherVGAtoEGA(int &pitch, int &x, int &y, int &width, int &height) const {
|
|
pitch <<= 1;
|
|
int pitch2 = (pitch - width) << 1;
|
|
|
|
uint8 *dst0 = _hercCGAScaleBuf;
|
|
uint8 *dst1 = _hercCGAScaleBuf + pitch;
|
|
uint8 *src = _compositeBuf;
|
|
|
|
for (int i = height, st = 1 ^ (y & 1); i; --i, st ^= 1) {
|
|
for (int ii = width; ii; --ii) {
|
|
byte in = *src++;
|
|
*dst0++ = *dst1++ = _egaColorMap[st][in];
|
|
*dst0++ = *dst1++ = _egaColorMap[st ^ 1][in];
|
|
}
|
|
dst0 += pitch2;
|
|
dst1 += pitch2;
|
|
}
|
|
|
|
x <<= 1;
|
|
y <<= 1;
|
|
width <<= 1;
|
|
height <<= 1;
|
|
|
|
return _hercCGAScaleBuf;
|
|
}
|
|
|
|
#pragma mark -
|
|
#pragma mark --- Background buffers & charset mask ---
|
|
#pragma mark -
|
|
|
|
|
|
void ScummEngine::initBGBuffers(int height) {
|
|
const byte *ptr;
|
|
int size, itemsize, i;
|
|
byte *room;
|
|
|
|
if (_game.version >= 7) {
|
|
// Resize main virtual screen in V7 games. This is necessary
|
|
// because in V7, rooms may be higher than one screen, so we have
|
|
// to accommodate for that.
|
|
initVirtScreen(kMainVirtScreen, _virtscr[kMainVirtScreen].topline, _screenWidth, height, true, true);
|
|
}
|
|
|
|
if (_game.heversion >= 70)
|
|
room = getResourceAddress(rtRoomImage, _roomResource);
|
|
else
|
|
room = getResourceAddress(rtRoom, _roomResource);
|
|
|
|
if (_game.version <= 3) {
|
|
_gdi->_numZBuffer = 2;
|
|
} else if (_game.features & GF_SMALL_HEADER) {
|
|
int off;
|
|
ptr = findResourceData(MKTAG('S','M','A','P'), room);
|
|
_gdi->_numZBuffer = 0;
|
|
|
|
if (_game.features & GF_16COLOR)
|
|
off = READ_LE_UINT16(ptr);
|
|
else
|
|
off = READ_LE_UINT32(ptr);
|
|
|
|
while (off && _gdi->_numZBuffer < 4) {
|
|
_gdi->_numZBuffer++;
|
|
ptr += off;
|
|
off = READ_LE_UINT16(ptr);
|
|
}
|
|
} else if (_game.version == 8) {
|
|
// in V8 there is no RMIH and num z buffers is in RMHD
|
|
ptr = findResource(MKTAG('R','M','H','D'), room);
|
|
_gdi->_numZBuffer = READ_LE_UINT32(ptr + 24) + 1;
|
|
} else if (_game.heversion >= 70) {
|
|
ptr = findResource(MKTAG('R','M','I','H'), room);
|
|
_gdi->_numZBuffer = READ_LE_UINT16(ptr + 8) + 1;
|
|
} else {
|
|
ptr = findResource(MKTAG('R','M','I','H'), findResource(MKTAG('R','M','I','M'), room));
|
|
_gdi->_numZBuffer = READ_LE_UINT16(ptr + 8) + 1;
|
|
}
|
|
assert(_gdi->_numZBuffer >= 1 && _gdi->_numZBuffer <= 8);
|
|
|
|
if (_game.version >= 7)
|
|
itemsize = (_roomHeight + 10) * _gdi->_numStrips;
|
|
else
|
|
itemsize = (_roomHeight + 4) * _gdi->_numStrips;
|
|
|
|
|
|
size = itemsize * _gdi->_numZBuffer;
|
|
memset(_res->createResource(rtBuffer, 9, size), 0, size);
|
|
|
|
for (i = 0; i < (int)ARRAYSIZE(_gdi->_imgBufOffs); i++) {
|
|
if (i < _gdi->_numZBuffer)
|
|
_gdi->_imgBufOffs[i] = i * itemsize;
|
|
else
|
|
_gdi->_imgBufOffs[i] = (_gdi->_numZBuffer - 1) * itemsize;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Redraw background as needed, i.e. the left/right sides if scrolling took place etc.
|
|
* Note that this only updated the virtual screen, not the actual display.
|
|
*/
|
|
void ScummEngine::redrawBGAreas() {
|
|
int i;
|
|
int diff;
|
|
int val = 0;
|
|
|
|
if (_game.id != GID_PASS && _game.version >= 4 && _game.version <= 6) {
|
|
// Starting with V4 games (with the exception of the PASS demo), text
|
|
// is drawn over the game graphics (as opposed to be drawn in a
|
|
// separate region of the screen). So, when scrolling in one of these
|
|
// games (pre-new camera system), if actor text is visible (as indicated
|
|
// by the _hasMask flag), we first remove it before proceeding.
|
|
if (camera._cur.x != camera._last.x && _charset->_hasMask)
|
|
stopTalk();
|
|
}
|
|
|
|
// Redraw parts of the background which are marked as dirty.
|
|
if (!_fullRedraw && _bgNeedsRedraw) {
|
|
for (i = 0; i != _gdi->_numStrips; i++) {
|
|
if (testGfxUsageBit(_screenStartStrip + i, USAGE_BIT_DIRTY)) {
|
|
redrawBGStrip(i, 1);
|
|
}
|
|
}
|
|
}
|
|
|
|
if (_game.version >= 7) {
|
|
diff = camera._cur.x / 8 - camera._last.x / 8;
|
|
if (_fullRedraw || ABS(diff) >= _gdi->_numStrips) {
|
|
_bgNeedsRedraw = false;
|
|
redrawBGStrip(0, _gdi->_numStrips);
|
|
} else if (diff > 0) {
|
|
val = -diff;
|
|
redrawBGStrip(_gdi->_numStrips - diff, diff);
|
|
} else if (diff < 0) {
|
|
val = -diff;
|
|
redrawBGStrip(0, -diff);
|
|
}
|
|
} else {
|
|
diff = camera._cur.x - camera._last.x;
|
|
if (!_fullRedraw && diff == 8) {
|
|
val = -1;
|
|
scrollLeft();
|
|
} else if (!_fullRedraw && diff == -8) {
|
|
val = +1;
|
|
scrollRight();
|
|
} else if (_fullRedraw || diff != 0) {
|
|
if (_game.version <= 5) {
|
|
((ScummEngine_v5 *)this)->clearFlashlight();
|
|
}
|
|
_bgNeedsRedraw = false;
|
|
redrawBGStrip(0, _gdi->_numStrips);
|
|
}
|
|
}
|
|
|
|
drawRoomObjects(val);
|
|
_bgNeedsRedraw = false;
|
|
}
|
|
|
|
#ifdef ENABLE_HE
|
|
void ScummEngine_v71he::redrawBGAreas() {
|
|
if (camera._cur.x != camera._last.x && _charset->_hasMask)
|
|
stopTalk();
|
|
|
|
byte *room = getResourceAddress(rtRoomImage, _roomResource) + _IM00_offs;
|
|
if (_fullRedraw) {
|
|
_bgNeedsRedraw = false;
|
|
_gdi->drawBMAPBg(room, &_virtscr[kMainVirtScreen]);
|
|
}
|
|
|
|
drawRoomObjects(0);
|
|
_bgNeedsRedraw = false;
|
|
}
|
|
|
|
void ScummEngine_v72he::redrawBGAreas() {
|
|
ScummEngine_v71he::redrawBGAreas();
|
|
_wiz->flushAWizBuffer();
|
|
}
|
|
#endif
|
|
|
|
void ScummEngine::redrawBGStrip(int start, int num) {
|
|
byte *room;
|
|
|
|
int s = _screenStartStrip + start;
|
|
|
|
for (int i = 0; i < num; i++)
|
|
setGfxUsageBit(s + i, USAGE_BIT_DIRTY);
|
|
|
|
if (_game.heversion >= 70)
|
|
room = getResourceAddress(rtRoomImage, _roomResource);
|
|
else
|
|
room = getResourceAddress(rtRoom, _roomResource);
|
|
|
|
_gdi->drawBitmap(room + _IM00_offs, &_virtscr[kMainVirtScreen], s, 0, _roomWidth, _virtscr[kMainVirtScreen].h, s, num, 0);
|
|
}
|
|
|
|
void ScummEngine::restoreBackground(Common::Rect rect, byte backColor) {
|
|
VirtScreen *vs;
|
|
byte *screenBuf;
|
|
|
|
if (rect.top < 0)
|
|
rect.top = 0;
|
|
if (rect.left >= rect.right || rect.top >= rect.bottom)
|
|
return;
|
|
|
|
if ((vs = findVirtScreen(rect.top)) == nullptr)
|
|
return;
|
|
|
|
if (rect.left > vs->w)
|
|
return;
|
|
|
|
// Indy4 Amiga always uses the room or verb palette map to match colors to
|
|
// the currently setup palette, thus we need to select it over here too.
|
|
// Done like the original interpreter.
|
|
if (_game.platform == Common::kPlatformAmiga && _game.id == GID_INDY4) {
|
|
if (vs->number == kVerbVirtScreen)
|
|
backColor = _verbPalette[backColor];
|
|
else
|
|
backColor = _roomPalette[backColor];
|
|
}
|
|
|
|
// MM NES background color is 0x1d
|
|
if (_game.platform == Common::kPlatformNES) {
|
|
backColor = 0x1d;
|
|
}
|
|
|
|
// Convert 'rect' to local (virtual screen) coordinates
|
|
rect.top -= vs->topline;
|
|
rect.bottom -= vs->topline;
|
|
|
|
rect.clip(vs->w, vs->h);
|
|
|
|
const int height = rect.height();
|
|
const int width = rect.width();
|
|
|
|
#ifndef DISABLE_TOWNS_DUAL_LAYER_MODE
|
|
if (_game.platform == Common::kPlatformFMTowns && _game.id == GID_MONKEY && vs->number == kVerbVirtScreen && rect.bottom <= 154)
|
|
rect.right = 319;
|
|
#endif
|
|
|
|
markRectAsDirty(vs->number, rect, USAGE_BIT_RESTORED);
|
|
|
|
screenBuf = vs->getPixels(rect.left, rect.top);
|
|
|
|
if (!height)
|
|
return;
|
|
|
|
if (vs->hasTwoBuffers && _currentRoom != 0 && isLightOn()) {
|
|
blit(screenBuf, vs->pitch, vs->getBackPixels(rect.left, rect.top), vs->pitch, width, height, vs->format.bytesPerPixel);
|
|
if (vs->number == kMainVirtScreen && _charset->_hasMask) {
|
|
#ifndef DISABLE_TOWNS_DUAL_LAYER_MODE
|
|
if (_game.platform == Common::kPlatformFMTowns) {
|
|
byte *mask = (byte *)_textSurface.getBasePtr(rect.left * _textSurfaceMultiplier, (rect.top + vs->topline) * _textSurfaceMultiplier);
|
|
fill(mask, _textSurface.pitch, 0, width * _textSurfaceMultiplier, height * _textSurfaceMultiplier, _textSurface.format.bytesPerPixel);
|
|
} else
|
|
#endif
|
|
{
|
|
byte *mask = (byte *)_textSurface.getBasePtr(rect.left, rect.top - _screenTop - _screenDrawOffset);
|
|
fill(mask, _textSurface.pitch, CHARSET_MASK_TRANSPARENCY, width * _textSurfaceMultiplier, height * _textSurfaceMultiplier, _textSurface.format.bytesPerPixel);
|
|
}
|
|
}
|
|
} else {
|
|
#ifndef DISABLE_TOWNS_DUAL_LAYER_MODE
|
|
if (_game.platform == Common::kPlatformFMTowns) {
|
|
backColor |= (backColor << 4);
|
|
byte *mask = (byte *)_textSurface.getBasePtr(rect.left * _textSurfaceMultiplier, (rect.top + vs->topline) * _textSurfaceMultiplier);
|
|
fill(mask, _textSurface.pitch, backColor, width * _textSurfaceMultiplier, height * _textSurfaceMultiplier, _textSurface.format.bytesPerPixel);
|
|
}
|
|
#endif
|
|
|
|
if (_macScreen) {
|
|
byte *mask = (byte *)_textSurface.getBasePtr(rect.left * _textSurfaceMultiplier, (rect.top + vs->topline) * _textSurfaceMultiplier);
|
|
fill(mask, _textSurface.pitch, CHARSET_MASK_TRANSPARENCY, width * _textSurfaceMultiplier, height * _textSurfaceMultiplier, _textSurface.format.bytesPerPixel);
|
|
}
|
|
|
|
if (_game.features & GF_16BIT_COLOR)
|
|
fill(screenBuf, vs->pitch, _16BitPalette[backColor], width, height, vs->format.bytesPerPixel);
|
|
else
|
|
fill(screenBuf, vs->pitch, backColor, width, height, vs->format.bytesPerPixel);
|
|
}
|
|
}
|
|
|
|
void ScummEngine::restoreCharsetBg() {
|
|
_nextLeft = _string[0].xpos;
|
|
_nextTop = _string[0].ypos + _screenTop + _screenDrawOffset;
|
|
|
|
if (_charset->_hasMask || _postGUICharMask) {
|
|
_postGUICharMask = false;
|
|
_charset->_hasMask = false;
|
|
_charset->_str.left = -1;
|
|
_charset->_left = -1;
|
|
|
|
if (_macGui && _game.id == GID_INDY3 && _charset->_textScreenID == kTextVirtScreen) {
|
|
mac_undrawIndy3TextBox();
|
|
return;
|
|
}
|
|
|
|
// Restore background on the whole text area. This code is based on
|
|
// restoreBackground(), but was changed to only restore those parts which are
|
|
// currently covered by the charset mask.
|
|
|
|
VirtScreen *vs = &_virtscr[_charset->_textScreenID];
|
|
if (!vs->h)
|
|
return;
|
|
|
|
markRectAsDirty(vs->number, Common::Rect(vs->w, vs->h), USAGE_BIT_RESTORED);
|
|
|
|
byte *screenBuf = vs->getPixels(0, 0);
|
|
|
|
if (vs->hasTwoBuffers && _currentRoom != 0 && isLightOn()) {
|
|
if (vs->number != kMainVirtScreen) {
|
|
// Restore from back buffer
|
|
const byte *backBuf = vs->getBackPixels(0, 0);
|
|
blit(screenBuf, vs->pitch, backBuf, vs->pitch, vs->w, vs->h, vs->format.bytesPerPixel);
|
|
}
|
|
} else {
|
|
// Clear area
|
|
if (_game.platform == Common::kPlatformNES)
|
|
memset(screenBuf, 0x1d, vs->h * vs->pitch);
|
|
else
|
|
memset(screenBuf, 0, vs->h * vs->pitch);
|
|
}
|
|
|
|
if (vs->hasTwoBuffers || _macScreen) {
|
|
// Clean out the charset mask
|
|
clearTextSurface();
|
|
}
|
|
}
|
|
}
|
|
|
|
void ScummEngine::clearCharsetMask() {
|
|
memset(getResourceAddress(rtBuffer, 9), 0, _gdi->_imgBufOffs[1]);
|
|
}
|
|
|
|
void ScummEngine::clearTextSurface() {
|
|
towns_fillTopLayerRect(0, 0, _textSurface.w, _textSurface.h, 0);
|
|
fill((byte *)_textSurface.getPixels(), _textSurface.pitch,
|
|
#ifndef DISABLE_TOWNS_DUAL_LAYER_MODE
|
|
_game.platform == Common::kPlatformFMTowns ? 0 :
|
|
#endif
|
|
CHARSET_MASK_TRANSPARENCY, _textSurface.w, _textSurface.h, _textSurface.format.bytesPerPixel);
|
|
}
|
|
|
|
byte *ScummEngine::getMaskBuffer(int x, int y, int z) {
|
|
return _gdi->getMaskBuffer((x + _virtscr[kMainVirtScreen].xstart) / 8, y, z);
|
|
}
|
|
|
|
byte *Gdi::getMaskBuffer(int x, int y, int z) {
|
|
return _vm->getResourceAddress(rtBuffer, 9)
|
|
+ x + y * _numStrips + _imgBufOffs[z];
|
|
}
|
|
|
|
|
|
#pragma mark -
|
|
#pragma mark --- Misc ---
|
|
#pragma mark -
|
|
|
|
static void blit(byte *dst, int dstPitch, const byte *src, int srcPitch, int w, int h, uint8 bitDepth) {
|
|
assert(w > 0);
|
|
assert(h > 0);
|
|
assert(src != nullptr);
|
|
assert(dst != nullptr);
|
|
|
|
if ((w * bitDepth == srcPitch) && (w * bitDepth == dstPitch)) {
|
|
memcpy(dst, src, w * h * bitDepth);
|
|
} else {
|
|
do {
|
|
memcpy(dst, src, w * bitDepth);
|
|
dst += dstPitch;
|
|
src += srcPitch;
|
|
} while (--h);
|
|
}
|
|
}
|
|
|
|
static void fill(byte *dst, int dstPitch, uint16 color, int w, int h, uint8 bitDepth) {
|
|
assert(h > 0);
|
|
assert(dst != nullptr);
|
|
|
|
if (bitDepth == 2) {
|
|
do {
|
|
for (int i = 0; i < w; i++)
|
|
WRITE_UINT16(dst + i * 2, color);
|
|
dst += dstPitch;
|
|
} while (--h);
|
|
} else {
|
|
if (w == dstPitch) {
|
|
memset(dst, color, w * h);
|
|
} else {
|
|
do {
|
|
memset(dst, color, w);
|
|
dst += dstPitch;
|
|
} while (--h);
|
|
}
|
|
}
|
|
}
|
|
|
|
#ifdef USE_ARM_GFX_ASM
|
|
|
|
#define copy8Col(A,B,C,D,E) asmCopy8Col(A,B,C,D,E)
|
|
|
|
#else
|
|
|
|
static void copy8Col(byte *dst, int dstPitch, const byte *src, int height, uint8 bitDepth) {
|
|
|
|
do {
|
|
#if defined(SCUMM_NEED_ALIGNMENT)
|
|
memcpy(dst, src, 8 * bitDepth);
|
|
#else
|
|
((uint32 *)dst)[0] = ((const uint32 *)src)[0];
|
|
((uint32 *)dst)[1] = ((const uint32 *)src)[1];
|
|
if (bitDepth == 2) {
|
|
((uint32 *)dst)[2] = ((const uint32 *)src)[2];
|
|
((uint32 *)dst)[3] = ((const uint32 *)src)[3];
|
|
}
|
|
#endif
|
|
dst += dstPitch;
|
|
src += dstPitch;
|
|
} while (--height);
|
|
}
|
|
|
|
#endif /* USE_ARM_GFX_ASM */
|
|
|
|
static void clear8Col(byte *dst, int dstPitch, int height, uint8 bitDepth) {
|
|
do {
|
|
#if defined(SCUMM_NEED_ALIGNMENT)
|
|
if (g_scumm->_game.platform == Common::kPlatformNES)
|
|
memset(dst, 0x1d, 8 * bitDepth);
|
|
else
|
|
memset(dst, 0, 8 * bitDepth);
|
|
#else
|
|
if (g_scumm->_game.platform == Common::kPlatformNES) {
|
|
memset(dst, 0x1d, 8 * bitDepth);
|
|
} else {
|
|
((uint32*)dst)[0] = 0;
|
|
((uint32*)dst)[1] = 0;
|
|
if (bitDepth == 2) {
|
|
((uint32*)dst)[2] = 0;
|
|
((uint32*)dst)[3] = 0;
|
|
}
|
|
}
|
|
#endif
|
|
dst += dstPitch;
|
|
} while (--height);
|
|
}
|
|
|
|
void ScummEngine::drawBox(int x, int y, int x2, int y2, int color) {
|
|
int width, height;
|
|
VirtScreen *vs;
|
|
byte *backbuff, *bgbuff;
|
|
|
|
if ((vs = findVirtScreen(y + _screenDrawOffset)) == nullptr)
|
|
return;
|
|
|
|
if (_game.version == 8) {
|
|
width = _screenWidth + 8;
|
|
height = _screenHeight;
|
|
int effX2 = x2;
|
|
int effX;
|
|
if (width >= x2) {
|
|
effX = x;
|
|
} else {
|
|
effX2 = width;
|
|
effX = x;
|
|
if (x < 0)
|
|
effX = 0;
|
|
}
|
|
backbuff = vs->getPixels(effX, y + _screenTop);
|
|
fill(backbuff, vs->pitch, color, effX2, y2, vs->format.bytesPerPixel);
|
|
markRectAsDirty(vs->number, effX, effX + effX2, y + _screenTop, y + y2 + _screenTop);
|
|
return;
|
|
}
|
|
|
|
// Indy4 Amiga always uses the room or verb palette map to match colors to
|
|
// the currently setup palette, thus we need to select it over here too.
|
|
// Done like the original interpreter.
|
|
if (_game.platform == Common::kPlatformAmiga && _game.id == GID_INDY4) {
|
|
if (vs->number == kVerbVirtScreen)
|
|
color = _verbPalette[color];
|
|
else
|
|
color = _roomPalette[color];
|
|
}
|
|
|
|
if (x > x2)
|
|
SWAP(x, x2);
|
|
|
|
if (y > y2)
|
|
SWAP(y, y2);
|
|
|
|
x2++;
|
|
y2++;
|
|
|
|
// Adjust for the topline of the VirtScreen
|
|
y -= vs->topline - _screenDrawOffset;
|
|
y2 -= vs->topline - _screenDrawOffset;
|
|
|
|
// Clip the coordinates
|
|
if (x < 0)
|
|
x = 0;
|
|
else if (x >= vs->w)
|
|
return;
|
|
|
|
if (x2 < 0)
|
|
return;
|
|
else if (x2 > vs->w)
|
|
x2 = vs->w;
|
|
|
|
if (y < 0)
|
|
y = 0;
|
|
else if (y > vs->h)
|
|
return;
|
|
|
|
if (y2 < 0)
|
|
return;
|
|
else if (y2 > vs->h)
|
|
y2 = vs->h;
|
|
|
|
width = x2 - x;
|
|
height = y2 - y;
|
|
|
|
// This will happen in the Sam & Max intro - see bug #1797 - where
|
|
// it would trigger an assertion in blit().
|
|
|
|
if (width <= 0 || height <= 0)
|
|
return;
|
|
|
|
#ifndef DISABLE_TOWNS_DUAL_LAYER_MODE
|
|
// Some FM-Towns games draw directly on layer 2, without setting the virtscreen dirty, bypassing the normal drawing
|
|
// routines. It can make a difference, e. g. bug no. 15027 ("INDY3 (FMTowns): Map lines are drawn incorrectly, plus
|
|
// more issues when leaving Germany"). Making the virtscreen dirty, would cause some wrong colors, due to the way the
|
|
// scripts handle the shadow palette there.
|
|
if (!_townsScreen || _game.platform != Common::kPlatformFMTowns || _game.version != 3 || vs->number == kTextVirtScreen)
|
|
#endif
|
|
markRectAsDirty(vs->number, x, x2, y, y2);
|
|
|
|
backbuff = vs->getPixels(x, y);
|
|
bgbuff = vs->getBackPixels(x, y);
|
|
|
|
// A check for -1 might be wrong in all cases since o5_drawBox() in its current form
|
|
// is definitely not capable of passing a parameter of -1 (color range is 0 - 255).
|
|
// Just to make sure I don't break anything I restrict the code change to FM-Towns
|
|
// version 5 games where this change is necessary to fix certain long standing bugs.
|
|
if (color == -1
|
|
#ifndef DISABLE_TOWNS_DUAL_LAYER_MODE
|
|
|| (color >= 254 && _game.platform == Common::kPlatformFMTowns && (_game.id == GID_MONKEY2 || _game.id == GID_INDY4))
|
|
#endif
|
|
) {
|
|
#ifndef DISABLE_TOWNS_DUAL_LAYER_MODE
|
|
if (_game.platform == Common::kPlatformFMTowns) {
|
|
if (color == 254)
|
|
towns_setupPalCycleField(x, y, x2, y2);
|
|
} else
|
|
#endif
|
|
{
|
|
if (vs->number != kMainVirtScreen)
|
|
error("can only copy bg to main window");
|
|
|
|
blit(backbuff, vs->pitch, bgbuff, vs->pitch, width, height, vs->format.bytesPerPixel);
|
|
if (_charset->_hasMask) {
|
|
byte *mask = (byte *)_textSurface.getBasePtr(x * _textSurfaceMultiplier, (y - _screenTop) * _textSurfaceMultiplier);
|
|
fill(mask, _textSurface.pitch, CHARSET_MASK_TRANSPARENCY, width * _textSurfaceMultiplier, height * _textSurfaceMultiplier, _textSurface.format.bytesPerPixel);
|
|
}
|
|
}
|
|
} else if (_game.heversion >= 72) {
|
|
// Flags are used for different methods in HE games
|
|
uint32 flags = color;
|
|
if ((flags & 0x2000) || (flags & 0x4000000)) {
|
|
blit(backbuff, vs->pitch, bgbuff, vs->pitch, width, height, vs->format.bytesPerPixel);
|
|
} else if ((flags & 0x4000) || (flags & 0x2000000)) {
|
|
blit(bgbuff, vs->pitch, backbuff, vs->pitch, width, height, vs->format.bytesPerPixel);
|
|
} else if ((flags & 0x8000) || (flags & 0x1000000)) {
|
|
flags &= (flags & 0x1000000) ? 0xFFFFFF : 0x7FFF;
|
|
fill(backbuff, vs->pitch, flags, width, height, vs->format.bytesPerPixel);
|
|
fill(bgbuff, vs->pitch, flags, width, height, vs->format.bytesPerPixel);
|
|
} else {
|
|
fill(backbuff, vs->pitch, flags, width, height, vs->format.bytesPerPixel);
|
|
}
|
|
} else if (_game.heversion >= 60) {
|
|
// Flags are used for different methods in HE games
|
|
uint16 flags = color;
|
|
if (flags & 0x2000) {
|
|
blit(backbuff, vs->pitch, bgbuff, vs->pitch, width, height, vs->format.bytesPerPixel);
|
|
} else if (flags & 0x4000) {
|
|
blit(bgbuff, vs->pitch, backbuff, vs->pitch, width, height, vs->format.bytesPerPixel);
|
|
} else if (flags & 0x8000) {
|
|
flags &= 0x7FFF;
|
|
fill(backbuff, vs->pitch, flags, width, height, vs->format.bytesPerPixel);
|
|
fill(bgbuff, vs->pitch, flags, width, height, vs->format.bytesPerPixel);
|
|
} else {
|
|
fill(backbuff, vs->pitch, flags, width, height, vs->format.bytesPerPixel);
|
|
}
|
|
} else {
|
|
if (_game.features & GF_16BIT_COLOR) {
|
|
fill(backbuff, vs->pitch, _16BitPalette[color], width, height, vs->format.bytesPerPixel);
|
|
} else {
|
|
#ifndef DISABLE_TOWNS_DUAL_LAYER_MODE
|
|
if (_game.platform == Common::kPlatformFMTowns) {
|
|
if (_game.version == 3 && vs->number != kTextVirtScreen) {
|
|
// The original FM-Towns v3 interpreter overdraws both width
|
|
// and height by 1 pixel. I don't know if it is voluntary or
|
|
// not, so I have added safety checks.
|
|
if (x + width < vs->w)
|
|
width++;
|
|
if (y + height < vs->h)
|
|
height++;
|
|
}
|
|
|
|
color = ((color & 0x0f) << 4) | (color & 0x0f);
|
|
// Some FM-Towns games draw directly on layer 2, without setting the virtscreeb dirty, bypassing the
|
|
// normal drawing routines. It can make a difference, e. g. bug no. 15027 ("INDY3 (FMTowns): Map lines
|
|
// are drawn incorrectly, plus more issues when leaving Germany"). Making the virtscreen dirty, would
|
|
// cause some wrong colors, due to the way the scripts handle the shadow palette there.
|
|
if (_game.version == 3 && vs->number != kTextVirtScreen) {
|
|
towns_fillTopLayerRect(x * _textSurfaceMultiplier,
|
|
(y - _screenTop + vs->topline) * _textSurfaceMultiplier, width * _textSurfaceMultiplier, height * _textSurfaceMultiplier, color);
|
|
// We still need to continue and make the same changes to the textSurface (the original FM-Towns
|
|
// SCUMM3 interpreters don't have that, everything goes directly onto screen layer 2 there).
|
|
}
|
|
|
|
if (vs->number == kBannerVirtScreen) {
|
|
byte *mask = _virtscr[kBannerVirtScreen].getPixels(x, y);
|
|
fill(mask, vs->pitch, color, width * _textSurfaceMultiplier, height * _textSurfaceMultiplier, vs->format.bytesPerPixel);
|
|
} else {
|
|
byte *mask = (byte *)_textSurface.getBasePtr(x * _textSurfaceMultiplier, (y - _screenTop + vs->topline) * _textSurfaceMultiplier);
|
|
fill(mask, _textSurface.pitch, color, width * _textSurfaceMultiplier, height * _textSurfaceMultiplier, _textSurface.format.bytesPerPixel);
|
|
}
|
|
|
|
if (_game.id != GID_MONKEY && !(_game.version == 3 && vs->number == kTextVirtScreen))
|
|
return;
|
|
}
|
|
#endif
|
|
|
|
if (_macScreen) {
|
|
byte *mask = (byte *)_textSurface.getBasePtr(x * _textSurfaceMultiplier, (y - _screenTop + vs->topline) * _textSurfaceMultiplier);
|
|
fill(mask, _textSurface.pitch, CHARSET_MASK_TRANSPARENCY, width * _textSurfaceMultiplier, height * _textSurfaceMultiplier, _textSurface.format.bytesPerPixel);
|
|
}
|
|
|
|
fill(backbuff, vs->pitch, color, width, height, vs->format.bytesPerPixel);
|
|
}
|
|
}
|
|
}
|
|
|
|
void ScummEngine::drawLine(int x1, int y1, int x2, int y2, int color) {
|
|
if (_game.platform == Common::kPlatformFMTowns && _game.version == 5) {
|
|
drawBox(x1, y1, x2, y2, color);
|
|
return;
|
|
}
|
|
|
|
int effColor, black, white;
|
|
int effX1, effY1;
|
|
int width, height, widthAccumulator, heightAccumulator, horizontalStrips, originalHeight;
|
|
int nudgeX, nudgeY;
|
|
|
|
bool canDrawPixel, noColorSpecified;
|
|
|
|
VirtScreen *vs;
|
|
|
|
if ((vs = findVirtScreen(y1)) == nullptr)
|
|
return;
|
|
|
|
black = getPaletteColorFromRGB(_currentPalette, 0x00, 0x00, 0x00);
|
|
white = getPaletteColorFromRGB(_currentPalette, 0xFC, 0xFC, 0xFC);
|
|
|
|
noColorSpecified = false;
|
|
effColor = color;
|
|
if (color == -1) {
|
|
noColorSpecified = true;
|
|
effColor = white;
|
|
}
|
|
|
|
effX1 = x1;
|
|
effY1 = y1;
|
|
width = abs(x2 - x1);
|
|
height = abs(y2 - y1);
|
|
originalHeight = height;
|
|
|
|
if (height <= width)
|
|
height = width;
|
|
|
|
widthAccumulator = 0;
|
|
heightAccumulator = 0;
|
|
|
|
drawPixel(vs, x1, y1, effColor);
|
|
|
|
if (height >= 0) {
|
|
horizontalStrips = height + 1;
|
|
do {
|
|
widthAccumulator += width;
|
|
canDrawPixel = false;
|
|
heightAccumulator += originalHeight;
|
|
if (widthAccumulator > height) {
|
|
canDrawPixel = true;
|
|
widthAccumulator -= height;
|
|
nudgeX = 1;
|
|
if (x2 - x1 < 0)
|
|
nudgeX = -1;
|
|
effX1 += nudgeX;
|
|
}
|
|
|
|
if (heightAccumulator > height) {
|
|
canDrawPixel = true;
|
|
heightAccumulator -= height;
|
|
nudgeY = 1;
|
|
if (y2 - y1 < 0)
|
|
nudgeY = -1;
|
|
effY1 += nudgeY;
|
|
}
|
|
|
|
if (canDrawPixel) {
|
|
drawPixel(vs, effX1, effY1, effColor);
|
|
if (noColorSpecified) {
|
|
if (effColor != white)
|
|
effColor = white;
|
|
else
|
|
effColor = black;
|
|
}
|
|
}
|
|
|
|
horizontalStrips--;
|
|
} while (horizontalStrips);
|
|
}
|
|
}
|
|
|
|
void ScummEngine::drawPixel(VirtScreen *vs, int x, int y, int16 color, bool useBackbuffer) {
|
|
int factor = _isIndy4Jap ? 0 : 8;
|
|
int wScale = (vs->number == kBannerVirtScreen && _textSurfaceMultiplier == 2) ? 2 : 1;
|
|
if (x >= 0 && y >= 0 && _screenWidth + factor > x && _screenHeight > y) {
|
|
if (useBackbuffer) {
|
|
*(vs->getBackPixels(x, y + _screenTop - vs->topline)) = color;
|
|
} else {
|
|
// Is it elegant to do the kBannerVirtScreen horizontal scaling here like this? Certainly not,
|
|
// but it is just what the original interpreter does. So it will at least not break anything.
|
|
for (int i = 0; i < wScale; ++i)
|
|
*(vs->getPixels(x * wScale + i, y + _screenTop - vs->topline)) = color;
|
|
}
|
|
markRectAsDirty(vs->number, x * wScale, (x + 1) * wScale, y + _screenTop - vs->topline, y + 1 + _screenTop - vs->topline);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Moves the screen content by the offset specified via dx/dy.
|
|
* Only the region from x=0 till x=height-1 is affected.
|
|
* @param dx the horizontal offset.
|
|
* @param dy the vertical offset.
|
|
* @param height the number of lines which in which the move will be done.
|
|
*/
|
|
void ScummEngine::moveScreen(int dx, int dy, int height) {
|
|
// Short circuit check - do we have to do anything anyway?
|
|
if ((dx == 0 && dy == 0) || height <= 0)
|
|
return;
|
|
|
|
Graphics::Surface *screen = _system->lockScreen();
|
|
if (!screen)
|
|
return;
|
|
screen->move(dx, dy, height);
|
|
_system->unlockScreen();
|
|
}
|
|
|
|
void ScummEngine_v5::clearFlashlight() {
|
|
_flashlight.isDrawn = false;
|
|
_flashlight.buffer = nullptr;
|
|
}
|
|
|
|
void ScummEngine_v5::drawFlashlight() {
|
|
int i, j, x, y;
|
|
VirtScreen *vs = &_virtscr[kMainVirtScreen];
|
|
byte backgroundColor = 0;
|
|
|
|
// NES uses 0x1d for black
|
|
if (g_scumm->_game.platform == Common::kPlatformNES)
|
|
backgroundColor = 0x1d;
|
|
|
|
// Remove the flash light first if it was previously drawn
|
|
if (_flashlight.isDrawn) {
|
|
markRectAsDirty(kMainVirtScreen, _flashlight.x, _flashlight.x + _flashlight.w,
|
|
_flashlight.y, _flashlight.y + _flashlight.h, USAGE_BIT_DIRTY);
|
|
|
|
if (_flashlight.buffer) {
|
|
fill(_flashlight.buffer, vs->pitch, backgroundColor, _flashlight.w, _flashlight.h, vs->format.bytesPerPixel);
|
|
}
|
|
_flashlight.isDrawn = false;
|
|
}
|
|
|
|
if (_flashlight.xStrips == 0 || _flashlight.yStrips == 0)
|
|
return;
|
|
|
|
// Calculate the area of the flashlight
|
|
if (_game.id == GID_ZAK || _game.id == GID_MANIAC) {
|
|
x = _mouse.x + vs->xstart;
|
|
y = _mouse.y - vs->topline;
|
|
} else {
|
|
Actor *a = derefActor(VAR(VAR_EGO), "drawFlashlight");
|
|
x = a->getPos().x;
|
|
y = a->getPos().y;
|
|
}
|
|
_flashlight.w = _flashlight.xStrips * 8;
|
|
_flashlight.h = _flashlight.yStrips * 8;
|
|
_flashlight.x = x - _flashlight.w / 2 - _screenStartStrip * 8;
|
|
_flashlight.y = y - _flashlight.h / 2;
|
|
|
|
if (_game.id == GID_LOOM)
|
|
_flashlight.y -= 12;
|
|
|
|
// Clip the flashlight at the borders
|
|
if (_flashlight.x < 0)
|
|
_flashlight.x = 0;
|
|
else if (_flashlight.x + _flashlight.w > _gdi->_numStrips * 8)
|
|
_flashlight.x = _gdi->_numStrips * 8 - _flashlight.w;
|
|
if (_flashlight.y < 0)
|
|
_flashlight.y = 0;
|
|
else if (_flashlight.y + _flashlight.h> vs->h)
|
|
_flashlight.y = vs->h - _flashlight.h;
|
|
|
|
// Redraw any actors "under" the flashlight
|
|
for (i = _flashlight.x / 8; i < (_flashlight.x + _flashlight.w) / 8; i++) {
|
|
assert(0 <= i && i < _gdi->_numStrips);
|
|
setGfxUsageBit(_screenStartStrip + i, USAGE_BIT_DIRTY);
|
|
vs->tdirty[i] = 0;
|
|
vs->bdirty[i] = vs->h;
|
|
}
|
|
|
|
byte *bgbak;
|
|
_flashlight.buffer = vs->getPixels(_flashlight.x, _flashlight.y);
|
|
bgbak = vs->getBackPixels(_flashlight.x, _flashlight.y);
|
|
|
|
blit(_flashlight.buffer, vs->pitch, bgbak, vs->pitch, _flashlight.w, _flashlight.h, vs->format.bytesPerPixel);
|
|
|
|
// C64 & NES does not round the flashlight
|
|
if (_game.platform != Common::kPlatformC64 && _game.platform != Common::kPlatformNES) {
|
|
// Round the corners. To do so, we simply hard-code a set of nicely
|
|
// rounded corners.
|
|
static const int corner_data[] = { 8, 6, 4, 3, 2, 2, 1, 1 };
|
|
int minrow = 0;
|
|
int maxcol = (_flashlight.w - 1) * vs->format.bytesPerPixel;
|
|
int maxrow = (_flashlight.h - 1) * vs->pitch;
|
|
|
|
for (i = 0; i < 8; i++, minrow += vs->pitch, maxrow -= vs->pitch) {
|
|
int d = corner_data[i];
|
|
|
|
for (j = 0; j < d; j++) {
|
|
if (vs->format.bytesPerPixel == 2) {
|
|
WRITE_UINT16(&_flashlight.buffer[minrow + 2 * j], 0);
|
|
WRITE_UINT16(&_flashlight.buffer[minrow + maxcol - 2 * j], 0);
|
|
WRITE_UINT16(&_flashlight.buffer[maxrow + 2 * j], 0);
|
|
WRITE_UINT16(&_flashlight.buffer[maxrow + maxcol - 2 * j], 0);
|
|
}
|
|
else {
|
|
_flashlight.buffer[minrow + j] = backgroundColor;
|
|
_flashlight.buffer[minrow + maxcol - j] = backgroundColor;
|
|
_flashlight.buffer[maxrow + j] = backgroundColor;
|
|
_flashlight.buffer[maxrow + maxcol - j] = backgroundColor;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
_flashlight.isDrawn = true;
|
|
}
|
|
|
|
int ScummEngine_v0::getCurrentLights() const {
|
|
// V0 Maniac doesn't have a ScummVar for VAR_CURRENT_LIGHTS, and just uses
|
|
// an internal variable. Emulate this to prevent overwriting script vars...
|
|
// And V6 games do not use the "lights" at all. There, the whole screen is
|
|
// always visible, and actors are always colored, so we fake the correct
|
|
// light value for it.
|
|
return _currentLights;
|
|
}
|
|
|
|
int ScummEngine::getCurrentLights() const {
|
|
if (_game.version >= 6)
|
|
return LIGHTMODE_room_lights_on | LIGHTMODE_actor_use_colors;
|
|
else
|
|
return VAR(VAR_CURRENT_LIGHTS);
|
|
}
|
|
|
|
bool ScummEngine::isLightOn() const {
|
|
return (getCurrentLights() & LIGHTMODE_room_lights_on) != 0;
|
|
}
|
|
|
|
void ScummEngine::setShake(int mode) {
|
|
if (_shakeEnabled != (mode != 0))
|
|
_fullRedraw = true;
|
|
|
|
_shakeEnabled = mode != 0;
|
|
_shakeFrame = 0;
|
|
_system->setShakePos(0, 0);
|
|
}
|
|
|
|
#pragma mark -
|
|
#pragma mark --- Image drawing ---
|
|
#pragma mark -
|
|
|
|
|
|
void Gdi::prepareDrawBitmap(const byte *ptr, VirtScreen *vs,
|
|
const int x, const int y, const int width, const int height,
|
|
int stripnr, int numstrip) {
|
|
// Do nothing by default
|
|
}
|
|
|
|
void GdiHE::prepareDrawBitmap(const byte *ptr, VirtScreen *vs,
|
|
const int x, const int y, const int width, const int height,
|
|
int stripnr, int numstrip) {
|
|
if (_vm->_game.heversion >= 72) {
|
|
_tmskPtr = _vm->findResource(MKTAG('T','M','S','K'), ptr);
|
|
} else
|
|
_tmskPtr = nullptr;
|
|
}
|
|
|
|
void GdiV1::prepareDrawBitmap(const byte *ptr, VirtScreen *vs,
|
|
const int x, const int y, const int width, const int height,
|
|
int stripnr, int numstrip) {
|
|
if (_objectMode) {
|
|
decodeV1Gfx(ptr, _V1.objectMap, (width / 8) * (height / 8) * 3);
|
|
}
|
|
}
|
|
|
|
void GdiNES::prepareDrawBitmap(const byte *ptr, VirtScreen *vs,
|
|
const int x, const int y, const int width, const int height,
|
|
int stripnr, int numstrip) {
|
|
if (_objectMode) {
|
|
decodeNESObject(ptr, x - stripnr, y, width, height);
|
|
}
|
|
}
|
|
|
|
#ifdef USE_RGB_COLOR
|
|
void GdiPCEngine::prepareDrawBitmap(const byte *ptr, VirtScreen *vs,
|
|
const int x, const int y, const int width, const int height,
|
|
int stripnr, int numstrip) {
|
|
if (_objectMode) {
|
|
decodePCEngineObject(ptr, x - stripnr, y, width, height);
|
|
}
|
|
}
|
|
#endif
|
|
|
|
void GdiV2::prepareDrawBitmap(const byte *ptr, VirtScreen *vs,
|
|
const int x, const int y, const int width, const int height,
|
|
int stripnr, int numstrip) {
|
|
//
|
|
// Since V3, all graphics data was encoded in strips, which is very efficient
|
|
// for redrawing only parts of the screen. However, V2 is different: here
|
|
// the whole graphics are encoded as one big chunk. That makes it rather
|
|
// difficult to draw only parts of a room/object. We handle the V2 graphics
|
|
// differently from all other (newer) graphic formats for this reason.
|
|
//
|
|
StripTable *table = (_objectMode ? nullptr : _roomStrips);
|
|
const int left = (stripnr * 8);
|
|
const int right = left + (numstrip * 8);
|
|
byte *dst;
|
|
byte *mask_ptr;
|
|
const byte *src;
|
|
byte color, data = 0;
|
|
int run;
|
|
bool dither = false;
|
|
byte dither_table[128];
|
|
byte *ptr_dither_table;
|
|
int theX, theY, maxX;
|
|
|
|
memset(dither_table, 0, sizeof(dither_table));
|
|
|
|
if (vs->hasTwoBuffers)
|
|
dst = vs->backBuf + y * vs->pitch + x * 8;
|
|
else
|
|
dst = (byte *)vs->getBasePtr(x * 8, y);
|
|
|
|
mask_ptr = getMaskBuffer(x, y, 1);
|
|
|
|
|
|
if (table) {
|
|
run = table->run[stripnr];
|
|
color = table->color[stripnr];
|
|
src = ptr + table->offsets[stripnr];
|
|
theX = left;
|
|
maxX = right;
|
|
} else {
|
|
run = 1;
|
|
color = 0;
|
|
src = ptr;
|
|
theX = 0;
|
|
maxX = width;
|
|
}
|
|
|
|
// Decode and draw the image data.
|
|
assert(height <= 128);
|
|
for (; theX < maxX; theX++) {
|
|
ptr_dither_table = dither_table;
|
|
for (theY = 0; theY < height; theY++) {
|
|
if (--run == 0) {
|
|
data = *src++;
|
|
if (data & 0x80) {
|
|
run = data & 0x7f;
|
|
dither = true;
|
|
} else {
|
|
run = data >> 4;
|
|
dither = false;
|
|
}
|
|
color = _roomPalette[data & 0x0f];
|
|
if (run == 0) {
|
|
run = *src++;
|
|
}
|
|
}
|
|
if (!dither) {
|
|
*ptr_dither_table = color;
|
|
}
|
|
if (left <= theX && theX < right) {
|
|
*dst = *ptr_dither_table++;
|
|
dst += vs->pitch;
|
|
}
|
|
}
|
|
if (left <= theX && theX < right) {
|
|
dst -= _vertStripNextInc;
|
|
}
|
|
}
|
|
|
|
|
|
// Draw mask (zplane) data
|
|
theY = 0;
|
|
|
|
if (table) {
|
|
src = ptr + table->zoffsets[stripnr];
|
|
run = table->zrun[stripnr];
|
|
theX = left;
|
|
} else {
|
|
run = *src++;
|
|
theX = 0;
|
|
}
|
|
while (theX < right) {
|
|
const byte runFlag = run & 0x80;
|
|
if (runFlag) {
|
|
run &= 0x7f;
|
|
data = *src++;
|
|
}
|
|
do {
|
|
if (!runFlag)
|
|
data = *src++;
|
|
|
|
if (left <= theX) {
|
|
*mask_ptr = data;
|
|
mask_ptr += _numStrips;
|
|
}
|
|
theY++;
|
|
if (theY >= height) {
|
|
if (left <= theX) {
|
|
mask_ptr -= _numStrips * height - 1;
|
|
}
|
|
theY = 0;
|
|
theX += 8;
|
|
if (theX >= right)
|
|
break;
|
|
}
|
|
} while (--run);
|
|
run = *src++;
|
|
}
|
|
}
|
|
|
|
int Gdi::getZPlanes(const byte *ptr, const byte *zplane_list[9], bool bmapImage) const {
|
|
int numzbuf;
|
|
int i;
|
|
|
|
if ((_vm->_game.features & GF_SMALL_HEADER) || _vm->_game.version == 8)
|
|
zplane_list[0] = ptr;
|
|
else if (bmapImage)
|
|
zplane_list[0] = _vm->findResource(MKTAG('B','M','A','P'), ptr);
|
|
else
|
|
zplane_list[0] = _vm->findResource(MKTAG('S','M','A','P'), ptr);
|
|
|
|
if (_zbufferDisabled)
|
|
numzbuf = 0;
|
|
else if (_numZBuffer <= 1 || (_vm->_game.version <= 2))
|
|
numzbuf = _numZBuffer;
|
|
else {
|
|
numzbuf = _numZBuffer;
|
|
assert(numzbuf <= 9);
|
|
|
|
if (_vm->_game.id == GID_LOOM && _vm->_game.platform == Common::kPlatformPCEngine) {
|
|
zplane_list[1] = nullptr;
|
|
} else if (_vm->_game.features & GF_SMALL_HEADER) {
|
|
if (_vm->_game.features & GF_16COLOR)
|
|
zplane_list[1] = ptr + READ_LE_UINT16(ptr);
|
|
else {
|
|
zplane_list[1] = ptr + READ_LE_UINT32(ptr);
|
|
if (_vm->_game.features & GF_OLD256) {
|
|
if (0 == READ_LE_UINT32(zplane_list[1]))
|
|
zplane_list[1] = nullptr;
|
|
}
|
|
}
|
|
for (i = 2; i < numzbuf; i++) {
|
|
zplane_list[i] = zplane_list[i-1] + READ_LE_UINT16(zplane_list[i-1]);
|
|
}
|
|
} else if (_vm->_game.version == 8) {
|
|
// Find the OFFS chunk of the ZPLN chunk
|
|
const byte *zplnOffsChunkStart = ptr + 24 + READ_BE_UINT32(ptr + 12);
|
|
|
|
// Each ZPLN contains a WRAP chunk, which has (as always) an OFFS subchunk pointing
|
|
// at ZSTR chunks. These once more contain a WRAP chunk which contains nothing but
|
|
// an OFFS chunk. The content of this OFFS chunk contains the offsets to the
|
|
// Z-planes.
|
|
// We do not directly make use of this, but rather hard code offsets (like we do
|
|
// for all other Scumm-versions, too). Clearly this is a bit hackish, but works
|
|
// well enough, and there is no reason to assume that there are any cases where it
|
|
// might fail. Still, doing this properly would have the advantage of catching
|
|
// invalid/damaged data files, and allow us to exit gracefully instead of segfaulting.
|
|
for (i = 1; i < numzbuf; i++) {
|
|
zplane_list[i] = zplnOffsChunkStart + READ_LE_UINT32(zplnOffsChunkStart + 4 + i*4) + 16;
|
|
}
|
|
} else {
|
|
const uint32 zplane_tags[] = {
|
|
MKTAG('Z','P','0','0'),
|
|
MKTAG('Z','P','0','1'),
|
|
MKTAG('Z','P','0','2'),
|
|
MKTAG('Z','P','0','3'),
|
|
MKTAG('Z','P','0','4')
|
|
};
|
|
|
|
for (i = 1; i < numzbuf; i++) {
|
|
zplane_list[i] = _vm->findResource(zplane_tags[i], ptr);
|
|
}
|
|
}
|
|
}
|
|
|
|
return numzbuf;
|
|
}
|
|
|
|
/**
|
|
* Draw a bitmap onto a virtual screen. This is main drawing method for room backgrounds
|
|
* and objects, used throughout all SCUMM versions.
|
|
*/
|
|
void Gdi::drawBitmap(const byte *ptr, VirtScreen *vs, int x, const int y, const int width, const int height,
|
|
int stripnr, int numstrip, byte flag) {
|
|
assert(ptr);
|
|
assert(height > 0);
|
|
|
|
byte *dstPtr;
|
|
const byte *smap_ptr;
|
|
const byte *zplane_list[9];
|
|
int numzbuf;
|
|
int sx;
|
|
bool transpStrip = false;
|
|
|
|
// Check whether lights are turned on or not
|
|
const bool lightsOn = _vm->isLightOn();
|
|
|
|
if ((_vm->_game.features & GF_SMALL_HEADER) || _vm->_game.version == 8) {
|
|
smap_ptr = ptr;
|
|
} else {
|
|
smap_ptr = _vm->findResource(MKTAG('S','M','A','P'), ptr);
|
|
assert(smap_ptr);
|
|
}
|
|
|
|
numzbuf = getZPlanes(ptr, zplane_list, false);
|
|
|
|
if (y + height > vs->h) {
|
|
warning("Gdi::drawBitmap, strip drawn to %d below window bottom %d", y + height, vs->h);
|
|
}
|
|
|
|
#ifndef DISABLE_TOWNS_DUAL_LAYER_MODE
|
|
if (_vm->_townsPaletteFlags & 2) {
|
|
int cx = (x - _vm->_screenStartStrip) << 3;
|
|
_vm->_textSurface.fillRect(Common::Rect(cx * _vm->_textSurfaceMultiplier, y * _vm->_textSurfaceMultiplier, (cx + width - 1) * _vm->_textSurfaceMultiplier, (y + height - 1) * _vm->_textSurfaceMultiplier), 0);
|
|
}
|
|
#endif
|
|
|
|
_vertStripNextInc = height * vs->pitch - 1 * vs->format.bytesPerPixel;
|
|
|
|
_objectMode = (flag & dbObjectMode) == dbObjectMode;
|
|
prepareDrawBitmap(ptr, vs, x, y, width, height, stripnr, numstrip);
|
|
|
|
sx = x - vs->xstart / 8;
|
|
if (sx < 0) {
|
|
numstrip -= -sx;
|
|
x += -sx;
|
|
stripnr += -sx;
|
|
sx = 0;
|
|
}
|
|
|
|
// Compute the number of strips we have to iterate over.
|
|
// TODO/FIXME: The computation of its initial value looks very fishy.
|
|
// It was added as a kind of hack to fix some corner cases, but it compares
|
|
// the room width to the virtual screen width; but the former should always
|
|
// be bigger than the latter (except for MM NES, maybe)... strange
|
|
int limit = MAX(_vm->_roomWidth, (int)vs->w) / 8 - x;
|
|
if (limit > numstrip)
|
|
limit = numstrip;
|
|
if (limit > _numStrips - sx)
|
|
limit = _numStrips - sx;
|
|
for (int k = 0; k < limit; ++k, ++stripnr, ++sx, ++x) {
|
|
if (y < vs->tdirty[sx])
|
|
vs->tdirty[sx] = y;
|
|
|
|
if (y + height > vs->bdirty[sx])
|
|
vs->bdirty[sx] = y + height;
|
|
|
|
// In the case of a double buffered virtual screen, we draw to
|
|
// the backbuffer, otherwise to the primary surface memory.
|
|
if (vs->hasTwoBuffers)
|
|
dstPtr = vs->backBuf + y * vs->pitch + (x * 8 * vs->format.bytesPerPixel);
|
|
else
|
|
dstPtr = (byte *)vs->getBasePtr(x * 8, y);
|
|
|
|
transpStrip = drawStrip(dstPtr, vs, x, y, width, height, stripnr, smap_ptr);
|
|
|
|
// COMI and HE games only uses flag value
|
|
if (_vm->_game.version == 8 || _vm->_game.heversion >= 60)
|
|
transpStrip = true;
|
|
|
|
if (vs->hasTwoBuffers) {
|
|
byte *frontBuf = (byte *)vs->getBasePtr(x * 8, y);
|
|
if (lightsOn)
|
|
copy8Col(frontBuf, vs->pitch, dstPtr, height, vs->format.bytesPerPixel);
|
|
else
|
|
clear8Col(frontBuf, vs->pitch, height, vs->format.bytesPerPixel);
|
|
}
|
|
|
|
decodeMask(x, y, width, height, stripnr, numzbuf, zplane_list, transpStrip, flag);
|
|
|
|
#if 0
|
|
// HACK: blit mask(s) onto normal screen. Useful to debug masking
|
|
for (int i = 0; i < numzbuf; i++) {
|
|
byte *dst1, *dst2;
|
|
|
|
dst1 = dst2 = (byte *)vs->pixels + y * vs->pitch + x * 8;
|
|
if (vs->hasTwoBuffers)
|
|
dst2 = vs->backBuf + y * vs->pitch + x * 8;
|
|
byte *mask_ptr = getMaskBuffer(x, y, i);
|
|
|
|
for (int h = 0; h < height; h++) {
|
|
int maskbits = *mask_ptr;
|
|
for (int j = 0; j < 8; j++) {
|
|
if (maskbits & 0x80)
|
|
dst1[j] = dst2[j] = 12 + i;
|
|
maskbits <<= 1;
|
|
}
|
|
dst1 += vs->pitch;
|
|
dst2 += vs->pitch;
|
|
mask_ptr += _numStrips;
|
|
}
|
|
}
|
|
#endif
|
|
}
|
|
}
|
|
|
|
bool Gdi::drawStrip(byte *dstPtr, VirtScreen *vs, int x, int y, const int width, const int height,
|
|
int stripnr, const byte *smap_ptr) {
|
|
// Do some input verification and make sure the strip/strip offset
|
|
// are actually valid. Normally, this should never be a problem,
|
|
// but if e.g. a savegame gets corrupted, we can easily get into
|
|
// trouble here. See also bug #1191.
|
|
int offset = -1, smapLen;
|
|
if (_vm->_game.features & GF_16COLOR) {
|
|
smapLen = READ_LE_UINT16(smap_ptr);
|
|
if (stripnr * 2 + 2 < smapLen) {
|
|
offset = READ_LE_UINT16(smap_ptr + stripnr * 2 + 2);
|
|
}
|
|
} else if (_vm->_game.features & GF_SMALL_HEADER) {
|
|
smapLen = READ_LE_UINT32(smap_ptr);
|
|
if (stripnr * 4 + 4 < smapLen)
|
|
offset = READ_LE_UINT32(smap_ptr + stripnr * 4 + 4);
|
|
} else if (_vm->_game.version == 8) {
|
|
smapLen = READ_BE_UINT32(smap_ptr + 4);
|
|
// Skip to the BSTR->WRAP->OFFS chunk
|
|
smap_ptr += 24;
|
|
if (stripnr * 4 + 8 < smapLen)
|
|
offset = READ_LE_UINT32(smap_ptr + stripnr * 4 + 8);
|
|
} else {
|
|
smapLen = READ_BE_UINT32(smap_ptr + 4);
|
|
if (stripnr * 4 + 8 < smapLen)
|
|
offset = READ_LE_UINT32(smap_ptr + stripnr * 4 + 8);
|
|
}
|
|
assertRange(0, offset, smapLen-1, "screen strip");
|
|
|
|
// Indy4 Amiga always uses the room or verb palette map to match colors to
|
|
// the currently setup palette, thus we need to select it over here too.
|
|
// Done like the original interpreter.
|
|
if (_vm->_game.platform == Common::kPlatformAmiga && _vm->_game.id == GID_INDY4) {
|
|
if (vs->number == kVerbVirtScreen)
|
|
_roomPalette = _vm->_verbPalette;
|
|
else
|
|
_roomPalette = _vm->_roomPalette;
|
|
}
|
|
|
|
// WORKAROUND: 256-color versions of Indy3 feature unusual pink and cyan
|
|
// horizontal lines when Indy meets Elsa in Berlin. This has only been fixed
|
|
// in the official FM-TOWNS release (with a few other subtle adjustments),
|
|
// but a simpler fix here is to override the pink and cyan colors in the local
|
|
// palette so that it matches the way these lines have been redrawn in the
|
|
// FM-TOWNS release. We take care not to apply this palette change to the
|
|
// text or inventory, as they still require the original colors.
|
|
if (_vm->_game.id == GID_INDY3 && (_vm->_game.features & GF_OLD256) && _vm->_game.platform != Common::kPlatformFMTowns
|
|
&& _vm->_roomResource == 46 && smapLen == 43159 && vs->number == kMainVirtScreen && _vm->enhancementEnabled(kEnhMinorBugFixes)) {
|
|
if (_roomPalette[11] == 11 && _roomPalette[86] == 86)
|
|
_roomPalette[11] = 86;
|
|
if (_roomPalette[13] == 13 && _roomPalette[80] == 80)
|
|
_roomPalette[13] = 80;
|
|
}
|
|
|
|
// WORKAROUND: In the CD version of MI1, the sign about how the dogs
|
|
// are only sleeping has a dark blue background instead of white. This
|
|
// makes the sign harder to read, so temporarily remap the color while
|
|
// drawing it. The text is also slightly different, but that is taken
|
|
// care of elsewhere.
|
|
//
|
|
// The SEGA CD version uses the old colors already, and the FM Towns
|
|
// version makes the text more readable by giving it a black outline.
|
|
|
|
else if (_vm->_game.id == GID_MONKEY &&
|
|
!(_vm->_game.features & GF_ULTIMATE_TALKIE) &&
|
|
_vm->_game.platform != Common::kPlatformSegaCD &&
|
|
_vm->_game.platform != Common::kPlatformFMTowns &&
|
|
_vm->_currentRoom == 36 &&
|
|
vs->number == kMainVirtScreen &&
|
|
y == 8 && x >= 7 && x <= 30 && height == 88 &&
|
|
_vm->enhancementEnabled(kEnhVisualChanges)) {
|
|
_roomPalette[47] = 15;
|
|
|
|
byte result = decompressBitmap(dstPtr, vs->pitch, smap_ptr + offset, height);
|
|
|
|
_roomPalette[47] = 47;
|
|
return result;
|
|
}
|
|
|
|
// WORKAROUND: In the French VGA floppy version of MI1, the easter egg
|
|
// poking fun at Sierra has a dark blue background instead of white,
|
|
// which causes similar legibility issues (the other VGA floppy
|
|
// translations are fine, and the French VGA Amiga and CD releases
|
|
// fixed this).
|
|
|
|
else if (_vm->_game.id == GID_MONKEY_VGA &&
|
|
_vm->_language == Common::FR_FRA &&
|
|
_vm->_game.platform != Common::kPlatformAmiga &&
|
|
_vm->_currentRoom == 11 &&
|
|
vs->number == kMainVirtScreen &&
|
|
y == 24 && x >= 28 && x <= 52 && height == 56 &&
|
|
_vm->enhancementEnabled(kEnhVisualChanges)) {
|
|
_roomPalette[1] = 15;
|
|
|
|
byte result = decompressBitmap(dstPtr, vs->pitch, smap_ptr + offset, height);
|
|
|
|
_roomPalette[1] = 1;
|
|
return result;
|
|
}
|
|
|
|
return decompressBitmap(dstPtr, vs->pitch, smap_ptr + offset, height);
|
|
}
|
|
|
|
bool GdiNES::drawStrip(byte *dstPtr, VirtScreen *vs, int x, int y, const int width, const int height,
|
|
int stripnr, const byte *smap_ptr) {
|
|
byte *mask_ptr = getMaskBuffer(x, y, 1);
|
|
drawStripNES(dstPtr, mask_ptr, vs->pitch, stripnr, y, height);
|
|
|
|
return false;
|
|
}
|
|
|
|
#ifdef USE_RGB_COLOR
|
|
bool GdiPCEngine::drawStrip(byte *dstPtr, VirtScreen *vs, int x, int y, const int width, const int height,
|
|
int stripnr, const byte *smap_ptr) {
|
|
byte *mask_ptr = getMaskBuffer(x, y, 1);
|
|
drawStripPCEngine(dstPtr, mask_ptr, vs->pitch, stripnr, y, height);
|
|
return false;
|
|
}
|
|
#endif
|
|
|
|
bool GdiV1::drawStrip(byte *dstPtr, VirtScreen *vs, int x, int y, const int width, const int height,
|
|
int stripnr, const byte *smap_ptr) {
|
|
if (_objectMode)
|
|
drawStripV1Object(dstPtr, vs->pitch, stripnr, width, height);
|
|
else
|
|
drawStripV1Background(dstPtr, vs->pitch, stripnr, height);
|
|
|
|
return false;
|
|
}
|
|
|
|
bool GdiV2::drawStrip(byte *dstPtr, VirtScreen *vs, int x, int y, const int width, const int height,
|
|
int stripnr, const byte *smap_ptr) {
|
|
// Do nothing here for V2 games - drawing was already handled.
|
|
return false;
|
|
}
|
|
|
|
void Gdi::decodeMask(int x, int y, const int width, const int height,
|
|
int stripnr, int numzbuf, const byte *zplane_list[9],
|
|
bool transpStrip, byte flag) {
|
|
int i;
|
|
byte *mask_ptr;
|
|
const byte *z_plane_ptr;
|
|
|
|
if (flag & dbDrawMaskOnAll) {
|
|
// Sam & Max uses dbDrawMaskOnAll for things like the inventory
|
|
// box and the speech icons. While these objects only have one
|
|
// mask, it should be applied to all the Z-planes in the room,
|
|
// i.e. they should mask every actor.
|
|
//
|
|
// This flag used to be called dbDrawMaskOnBoth, and all it
|
|
// would do was to mask Z-plane 0. (Z-plane 1 would also be
|
|
// masked, because what is now the else-clause used to be run
|
|
// always.) While this seems to be the only way there is to
|
|
// mask Z-plane 0, this wasn't good enough since actors in
|
|
// Z-planes >= 2 would not be masked.
|
|
//
|
|
// The flag is also used by The Dig and Full Throttle, but I
|
|
// don't know what for. At the time of writing, these games
|
|
// are still too unstable for me to investigate.
|
|
|
|
if (_vm->_game.version == 8)
|
|
z_plane_ptr = zplane_list[1] + READ_LE_UINT32(zplane_list[1] + stripnr * 4 + 8);
|
|
else
|
|
z_plane_ptr = zplane_list[1] + READ_LE_UINT16(zplane_list[1] + stripnr * 2 + 8);
|
|
for (i = 0; i < numzbuf; i++) {
|
|
mask_ptr = getMaskBuffer(x, y, i);
|
|
if (transpStrip && (flag & dbAllowMaskOr))
|
|
decompressMaskImgOr(mask_ptr, z_plane_ptr, height);
|
|
else
|
|
decompressMaskImg(mask_ptr, z_plane_ptr, height);
|
|
}
|
|
} else {
|
|
for (i = 1; i < numzbuf; i++) {
|
|
uint32 offs;
|
|
|
|
if (!zplane_list[i])
|
|
continue;
|
|
|
|
if (_vm->_game.features & GF_OLD_BUNDLE)
|
|
offs = READ_LE_UINT16(zplane_list[i] + stripnr * 2);
|
|
else if (_vm->_game.features & GF_OLD256)
|
|
offs = READ_LE_UINT16(zplane_list[i] + stripnr * 2 + 4);
|
|
else if (_vm->_game.features & GF_SMALL_HEADER)
|
|
offs = READ_LE_UINT16(zplane_list[i] + stripnr * 2 + 2);
|
|
else if (_vm->_game.version == 8)
|
|
offs = READ_LE_UINT32(zplane_list[i] + stripnr * 4 + 8);
|
|
else
|
|
offs = READ_LE_UINT16(zplane_list[i] + stripnr * 2 + 8);
|
|
|
|
mask_ptr = getMaskBuffer(x, y, i);
|
|
|
|
if (offs) {
|
|
z_plane_ptr = zplane_list[i] + offs;
|
|
|
|
if (transpStrip && (flag & dbAllowMaskOr)) {
|
|
decompressMaskImgOr(mask_ptr, z_plane_ptr, height);
|
|
} else {
|
|
decompressMaskImg(mask_ptr, z_plane_ptr, height);
|
|
}
|
|
|
|
} else {
|
|
if (!(transpStrip && (flag & dbAllowMaskOr)))
|
|
for (int h = 0; h < height; h++)
|
|
mask_ptr[h * _numStrips] = 0;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
void GdiHE::decodeMask(int x, int y, const int width, const int height,
|
|
int stripnr, int numzbuf, const byte *zplane_list[9],
|
|
bool transpStrip, byte flag) {
|
|
int i;
|
|
byte *mask_ptr;
|
|
const byte *z_plane_ptr;
|
|
|
|
for (i = 1; i < numzbuf; i++) {
|
|
uint32 offs;
|
|
|
|
if (!zplane_list[i])
|
|
continue;
|
|
|
|
offs = READ_LE_UINT16(zplane_list[i] + stripnr * 2 + 8);
|
|
|
|
mask_ptr = getMaskBuffer(x, y, i);
|
|
|
|
if (offs) {
|
|
z_plane_ptr = zplane_list[i] + offs;
|
|
|
|
if (_tmskPtr) {
|
|
const byte *tmsk = _tmskPtr + READ_LE_UINT16(_tmskPtr + stripnr * 2 + 8);
|
|
decompressTMSK(mask_ptr, tmsk, z_plane_ptr, height);
|
|
} else if (transpStrip && (flag & dbAllowMaskOr)) {
|
|
decompressMaskImgOr(mask_ptr, z_plane_ptr, height);
|
|
} else {
|
|
decompressMaskImg(mask_ptr, z_plane_ptr, height);
|
|
}
|
|
|
|
} else {
|
|
if (!(transpStrip && (flag & dbAllowMaskOr)))
|
|
for (int h = 0; h < height; h++)
|
|
mask_ptr[h * _numStrips] = 0;
|
|
}
|
|
}
|
|
}
|
|
|
|
void GdiNES::decodeMask(int x, int y, const int width, const int height,
|
|
int stripnr, int numzbuf, const byte *zplane_list[9],
|
|
bool transpStrip, byte flag) {
|
|
byte *mask_ptr = getMaskBuffer(x, y, 1);
|
|
drawStripNESMask(mask_ptr, stripnr, y, height);
|
|
}
|
|
|
|
#ifdef USE_RGB_COLOR
|
|
void GdiPCEngine::decodeMask(int x, int y, const int width, const int height,
|
|
int stripnr, int numzbuf, const byte *zplane_list[9],
|
|
bool transpStrip, byte flag) {
|
|
byte *mask_ptr = getMaskBuffer(x, y, 1);
|
|
drawStripPCEngineMask(mask_ptr, stripnr, y, height);
|
|
}
|
|
#endif
|
|
|
|
void GdiV1::decodeMask(int x, int y, const int width, const int height,
|
|
int stripnr, int numzbuf, const byte *zplane_list[9],
|
|
bool transpStrip, byte flag) {
|
|
byte *mask_ptr = getMaskBuffer(x, y, 1);
|
|
drawStripV1Mask(mask_ptr, stripnr, width, height);
|
|
}
|
|
|
|
void GdiV2::decodeMask(int x, int y, const int width, const int height,
|
|
int stripnr, int numzbuf, const byte *zplane_list[9],
|
|
bool transpStrip, byte flag) {
|
|
// Do nothing here for V2 games - zplane was already handled.
|
|
}
|
|
|
|
#ifdef ENABLE_HE
|
|
/**
|
|
* Draw a bitmap onto a virtual screen. This is main drawing method for room backgrounds
|
|
* used throughout HE71+ versions.
|
|
*
|
|
* @note This function essentially is a stripped down & special cased version of
|
|
* the generic Gdi::drawBitmap() method.
|
|
*/
|
|
void Gdi::drawBMAPBg(const byte *ptr, VirtScreen *vs) {
|
|
const byte *zPlanePtr;
|
|
byte *maskPtr;
|
|
const byte *zPlaneList[9];
|
|
|
|
const byte *bmapPtr = _vm->findResourceData(MKTAG('B','M','A','P'), ptr);
|
|
assert(bmapPtr);
|
|
|
|
byte code = *bmapPtr++;
|
|
byte *dst = vs->getBackPixels(0, 0);
|
|
|
|
switch (code) {
|
|
case BMCOMP_NMAJMIN_H4:
|
|
case BMCOMP_NMAJMIN_H5:
|
|
case BMCOMP_NMAJMIN_H6:
|
|
case BMCOMP_NMAJMIN_H7:
|
|
case BMCOMP_NMAJMIN_H8:
|
|
_decomp_shr = code - BMCOMP_NMAJMIN_H0; // Bits per pixel
|
|
_decomp_mask = bitMasks[_decomp_shr];
|
|
drawStripHE(dst, vs->pitch, bmapPtr, vs->w, vs->h, false);
|
|
break;
|
|
case BMCOMP_NMAJMIN_HT4:
|
|
case BMCOMP_NMAJMIN_HT5:
|
|
case BMCOMP_NMAJMIN_HT6:
|
|
case BMCOMP_NMAJMIN_HT7:
|
|
case BMCOMP_NMAJMIN_HT8:
|
|
_decomp_shr = code - BMCOMP_NMAJMIN_HT0; // Bits per pixel
|
|
_decomp_mask = bitMasks[_decomp_shr];
|
|
drawStripHE(dst, vs->pitch, bmapPtr, vs->w, vs->h, false);
|
|
break;
|
|
case BMCOMP_SOLID_COLOR_FILL:
|
|
{
|
|
WizRawPixel color = (WizRawPixel)(*bmapPtr);
|
|
|
|
if (_vm->_game.heversion > 90)
|
|
color = ((ScummEngine_v71he *)_vm)->_wiz->convert8BppToRawPixel(*bmapPtr, (WizRawPixel *)_vm->getHEPaletteSlot(1));
|
|
|
|
if (_vm->_game.heversion > 99 && _vm->VAR_COLOR_BLACK != 0xFF && _vm->VAR(_vm->VAR_COLOR_BLACK) == color)
|
|
break;
|
|
|
|
WizSimpleBitmap dstBitmap;
|
|
dstBitmap.bufferPtr = WizPxShrdBuffer(dst, false);
|
|
dstBitmap.bitmapWidth = vs->w;
|
|
dstBitmap.bitmapHeight = vs->h;
|
|
Common::Rect fillRect(0, 0, (dstBitmap.bitmapWidth - 1), (dstBitmap.bitmapHeight - 1));
|
|
|
|
((ScummEngine_v71he *)_vm)->_wiz->pgDrawSolidRect(&dstBitmap, &fillRect, color);
|
|
break;
|
|
}
|
|
default:
|
|
// Alternative russian freddi3 uses badly formatted bitmaps
|
|
debug(0, "Gdi::drawBMAPBg: default case %d", code);
|
|
}
|
|
|
|
((ScummEngine_v71he *)_vm)->backgroundToForegroundBlit(Common::Rect(vs->w, vs->h));
|
|
|
|
int numzbuf = getZPlanes(ptr, zPlaneList, true);
|
|
if (numzbuf <= 1)
|
|
return;
|
|
|
|
uint32 offs;
|
|
for (int stripnr = 0; stripnr < _numStrips; stripnr++) {
|
|
for (int i = 1; i < numzbuf; i++) {
|
|
if (!zPlaneList[i])
|
|
continue;
|
|
|
|
offs = READ_LE_UINT16(zPlaneList[i] + stripnr * 2 + 8);
|
|
maskPtr = getMaskBuffer(stripnr, 0, i);
|
|
|
|
if (offs) {
|
|
zPlanePtr = zPlaneList[i] + offs;
|
|
decompressMaskImg(maskPtr, zPlanePtr, vs->h);
|
|
}
|
|
}
|
|
|
|
#if 0
|
|
// HACK: blit mask(s) onto normal screen. Useful to debug masking
|
|
for (int i = 0; i < numzbuf; i++) {
|
|
byte *dst1 = (byte *)vs->pixels + stripnr * 8;
|
|
byte *dst2 = vs->backBuf + stripnr * 8;
|
|
|
|
mask_ptr = getMaskBuffer(stripnr, 0, i);
|
|
for (int h = 0; h < vs->h; h++) {
|
|
int maskbits = *mask_ptr;
|
|
for (int j = 0; j < 8; j++) {
|
|
if (maskbits & 0x80)
|
|
dst1[j] = dst2[j] = 12 + i;
|
|
maskbits <<= 1;
|
|
}
|
|
dst1 += vs->pitch;
|
|
dst2 += vs->pitch;
|
|
mask_ptr += _numStrips;
|
|
}
|
|
}
|
|
#endif
|
|
}
|
|
}
|
|
|
|
void Gdi::drawBMAPObject(const byte *ptr, VirtScreen *vs, int obj, int x, int y, int w, int h) {
|
|
const byte *bmapPtr = _vm->findResourceData(MKTAG('B','M','A','P'), ptr);
|
|
assert(bmapPtr);
|
|
|
|
byte code = *bmapPtr++;
|
|
int scrX = _vm->_screenStartStrip * 8 * _vm->_bytesPerPixel;
|
|
WizPxShrdBuffer dst(_vm->_virtscr[kMainVirtScreen].backBuf + scrX, false);
|
|
|
|
switch (code) {
|
|
case BMCOMP_RLE8BIT:
|
|
case BMCOMP_TRLE8BIT:
|
|
{
|
|
Common::Rect rScreen(0, 0, vs->w, vs->h);
|
|
((ScummEngine_v71he *)_vm)->_wiz->auxDecompTRLEImage(dst(), bmapPtr, vs->w, vs->h, x + scrX, y, w, h, &rScreen, nullptr);
|
|
break;
|
|
}
|
|
case BMCOMP_SOLID_COLOR_FILL:
|
|
{
|
|
ScummEngine_v71he *tmpVm = ((ScummEngine_v71he *)_vm);
|
|
WizRawPixel color = tmpVm->_wiz->convert8BppToRawPixel(*bmapPtr, (WizRawPixel *)_vm->getHEPaletteSlot(1));
|
|
|
|
if ((_vm->_game.heversion < 100) || (tmpVm->VAR_WIZ_TRANSPARENT_COLOR != 0xFF && _vm->VAR(tmpVm->VAR_WIZ_TRANSPARENT_COLOR) == *bmapPtr))
|
|
break;
|
|
|
|
WizSimpleBitmap dstBitmap;
|
|
dstBitmap.bufferPtr = dst;
|
|
dstBitmap.bitmapWidth = w;
|
|
dstBitmap.bitmapHeight = h;
|
|
Common::Rect fillRect(x + scrX, y, x + scrX + w - 1, y + h - 1);
|
|
|
|
((ScummEngine_v71he *)_vm)->_wiz->pgDrawSolidRect(&dstBitmap, &fillRect, color);
|
|
break;
|
|
}
|
|
default:
|
|
error("Gdi::drawBMAPObject(): Unhandled code %d", code);
|
|
}
|
|
|
|
Common::Rect renderArea, clipArea;
|
|
|
|
((ScummEngine_v71he *)_vm)->_wiz->makeSizedRectAt(&renderArea, x, y, w, h);
|
|
((ScummEngine_v71he *)_vm)->_wiz->makeSizedRect(&clipArea, vs->w, vs->h);
|
|
|
|
((ScummEngine_v71he *)_vm)->_wiz->findRectOverlap(&renderArea, &clipArea);
|
|
|
|
// Unless the image was entirely clipped, copy newly decompressed
|
|
// pixels from the background buffer into the foreground buffer...
|
|
if (((ScummEngine_v71he *)_vm)->_wiz->isRectValid(renderArea)) {
|
|
((ScummEngine_v71he *)_vm)->backgroundToForegroundBlit(renderArea);
|
|
}
|
|
}
|
|
|
|
void ScummEngine_v70he::backgroundToForegroundBlit(Common::Rect rect, int dirtybit) {
|
|
byte *src, *dst;
|
|
VirtScreen *vs = &_virtscr[kMainVirtScreen];
|
|
|
|
if (rect.top > vs->h || rect.bottom < 0)
|
|
return;
|
|
|
|
if (rect.left > vs->w || rect.right < 0)
|
|
return;
|
|
|
|
rect.left = MAX(0, (int)rect.left);
|
|
rect.left = MIN((int)rect.left, (int)vs->w - 1);
|
|
|
|
rect.right = MAX(0, (int)rect.right);
|
|
rect.right = MIN((int)rect.right, (int)vs->w - 1);
|
|
|
|
rect.top = MAX(0, (int)rect.top);
|
|
rect.top = MIN((int)rect.top, (int)vs->h - 1);
|
|
|
|
rect.bottom = MAX(0, (int)rect.bottom);
|
|
rect.bottom = MIN((int)rect.bottom, (int)vs->h - 1);
|
|
|
|
const int rw = rect.width() + 1;
|
|
const int rh = rect.height() + 1;
|
|
|
|
if (rw == 0 || rh == 0)
|
|
return;
|
|
|
|
src = _virtscr[kMainVirtScreen].getBackPixels(rect.left, rect.top);
|
|
dst = _virtscr[kMainVirtScreen].getPixels(rect.left, rect.top);
|
|
|
|
assert(rw <= _screenWidth && rw > 0);
|
|
assert(rh <= _screenHeight && rh > 0);
|
|
blit(dst, _virtscr[kMainVirtScreen].pitch, src, _virtscr[kMainVirtScreen].pitch, rw, rh, vs->format.bytesPerPixel);
|
|
|
|
rect.bottom++;
|
|
markRectAsDirty(kMainVirtScreen, rect, dirtybit);
|
|
}
|
|
#endif
|
|
|
|
/**
|
|
* Reset the background behind an actor or blast object.
|
|
*/
|
|
void Gdi::resetBackground(int top, int bottom, int strip) {
|
|
VirtScreen *vs = &_vm->_virtscr[kMainVirtScreen];
|
|
byte *backbuff_ptr, *bgbak_ptr;
|
|
int numLinesToProcess;
|
|
|
|
if (top < 0)
|
|
top = 0;
|
|
|
|
if (bottom > vs->h)
|
|
bottom = vs->h;
|
|
|
|
if (top >= bottom)
|
|
return;
|
|
|
|
assert(0 <= strip && strip < _numStrips);
|
|
|
|
if (top < vs->tdirty[strip])
|
|
vs->tdirty[strip] = top;
|
|
|
|
if (bottom > vs->bdirty[strip])
|
|
vs->bdirty[strip] = bottom;
|
|
|
|
bgbak_ptr = (byte *)vs->backBuf + top * vs->pitch + (strip + vs->xstart/8) * 8 * vs->format.bytesPerPixel;
|
|
backbuff_ptr = (byte *)vs->getBasePtr((strip + vs->xstart/8) * 8, top);
|
|
|
|
numLinesToProcess = bottom - top;
|
|
if (numLinesToProcess) {
|
|
if (_vm->isLightOn()) {
|
|
copy8Col(backbuff_ptr, vs->pitch, bgbak_ptr, numLinesToProcess, vs->format.bytesPerPixel);
|
|
} else {
|
|
clear8Col(backbuff_ptr, vs->pitch, numLinesToProcess, vs->format.bytesPerPixel);
|
|
}
|
|
}
|
|
}
|
|
|
|
bool Gdi::decompressBitmap(byte *dst, int dstPitch, const byte *src, int numLinesToProcess) {
|
|
assert(numLinesToProcess);
|
|
|
|
if (_vm->_game.features & GF_16COLOR) {
|
|
drawStripEGA(dst, dstPitch, src, numLinesToProcess);
|
|
return false;
|
|
}
|
|
|
|
if ((_vm->_game.platform == Common::kPlatformAmiga) && (_vm->_game.version >= 4))
|
|
_paletteMod = 16;
|
|
else
|
|
_paletteMod = 0;
|
|
|
|
byte code = *src++;
|
|
bool transpStrip = false;
|
|
_decomp_shr = code % 10;
|
|
_decomp_mask = 0xFF >> (8 - _decomp_shr);
|
|
|
|
switch (code) {
|
|
case BMCOMP_RAW256:
|
|
drawStripRaw(dst, dstPitch, src, numLinesToProcess, false);
|
|
break;
|
|
|
|
case BMCOMP_TOWNS_2:
|
|
unkDecode8(dst, dstPitch, src, numLinesToProcess); /* Ender - Zak256/Indy256 */
|
|
break;
|
|
|
|
case BMCOMP_TOWNS_3:
|
|
unkDecode9(dst, dstPitch, src, numLinesToProcess); /* Ender - Zak256/Indy256 */
|
|
break;
|
|
|
|
case BMCOMP_TOWNS_4:
|
|
unkDecode10(dst, dstPitch, src, numLinesToProcess); /* Ender - Zak256/Indy256 */
|
|
break;
|
|
|
|
case BMCOMP_TOWNS_7:
|
|
unkDecode11(dst, dstPitch, src, numLinesToProcess); /* Ender - Zak256/Indy256 */
|
|
break;
|
|
|
|
case BMCOMP_TRLE8BIT:
|
|
// Used in 3DO versions of HE games
|
|
transpStrip = true;
|
|
drawStrip3DO(dst, dstPitch, src, numLinesToProcess, true);
|
|
break;
|
|
|
|
case BMCOMP_RLE8BIT:
|
|
drawStrip3DO(dst, dstPitch, src, numLinesToProcess, false);
|
|
break;
|
|
|
|
case BMCOMP_PIX32:
|
|
// Used in Amiga version of Monkey Island 1
|
|
drawStripEGA(dst, dstPitch, src, numLinesToProcess);
|
|
break;
|
|
|
|
case BMCOMP_ZIGZAG_V4:
|
|
case BMCOMP_ZIGZAG_V5:
|
|
case BMCOMP_ZIGZAG_V6:
|
|
case BMCOMP_ZIGZAG_V7:
|
|
case BMCOMP_ZIGZAG_V8:
|
|
drawStripBasicV(dst, dstPitch, src, numLinesToProcess, false);
|
|
break;
|
|
|
|
case BMCOMP_ZIGZAG_H4:
|
|
case BMCOMP_ZIGZAG_H5:
|
|
case BMCOMP_ZIGZAG_H6:
|
|
case BMCOMP_ZIGZAG_H7:
|
|
case BMCOMP_ZIGZAG_H8:
|
|
drawStripBasicH(dst, dstPitch, src, numLinesToProcess, false);
|
|
break;
|
|
|
|
case BMCOMP_ZIGZAG_VT4:
|
|
case BMCOMP_ZIGZAG_VT5:
|
|
case BMCOMP_ZIGZAG_VT6:
|
|
case BMCOMP_ZIGZAG_VT7:
|
|
case BMCOMP_ZIGZAG_VT8:
|
|
transpStrip = true;
|
|
drawStripBasicV(dst, dstPitch, src, numLinesToProcess, true);
|
|
break;
|
|
|
|
case BMCOMP_ZIGZAG_HT4:
|
|
case BMCOMP_ZIGZAG_HT5:
|
|
case BMCOMP_ZIGZAG_HT6:
|
|
case BMCOMP_ZIGZAG_HT7:
|
|
case BMCOMP_ZIGZAG_HT8:
|
|
transpStrip = true;
|
|
drawStripBasicH(dst, dstPitch, src, numLinesToProcess, true);
|
|
break;
|
|
|
|
case BMCOMP_MAJMIN_H4:
|
|
case BMCOMP_MAJMIN_H5:
|
|
case BMCOMP_MAJMIN_H6:
|
|
case BMCOMP_MAJMIN_H7:
|
|
case BMCOMP_MAJMIN_H8:
|
|
case BMCOMP_RMAJMIN_H4:
|
|
case BMCOMP_RMAJMIN_H5:
|
|
case BMCOMP_RMAJMIN_H6:
|
|
case BMCOMP_RMAJMIN_H7:
|
|
case BMCOMP_RMAJMIN_H8:
|
|
drawStripComplex(dst, dstPitch, src, numLinesToProcess, false);
|
|
break;
|
|
|
|
case BMCOMP_MAJMIN_HT4:
|
|
case BMCOMP_MAJMIN_HT5:
|
|
case BMCOMP_MAJMIN_HT6:
|
|
case BMCOMP_MAJMIN_HT7:
|
|
case BMCOMP_MAJMIN_HT8:
|
|
case BMCOMP_RMAJMIN_HT4:
|
|
case BMCOMP_RMAJMIN_HT5:
|
|
case BMCOMP_RMAJMIN_HT6:
|
|
case BMCOMP_RMAJMIN_HT7:
|
|
case BMCOMP_RMAJMIN_HT8:
|
|
transpStrip = true;
|
|
drawStripComplex(dst, dstPitch, src, numLinesToProcess, true);
|
|
break;
|
|
|
|
case BMCOMP_NMAJMIN_H4:
|
|
case BMCOMP_NMAJMIN_H5:
|
|
case BMCOMP_NMAJMIN_H6:
|
|
case BMCOMP_NMAJMIN_H7:
|
|
case BMCOMP_NMAJMIN_H8:
|
|
drawStripHE(dst, dstPitch, src, 8, numLinesToProcess, false);
|
|
break;
|
|
|
|
case BMCOMP_CUSTOM_RU_TR: // Triggered by Russian water
|
|
case BMCOMP_NMAJMIN_HT4:
|
|
case BMCOMP_NMAJMIN_HT5:
|
|
case BMCOMP_NMAJMIN_HT6:
|
|
case BMCOMP_NMAJMIN_HT7:
|
|
case BMCOMP_NMAJMIN_HT8:
|
|
transpStrip = true;
|
|
drawStripHE(dst, dstPitch, src, 8, numLinesToProcess, true);
|
|
break;
|
|
|
|
case BMCOMP_TPIX256:
|
|
drawStripRaw(dst, dstPitch, src, numLinesToProcess, true);
|
|
break;
|
|
|
|
default:
|
|
error("Gdi::decompressBitmap: default case %d", code);
|
|
}
|
|
|
|
return transpStrip;
|
|
}
|
|
|
|
void Gdi::decompressMaskImg(byte *dst, const byte *src, int height) const {
|
|
byte b, c;
|
|
|
|
while (height) {
|
|
b = *src++;
|
|
|
|
if (b & 0x80) {
|
|
b &= 0x7F;
|
|
c = *src++;
|
|
|
|
do {
|
|
*dst = c;
|
|
dst += _numStrips;
|
|
--height;
|
|
} while (--b && height);
|
|
} else {
|
|
do {
|
|
*dst = *src++;
|
|
dst += _numStrips;
|
|
--height;
|
|
} while (--b && height);
|
|
}
|
|
}
|
|
}
|
|
|
|
void GdiHE::decompressTMSK(byte *dst, const byte *tmsk, const byte *src, int height) const {
|
|
byte srcbits = 0;
|
|
byte srcFlag = 0;
|
|
byte maskFlag = 0;
|
|
|
|
byte srcCount = 0;
|
|
byte maskCount = 0;
|
|
byte maskbits = 0;
|
|
|
|
while (height) {
|
|
if (srcCount == 0) {
|
|
srcCount = *src++;
|
|
srcFlag = srcCount & 0x80;
|
|
if (srcFlag) {
|
|
srcCount &= 0x7F;
|
|
srcbits = *src++;
|
|
}
|
|
}
|
|
|
|
if (srcFlag == 0) {
|
|
srcbits = *src++;
|
|
}
|
|
|
|
srcCount--;
|
|
|
|
if (maskCount == 0) {
|
|
maskCount = *tmsk++;
|
|
maskFlag = maskCount & 0x80;
|
|
if (maskFlag) {
|
|
maskCount &= 0x7F;
|
|
maskbits = *tmsk++;
|
|
}
|
|
}
|
|
|
|
if (maskFlag == 0) {
|
|
maskbits = *tmsk++;
|
|
}
|
|
|
|
maskCount--;
|
|
|
|
*dst |= srcbits;
|
|
*dst &= ~maskbits;
|
|
|
|
dst += _numStrips;
|
|
height--;
|
|
}
|
|
}
|
|
|
|
void Gdi::decompressMaskImgOr(byte *dst, const byte *src, int height) const {
|
|
byte b, c;
|
|
|
|
while (height) {
|
|
b = *src++;
|
|
|
|
if (b & 0x80) {
|
|
b &= 0x7F;
|
|
c = *src++;
|
|
|
|
do {
|
|
*dst |= c;
|
|
dst += _numStrips;
|
|
--height;
|
|
} while (--b && height);
|
|
} else {
|
|
do {
|
|
*dst |= *src++;
|
|
dst += _numStrips;
|
|
--height;
|
|
} while (--b && height);
|
|
}
|
|
}
|
|
}
|
|
|
|
static void decodeNESTileData(const byte *src, byte *dest) {
|
|
int len = READ_LE_UINT16(src); src += 2;
|
|
const byte *end = src + len;
|
|
src++; // skip number-of-tiles byte, assume it is correct
|
|
while (src < end) {
|
|
byte data = *src++;
|
|
for (int j = 0; j < (data & 0x7F); j++)
|
|
*dest++ = (data & 0x80) ? (*src++) : (*src);
|
|
if (!(data & 0x80))
|
|
src++;
|
|
}
|
|
}
|
|
|
|
void ScummEngine::decodeNESBaseTiles() {
|
|
byte *basetiles = getResourceAddress(rtCostume, 37);
|
|
_NESBaseTiles = basetiles[2];
|
|
decodeNESTileData(basetiles, _NESPatTable[1]);
|
|
}
|
|
|
|
static const int v1MMNEScostTables[2][6] = {
|
|
/* desc lens offs data gfx pal */
|
|
{ 25, 27, 29, 31, 33, 35},
|
|
{ 26, 28, 30, 32, 34, 36}
|
|
};
|
|
|
|
void ScummEngine::NES_loadCostumeSet(int n) {
|
|
int i;
|
|
_NESCostumeSet = n;
|
|
|
|
_NEScostdesc = getResourceAddress(rtCostume, v1MMNEScostTables[n][0]) + 2;
|
|
_NEScostlens = getResourceAddress(rtCostume, v1MMNEScostTables[n][1]) + 2;
|
|
_NEScostoffs = getResourceAddress(rtCostume, v1MMNEScostTables[n][2]) + 2;
|
|
_NEScostdata = getResourceAddress(rtCostume, v1MMNEScostTables[n][3]) + 2;
|
|
decodeNESTileData(getResourceAddress(rtCostume, v1MMNEScostTables[n][4]), _NESPatTable[0]);
|
|
byte *palette = getResourceAddress(rtCostume, v1MMNEScostTables[n][5]) + 2;
|
|
for (i = 0; i < 16; i++) {
|
|
byte c = *palette++;
|
|
_NESPalette[1][i] = c;
|
|
}
|
|
|
|
}
|
|
|
|
void GdiNES::decodeNESGfx(const byte *room) {
|
|
const byte *gdata = room + READ_LE_UINT16(room + 0x0A);
|
|
int tileset = *gdata++;
|
|
int width = READ_LE_UINT16(room + 0x04);
|
|
// int height = READ_LE_UINT16(room + 0x06);
|
|
int i, j, n;
|
|
|
|
// We have narrow room. so expand it
|
|
if (width < 32) {
|
|
_vm->_NESStartStrip = (32 - width) >> 1;
|
|
} else {
|
|
_vm->_NESStartStrip = 0;
|
|
}
|
|
|
|
decodeNESTileData(_vm->getResourceAddress(rtCostume, 37 + tileset), _vm->_NESPatTable[1] + _vm->_NESBaseTiles * 16);
|
|
for (i = 0; i < 16; i++) {
|
|
byte c = *gdata++;
|
|
_vm->_NESPalette[0][i] = c;
|
|
}
|
|
for (i = 0; i < 16; i++) {
|
|
_NES.nametable[i][0] = _NES.nametable[i][1] = 0;
|
|
n = 0;
|
|
while (n < width) {
|
|
byte data = *gdata++;
|
|
for (j = 0; j < (data & 0x7F); j++)
|
|
_NES.nametable[i][2 + n++] = (data & 0x80) ? (*gdata++) : (*gdata);
|
|
if (!(data & 0x80))
|
|
gdata++;
|
|
}
|
|
_NES.nametable[i][width+2] = _NES.nametable[i][width+3] = 0;
|
|
}
|
|
memcpy(_NES.nametableObj,_NES.nametable, 16*64);
|
|
|
|
const byte *adata = room + READ_LE_UINT16(room + 0x0C);
|
|
for (n = 0; n < 64;) {
|
|
byte data = *adata++;
|
|
for (j = 0; j < (data & 0x7F); j++)
|
|
_NES.attributes[n++] = (data & 0x80) ? (*adata++) : (*adata);
|
|
if (!(n & 7) && (width == 0x1C))
|
|
n += 8;
|
|
if (!(data & 0x80))
|
|
adata++;
|
|
}
|
|
memcpy(_NES.attributesObj, _NES.attributes, 64);
|
|
|
|
const byte *mdata = room + READ_LE_UINT16(room + 0x0E);
|
|
int mask = *mdata++;
|
|
if (mask == 0) {
|
|
_NES.hasmask = false;
|
|
return;
|
|
}
|
|
_NES.hasmask = true;
|
|
if (mask != 1)
|
|
debug(0,"NES room %i has irregular mask count %i",_vm->_currentRoom,mask);
|
|
int mwidth = *mdata++;
|
|
for (i = 0; i < 16; i++) {
|
|
n = 0;
|
|
while (n < mwidth) {
|
|
byte data = *mdata++;
|
|
for (j = 0; j < (data & 0x7F); j++)
|
|
_NES.masktable[i][n++] = (data & 0x80) ? (*mdata++) : (*mdata);
|
|
if (!(data & 0x80))
|
|
mdata++;
|
|
}
|
|
}
|
|
memcpy(_NES.masktableObj, _NES.masktable, 16*8);
|
|
}
|
|
|
|
void GdiNES::decodeNESObject(const byte *ptr, int xpos, int ypos, int width, int height) {
|
|
int x, y;
|
|
|
|
_NES.objX = xpos;
|
|
|
|
// decode tile update data
|
|
width /= 8;
|
|
ypos /= 8;
|
|
height /= 8;
|
|
|
|
for (y = ypos; y < ypos + height; y++) {
|
|
x = xpos;
|
|
while (x < xpos + width) {
|
|
byte len = *ptr++;
|
|
for (int i = 0; i < (len & 0x7F); i++)
|
|
_NES.nametableObj[y][2 + x++] = (len & 0x80) ? (*ptr++) : (*ptr);
|
|
if (!(len & 0x80))
|
|
ptr++;
|
|
}
|
|
}
|
|
|
|
int ax, ay;
|
|
// decode attribute update data
|
|
y = height / 2;
|
|
ay = ypos;
|
|
while (y) {
|
|
ax = xpos + 2;
|
|
x = 0;
|
|
int adata = 0;
|
|
while (x < (width >> 1)) {
|
|
if (!(x & 3))
|
|
adata = *ptr++;
|
|
byte *dest = &_NES.attributesObj[((ay << 2) & 0x30) | ((ax >> 2) & 0xF)];
|
|
|
|
int aand = 3;
|
|
int aor = adata & 3;
|
|
if (ay & 0x02) {
|
|
aand <<= 4;
|
|
aor <<= 4;
|
|
}
|
|
if (ax & 0x02) {
|
|
aand <<= 2;
|
|
aor <<= 2;
|
|
}
|
|
*dest = ((~aand) & *dest) | aor;
|
|
|
|
adata >>= 2;
|
|
ax += 2;
|
|
x++;
|
|
}
|
|
ay += 2;
|
|
y--;
|
|
}
|
|
|
|
// decode mask update data
|
|
if (!_NES.hasmask)
|
|
return;
|
|
int mx, mwidth;
|
|
int lmask, rmask;
|
|
mx = *ptr++;
|
|
mwidth = *ptr++;
|
|
lmask = *ptr++;
|
|
rmask = *ptr++;
|
|
|
|
for (y = 0; y < height; ++y) {
|
|
byte *dest = &_NES.masktableObj[y + ypos][mx];
|
|
*dest = (*dest & lmask) | *ptr++;
|
|
dest++;
|
|
for (x = 1; x < mwidth; x++) {
|
|
if (x + 1 == mwidth)
|
|
*dest = (*dest & rmask) | *ptr++;
|
|
else
|
|
*dest = *ptr++;
|
|
dest++;
|
|
}
|
|
}
|
|
}
|
|
|
|
void GdiNES::drawStripNES(byte *dst, byte *mask, int dstPitch, int stripnr, int top, int height) {
|
|
const byte darkPalette[16] = { 0x2d,0x1d,0x3d,0x20, 0x2d,0x1d,0x3d,0x20, 0x2d,0x1d,0x3d,0x20, 0x2d,0x1d,0x3d,0x20 };
|
|
const byte* stripPalette;
|
|
top /= 8;
|
|
height /= 8;
|
|
int x = stripnr + 2; // NES version has a 2 tile gap on each edge
|
|
|
|
// MM NES does not paint the background when lit with a flashlight
|
|
if (_vm->isLightOn())
|
|
stripPalette = _vm->_NESPalette[0];
|
|
else
|
|
stripPalette = darkPalette;
|
|
|
|
if (_objectMode)
|
|
x += _NES.objX; // for objects, need to start at the left edge of the object, not the screen
|
|
if (x > 63) {
|
|
debug(0,"NES tried to render invalid strip %i",stripnr);
|
|
return;
|
|
}
|
|
for (int y = top; y < top + height; y++) {
|
|
int palette = ((_objectMode ? _NES.attributesObj : _NES.attributes)[((y << 2) & 0x30) | ((x >> 2) & 0xF)] >> (((y & 2) << 1) | (x & 2))) & 0x3;
|
|
int tile = (_objectMode ? _NES.nametableObj : _NES.nametable)[y][x];
|
|
|
|
for (int i = 0; i < 8; i++) {
|
|
byte c0 = _vm->_NESPatTable[1][tile * 16 + i];
|
|
byte c1 = _vm->_NESPatTable[1][tile * 16 + i + 8];
|
|
for (int j = 0; j < 8; j++)
|
|
dst[j] = stripPalette[((c0 >> (7 - j)) & 1) | (((c1 >> (7 - j)) & 1) << 1) | (palette << 2)];
|
|
dst += dstPitch;
|
|
*mask = c0 | c1;
|
|
mask += _numStrips;
|
|
}
|
|
}
|
|
}
|
|
|
|
void GdiNES::drawStripNESMask(byte *dst, int stripnr, int top, int height) const {
|
|
top /= 8;
|
|
height /= 8;
|
|
int x = stripnr; // masks, unlike room graphics, should NOT be adjusted
|
|
|
|
if (_objectMode)
|
|
x += _NES.objX; // for objects, need to start at the left edge of the object, not the screen
|
|
if (x > 63) {
|
|
debug(0,"NES tried to mask invalid strip %i",stripnr);
|
|
return;
|
|
}
|
|
for (int y = top; y < top + height; y++) {
|
|
byte c;
|
|
if (_NES.hasmask)
|
|
c = (((_objectMode ? _NES.masktableObj : _NES.masktable)[y][x >> 3] >> (x & 7)) & 1) ? 0xFF : 0x00;
|
|
else
|
|
c = 0;
|
|
|
|
for (int i = 0; i < 8; i++) {
|
|
*dst &= c;
|
|
dst += _numStrips;
|
|
}
|
|
}
|
|
}
|
|
|
|
#ifdef USE_RGB_COLOR
|
|
void readOffsetTable(const byte *ptr, uint16 **table, int *count) {
|
|
int pos = 0;
|
|
*count = READ_LE_UINT16(ptr) / 2 + 1;
|
|
*table = (uint16 *)malloc(*count * sizeof(uint16));
|
|
for (int i = 0; i < *count; i++) {
|
|
(*table)[i] = READ_LE_UINT16(ptr + pos) + pos + 2;
|
|
pos += 2;
|
|
}
|
|
}
|
|
|
|
void decodeTileColor(byte cmd, byte *colors, int *rowIndex, int numRows) {
|
|
colors[(*rowIndex)++] = ((cmd) >> 4) & 0xF;
|
|
if (*rowIndex < numRows)
|
|
colors[(*rowIndex)++] = (cmd) & 0xF;
|
|
}
|
|
|
|
void GdiPCEngine::decodeStrip(const byte *ptr, uint16 *tiles, byte *colors, uint16 *masks, int numRows, bool isObject) {
|
|
int loopCnt;
|
|
uint16 lastTileData;
|
|
|
|
/*
|
|
* read tiles indices
|
|
*/
|
|
|
|
int rowIndex = 0;
|
|
if (isObject) {
|
|
loopCnt = numRows;
|
|
} else {
|
|
tiles[rowIndex++] = 0;
|
|
tiles[numRows - 1] = 0;
|
|
loopCnt = numRows - 1;
|
|
}
|
|
|
|
while (true) {
|
|
uint16 cmd = READ_LE_UINT16(ptr);
|
|
ptr += 2;
|
|
if (cmd & 0x8000) {
|
|
tiles[rowIndex - 1] = cmd & 0x0FFF;
|
|
} else if (cmd & 0x4000) {
|
|
tiles[numRows - 1] = cmd & 0x0FFF;
|
|
} else {
|
|
tiles[rowIndex++] = cmd;
|
|
lastTileData = cmd;
|
|
break;
|
|
}
|
|
}
|
|
|
|
while (rowIndex < loopCnt) {
|
|
byte cmd = *ptr++;
|
|
int cnt = cmd & 0x1F;
|
|
|
|
if (cmd & 0x80) {
|
|
for (int i = 0; i < cnt; ++i) {
|
|
tiles[rowIndex++] = lastTileData;
|
|
}
|
|
} else if (cmd & 0x40) {
|
|
for (int i = 0; i < cnt; ++i) {
|
|
++lastTileData;
|
|
tiles[rowIndex++] = lastTileData;
|
|
}
|
|
} else {
|
|
for (int i = 0; i < cnt; ++i) {
|
|
lastTileData = READ_LE_UINT16(ptr);
|
|
ptr += 2;
|
|
tiles[rowIndex++] = lastTileData;
|
|
}
|
|
}
|
|
}
|
|
|
|
/*
|
|
* read palette data
|
|
*/
|
|
|
|
rowIndex = 0;
|
|
byte cmd = *ptr++;
|
|
if (cmd == 0xFE) {
|
|
while (rowIndex < numRows) {
|
|
decodeTileColor(*ptr++, colors, &rowIndex, numRows);
|
|
}
|
|
} else {
|
|
byte lastCmd = cmd;
|
|
decodeTileColor(cmd, colors, &rowIndex, numRows);
|
|
while (rowIndex < numRows) {
|
|
cmd = *ptr++;
|
|
int cnt = cmd & 0x1F;
|
|
if (cmd & 0x80) {
|
|
for (int j = 0; j < cnt; ++j) {
|
|
decodeTileColor(lastCmd, colors, &rowIndex, numRows);
|
|
}
|
|
} else {
|
|
for (int j = 0; j < cnt; ++j) {
|
|
cmd = *ptr++;
|
|
decodeTileColor(cmd, colors, &rowIndex, numRows);
|
|
}
|
|
lastCmd = cmd;
|
|
}
|
|
}
|
|
}
|
|
|
|
/*
|
|
* read mask indices
|
|
*/
|
|
|
|
if (_distaff || _PCE.maskIDSize == 0 || numRows > 18) {
|
|
return;
|
|
}
|
|
|
|
rowIndex = 0;
|
|
while (rowIndex < numRows) {
|
|
cmd = *ptr++;
|
|
int cnt = cmd & 0x1F;
|
|
if (cmd & 0x80) {
|
|
uint16 value;
|
|
if (cmd & 0x60) {
|
|
value = (cmd & 0x40) ? 0 : 0xFF;
|
|
} else if (_PCE.maskIDSize == 1) {
|
|
value = *ptr++;
|
|
} else {
|
|
value = READ_LE_UINT16(ptr);
|
|
ptr += 2;
|
|
}
|
|
for (int i = 0; i < cnt; ++i) {
|
|
masks[rowIndex++] = value;
|
|
}
|
|
} else {
|
|
for (int i = 0; i < cnt; ++i) {
|
|
if (_PCE.maskIDSize == 1) {
|
|
masks[rowIndex++] = *ptr++;
|
|
} else {
|
|
masks[rowIndex++] = READ_LE_UINT16(ptr);
|
|
ptr += 2;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
void GdiPCEngine::decodePCEngineGfx(const byte *room) {
|
|
uint16* stripOffsets;
|
|
|
|
decodePCEngineTileData(_vm->findResourceData(MKTAG('T','I','L','E'), room));
|
|
decodePCEngineMaskData(_vm->findResourceData(MKTAG('Z','P','0','0'), room));
|
|
|
|
const byte* smap_ptr = _vm->findResourceData(MKTAG('I','M','0','0'), room);
|
|
smap_ptr++; // roomID
|
|
int numStrips = *smap_ptr++;
|
|
int numRows = *smap_ptr++;
|
|
_PCE.maskIDSize = *smap_ptr++;
|
|
smap_ptr++; // unknown
|
|
|
|
memset(_PCE.nametable, 0, sizeof(_PCE.nametable));
|
|
memset(_PCE.colortable, 0, sizeof(_PCE.colortable));
|
|
readOffsetTable(smap_ptr, &stripOffsets, &numStrips);
|
|
for (int i = 0; i < numStrips; ++i) {
|
|
const byte *tilePtr = smap_ptr + stripOffsets[i];
|
|
decodeStrip(tilePtr,
|
|
&_PCE.nametable[i * numRows],
|
|
&_PCE.colortable[i * numRows],
|
|
&_PCE.masktable[i * numRows],
|
|
numRows,
|
|
false);
|
|
}
|
|
free(stripOffsets);
|
|
}
|
|
|
|
void GdiPCEngine::decodePCEngineObject(const byte *ptr, int xpos, int ypos, int width, int height) {
|
|
uint16 *stripOffsets;
|
|
int numStrips;
|
|
int numRows = height / 8;
|
|
|
|
memset(_PCE.nametableObj, 0, sizeof(_PCE.nametableObj));
|
|
memset(_PCE.colortableObj, 0, sizeof(_PCE.colortableObj));
|
|
readOffsetTable(ptr, &stripOffsets, &numStrips);
|
|
for (int i = 0; i < numStrips; ++i) {
|
|
const byte *tilePtr = ptr + stripOffsets[i];
|
|
decodeStrip(tilePtr,
|
|
&_PCE.nametableObj[i * numRows],
|
|
&_PCE.colortableObj[i * numRows],
|
|
&_PCE.masktableObj[i * numRows],
|
|
numRows,
|
|
true);
|
|
}
|
|
free(stripOffsets);
|
|
}
|
|
|
|
void GdiPCEngine::setTileData(byte *tile, int index, byte byte0, byte byte1) {
|
|
int row = index % 8;
|
|
int plane = (index / 8) * 2;
|
|
int plane02Bit, plane13Bit;
|
|
for (int col = 0; col < 8; ++col) {
|
|
plane02Bit = (byte0 >> (7-col)) & 0x1; // plane=0: bit0, plane=2: bit2
|
|
plane13Bit = (byte1 >> (7-col)) & 0x1; // plane=0: bit1, plane=2: bit3
|
|
tile[row * 8 + col] |= plane02Bit << (plane + 0);
|
|
tile[row * 8 + col] |= plane13Bit << (plane + 1);
|
|
}
|
|
}
|
|
|
|
void GdiPCEngine::decodePCEngineTileData(const byte *ptr) {
|
|
const byte *tilePtr;
|
|
byte* tile;
|
|
uint16* tileOffsets;
|
|
byte byte0, byte1;
|
|
|
|
readOffsetTable(ptr, &tileOffsets, &_PCE.numTiles);
|
|
|
|
if (_distaff) {
|
|
free(_PCE.staffTiles);
|
|
_PCE.staffTiles = (byte *)calloc(_PCE.numTiles * 8 * 8, sizeof(byte));
|
|
} else {
|
|
free(_PCE.roomTiles);
|
|
_PCE.roomTiles = (byte *)calloc(_PCE.numTiles * 8 * 8, sizeof(byte));
|
|
}
|
|
|
|
for (int i = 0; i < _PCE.numTiles; ++i) {
|
|
tile = (_distaff) ? &_PCE.staffTiles[i * 64] : &_PCE.roomTiles[i * 64];
|
|
tilePtr = ptr + tileOffsets[i];
|
|
|
|
int index = 0;
|
|
while (index < 16) {
|
|
byte cmd = *tilePtr++;
|
|
byte cnt = (cmd & 0x0F) + 1;
|
|
if (cmd & 0x80) {
|
|
byte0 = (cmd & 0x10) ? 0 : *tilePtr++;
|
|
byte1 = (cmd & 0x40) ? 0 : *tilePtr++;
|
|
while (cnt--) {
|
|
setTileData(tile, index++, byte0, byte1);
|
|
}
|
|
} else {
|
|
while (cnt--) {
|
|
byte0 = (cmd & 0x10) ? 0 : *tilePtr++;
|
|
byte1 = (cmd & 0x40) ? 0 : *tilePtr++;
|
|
setTileData(tile, index++, byte0, byte1);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
free(tileOffsets);
|
|
}
|
|
|
|
void GdiPCEngine::decodePCEngineMaskData(const byte *ptr) {
|
|
const byte *maskPtr;
|
|
byte* mask;
|
|
uint16* maskOffsets;
|
|
|
|
if (!ptr) {
|
|
_PCE.numMasks = 0;
|
|
return;
|
|
}
|
|
|
|
readOffsetTable(ptr, &maskOffsets, &_PCE.numMasks);
|
|
|
|
free(_PCE.masks);
|
|
_PCE.masks = (byte *)malloc(_PCE.numMasks * 8 * sizeof(byte));
|
|
|
|
for (int i = 0; i < _PCE.numMasks; ++i) {
|
|
mask = &_PCE.masks[i * 8];
|
|
maskPtr = ptr + maskOffsets[i];
|
|
|
|
int index = 0;
|
|
while (index < 8) {
|
|
byte cmd = *maskPtr++;
|
|
int cnt = cmd & 0x1F;
|
|
if (cmd & 0x80) {
|
|
byte value;
|
|
if (cmd & 0x60)
|
|
value = (cmd & 0x40) ? 0x00 : 0xFF;
|
|
else
|
|
value = *maskPtr++;
|
|
for (int j = 0; j < cnt; ++j)
|
|
mask[index++] = ~value;
|
|
} else {
|
|
for (int j = 0; j < cnt; ++j)
|
|
mask[index++] = ~*maskPtr++;
|
|
}
|
|
}
|
|
}
|
|
|
|
free(maskOffsets);
|
|
}
|
|
|
|
void GdiPCEngine::drawStripPCEngine(byte *dst, byte *mask, int dstPitch, int stripnr, int top, int height) {
|
|
uint16 tileIdx;
|
|
byte *tile;
|
|
int paletteIdx, paletteOffset, paletteEntry;
|
|
height /= 8;
|
|
|
|
for (int y = 0; y < height; y++) {
|
|
tileIdx = (_objectMode ? _PCE.nametableObj : _PCE.nametable)[stripnr * height + y];
|
|
tile = (_distaff) ? &_PCE.staffTiles[tileIdx * 64] : &_PCE.roomTiles[tileIdx * 64];
|
|
paletteIdx = (_objectMode ? _PCE.colortableObj : _PCE.colortable)[stripnr * height + y];
|
|
paletteOffset = paletteIdx * 16;
|
|
for (int row = 0; row < 8; row++) {
|
|
for (int col = 0; col < 8; col++) {
|
|
paletteEntry = tile[row * 8 + col];
|
|
WRITE_UINT16(dst + col * 2, _vm->_16BitPalette[paletteOffset + paletteEntry]);
|
|
}
|
|
dst += dstPitch;
|
|
}
|
|
}
|
|
}
|
|
|
|
void GdiPCEngine::drawStripPCEngineMask(byte *dst, int stripnr, int top, int height) const {
|
|
uint16 maskIdx;
|
|
height /= 8;
|
|
|
|
for (int y = 0; y < height; y++) {
|
|
maskIdx = (_objectMode ? _PCE.masktableObj : _PCE.masktable)[stripnr * height + y];
|
|
for (int row = 0; row < 8; row++) {
|
|
if (_PCE.numMasks > 0)
|
|
*dst = _PCE.masks[maskIdx * 8 + row];
|
|
else
|
|
*dst = 0;
|
|
dst += _numStrips;
|
|
}
|
|
}
|
|
}
|
|
#endif
|
|
|
|
void GdiV1::drawStripV1Background(byte *dst, int dstPitch, int stripnr, int height) {
|
|
int charIdx;
|
|
height /= 8;
|
|
for (int y = 0; y < height; y++) {
|
|
_V1.colors[3] = (_V1.colorMap[y + stripnr * height] & 7);
|
|
// Check for room color change in V1 zak
|
|
if (_roomPalette[0] == 255) {
|
|
_V1.colors[2] = _roomPalette[2];
|
|
_V1.colors[1] = _roomPalette[1];
|
|
}
|
|
|
|
charIdx = _V1.picMap[y + stripnr * height] * 8;
|
|
for (int i = 0; i < 8; i++) {
|
|
byte c = _V1.charMap[charIdx + i];
|
|
dst[0] = dst[1] = _colorMap[_V1.colors[(c >> 6) & 3]];
|
|
dst[2] = dst[3] = _colorMap[_V1.colors[(c >> 4) & 3]];
|
|
dst[4] = dst[5] = _colorMap[_V1.colors[(c >> 2) & 3]];
|
|
dst[6] = dst[7] = _colorMap[_V1.colors[(c >> 0) & 3]];
|
|
dst += dstPitch;
|
|
}
|
|
}
|
|
}
|
|
|
|
void GdiV1::drawStripV1Object(byte *dst, int dstPitch, int stripnr, int width, int height) {
|
|
int charIdx;
|
|
height /= 8;
|
|
width /= 8;
|
|
for (int y = 0; y < height; y++) {
|
|
_V1.colors[3] = (_V1.objectMap[(y + height) * width + stripnr] & 7);
|
|
charIdx = _V1.objectMap[y * width + stripnr] * 8;
|
|
for (int i = 0; i < 8; i++) {
|
|
byte c = _V1.charMap[charIdx + i];
|
|
dst[0] = dst[1] = _colorMap[_V1.colors[(c >> 6) & 3]];
|
|
dst[2] = dst[3] = _colorMap[_V1.colors[(c >> 4) & 3]];
|
|
dst[4] = dst[5] = _colorMap[_V1.colors[(c >> 2) & 3]];
|
|
dst[6] = dst[7] = _colorMap[_V1.colors[(c >> 0) & 3]];
|
|
dst += dstPitch;
|
|
}
|
|
}
|
|
}
|
|
|
|
void GdiV1::drawStripV1Mask(byte *dst, int stripnr, int width, int height) const {
|
|
int maskIdx;
|
|
height /= 8;
|
|
width /= 8;
|
|
for (int y = 0; y < height; y++) {
|
|
if (_objectMode)
|
|
maskIdx = _V1.objectMap[(y + 2 * height) * width + stripnr] * 8;
|
|
else
|
|
maskIdx = _V1.maskMap[y + stripnr * height] * 8;
|
|
for (int i = 0; i < 8; i++) {
|
|
byte c = _V1.maskChar[maskIdx + i];
|
|
|
|
// V1/V0 masks are inverted compared to what ScummVM expects
|
|
*dst = c ^ 0xFF;
|
|
dst += _numStrips;
|
|
}
|
|
}
|
|
}
|
|
|
|
void GdiV1::decodeV1Gfx(const byte *src, byte *dst, int size) const {
|
|
int x, z;
|
|
byte color, run, common[4];
|
|
|
|
for (z = 0; z < 4; z++) {
|
|
common[z] = *src++;
|
|
}
|
|
|
|
x = 0;
|
|
while (x < size) {
|
|
run = *src++;
|
|
if (run & 0x80) {
|
|
color = common[(run >> 5) & 3];
|
|
run &= 0x1F;
|
|
for (z = 0; z <= run; z++) {
|
|
dst[x++] = color;
|
|
}
|
|
} else if (run & 0x40) {
|
|
run &= 0x3F;
|
|
color = *src++;
|
|
for (z = 0; z <= run; z++) {
|
|
dst[x++] = color;
|
|
}
|
|
} else {
|
|
for (z = 0; z <= run; z++) {
|
|
dst[x++] = *src++;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Create and fill a table with offsets to the graphic and mask strips in the
|
|
* given V2 EGA bitmap.
|
|
* @param src the V2 EGA bitmap
|
|
* @param width the width of the bitmap
|
|
* @param height the height of the bitmap
|
|
* @param table the strip table to fill
|
|
* @return filled strip table
|
|
*/
|
|
StripTable *GdiV2::generateStripTable(const byte *src, int width, int height, StripTable *table) const {
|
|
|
|
// If no strip table was given to use, allocate a new one
|
|
if (table == nullptr)
|
|
table = (StripTable *)calloc(1, sizeof(StripTable));
|
|
|
|
const byte *bitmapStart = src;
|
|
byte color = 0, data = 0;
|
|
int x, y, length = 0;
|
|
byte run = 1;
|
|
|
|
// Decode the graphics strips, and memorize the run/color values
|
|
// as well as the byte offset.
|
|
for (x = 0; x < width; x++) {
|
|
|
|
if ((x % 8) == 0) {
|
|
assert(x / 8 < 160);
|
|
table->run[x / 8] = run;
|
|
table->color[x / 8] = color;
|
|
table->offsets[x / 8] = src - bitmapStart;
|
|
}
|
|
|
|
for (y = 0; y < height; y++) {
|
|
if (--run == 0) {
|
|
data = *src++;
|
|
if (data & 0x80) {
|
|
run = data & 0x7f;
|
|
} else {
|
|
run = data >> 4;
|
|
}
|
|
if (run == 0) {
|
|
run = *src++;
|
|
}
|
|
color = data & 0x0f;
|
|
}
|
|
}
|
|
}
|
|
|
|
// The mask data follows immediately after the graphics.
|
|
x = 0;
|
|
y = height;
|
|
width /= 8;
|
|
|
|
for (;;) {
|
|
length = *src++;
|
|
const byte runFlag = length & 0x80;
|
|
if (runFlag) {
|
|
length &= 0x7f;
|
|
data = *src++;
|
|
}
|
|
do {
|
|
if (!runFlag)
|
|
data = *src++;
|
|
if (y == height) {
|
|
assert(x < 120);
|
|
table->zoffsets[x] = src - bitmapStart - 1;
|
|
table->zrun[x] = length | runFlag;
|
|
}
|
|
if (--y == 0) {
|
|
if (--width == 0)
|
|
return table;
|
|
x++;
|
|
y = height;
|
|
}
|
|
} while (--length);
|
|
}
|
|
|
|
return table;
|
|
}
|
|
|
|
void Gdi::drawStripEGA(byte *dst, int dstPitch, const byte *src, int height) const {
|
|
byte color = 0;
|
|
int run = 0, x = 0, y = 0, z;
|
|
|
|
while (x < 8) {
|
|
color = *src++;
|
|
|
|
if (color & 0x80) {
|
|
run = color & 0x3f;
|
|
|
|
if (color & 0x40) {
|
|
color = *src++;
|
|
|
|
if (run == 0) {
|
|
run = *src++;
|
|
}
|
|
for (z = 0; z < run; z++) {
|
|
*(dst + y * dstPitch + x) = (z & 1) ? _roomPalette[(color & 0xf) + _paletteMod] : _roomPalette[(color >> 4) + _paletteMod];
|
|
|
|
y++;
|
|
if (y >= height) {
|
|
y = 0;
|
|
x++;
|
|
}
|
|
}
|
|
} else {
|
|
if (run == 0) {
|
|
run = *src++;
|
|
}
|
|
|
|
for (z = 0; z < run; z++) {
|
|
*(dst + y * dstPitch + x) = *(dst + y * dstPitch + x - 1);
|
|
|
|
y++;
|
|
if (y >= height) {
|
|
y = 0;
|
|
x++;
|
|
}
|
|
}
|
|
}
|
|
} else {
|
|
run = color >> 4;
|
|
if (run == 0) {
|
|
run = *src++;
|
|
}
|
|
|
|
for (z = 0; z < run; z++) {
|
|
*(dst + y * dstPitch + x) = _roomPalette[(color & 0xf) + _paletteMod];
|
|
|
|
y++;
|
|
if (y >= height) {
|
|
y = 0;
|
|
x++;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
#define READ_BIT (shift--, dataBit = data & 1, data >>= 1, dataBit)
|
|
#define FILL_BITS(n) do { \
|
|
if (shift < n) { \
|
|
data |= *src++ << shift; \
|
|
shift += 8; \
|
|
} \
|
|
} while (0)
|
|
|
|
// NOTE: drawStripHE is actually very similar to drawStripComplex
|
|
void Gdi::drawStripHE(byte *dst, int dstPitch, const byte *src, int width, int height, const bool transpCheck) const {
|
|
static const int delta_color[] = { -4, -3, -2, -1, 1, 2, 3, 4 };
|
|
uint32 dataBit, data;
|
|
byte color;
|
|
int shift;
|
|
|
|
color = *src++;
|
|
data = READ_LE_UINT24(src);
|
|
src += 3;
|
|
shift = 24;
|
|
|
|
int x = width;
|
|
while (1) {
|
|
if (!transpCheck || color != _transparentColor)
|
|
writeRoomColor(dst, color);
|
|
dst += _vm->_bytesPerPixel;
|
|
--x;
|
|
if (x == 0) {
|
|
x = width;
|
|
dst += dstPitch - width * _vm->_bytesPerPixel;
|
|
--height;
|
|
if (height == 0)
|
|
return;
|
|
}
|
|
FILL_BITS(1);
|
|
if (READ_BIT) {
|
|
FILL_BITS(1);
|
|
if (READ_BIT) {
|
|
FILL_BITS(3);
|
|
color += delta_color[data & 7];
|
|
shift -= 3;
|
|
data >>= 3;
|
|
} else {
|
|
FILL_BITS(_decomp_shr);
|
|
color = data & _decomp_mask;
|
|
shift -= _decomp_shr;
|
|
data >>= _decomp_shr;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
#undef READ_BIT
|
|
#undef FILL_BITS
|
|
|
|
|
|
void Gdi::drawStrip3DO(byte *dst, int dstPitch, const byte *src, int height, const bool transpCheck) const {
|
|
if (height == 0)
|
|
return;
|
|
|
|
int decSize = height * 8;
|
|
int curSize = 0;
|
|
|
|
do {
|
|
uint8 data = *src++;
|
|
uint8 rle = data & 1;
|
|
int len = (data >> 1) + 1;
|
|
|
|
len = MIN(decSize, len);
|
|
decSize -= len;
|
|
|
|
if (!rle) {
|
|
for (; len > 0; len--, src++, dst++) {
|
|
if (!transpCheck || *src != _transparentColor)
|
|
*dst = _roomPalette[*src];
|
|
curSize++;
|
|
if (!(curSize & 7))
|
|
dst += dstPitch - 8; // Next row
|
|
}
|
|
} else {
|
|
byte color = *src++;
|
|
for (; len > 0; len--, dst++) {
|
|
if (!transpCheck || color != _transparentColor)
|
|
*dst = _roomPalette[color];
|
|
curSize++;
|
|
if (!(curSize & 7))
|
|
dst += dstPitch - 8; // Next row
|
|
}
|
|
}
|
|
} while (decSize > 0);
|
|
}
|
|
|
|
|
|
#define READ_BIT (cl--, bit = bits & 1, bits >>= 1, bit)
|
|
#define FILL_BITS do { \
|
|
if (cl <= 8) { \
|
|
bits |= (*src++ << cl); \
|
|
cl += 8; \
|
|
} \
|
|
} while (0)
|
|
|
|
void Gdi::drawStripComplex(byte *dst, int dstPitch, const byte *src, int height, const bool transpCheck) const {
|
|
byte color;
|
|
MajMinCodec majMin;
|
|
|
|
majMin.setupBitReader(_decomp_shr, src);
|
|
|
|
byte lineBuffer[8];
|
|
memset(lineBuffer, 0, 8);
|
|
|
|
while (height--) {
|
|
majMin.decodeLine(lineBuffer, 8, 1);
|
|
for (byte i = 0; i < 8; i ++) {
|
|
color = lineBuffer[i];
|
|
if (!transpCheck || color != _transparentColor)
|
|
writeRoomColor(dst, color);
|
|
dst += _vm->_bytesPerPixel;
|
|
}
|
|
dst += dstPitch - 8 * _vm->_bytesPerPixel;
|
|
}
|
|
}
|
|
|
|
void Gdi::drawStripBasicH(byte *dst, int dstPitch, const byte *src, int height, const bool transpCheck) const {
|
|
byte color = *src++;
|
|
uint bits = *src++;
|
|
byte cl = 8;
|
|
byte bit;
|
|
int8 inc = -1;
|
|
|
|
do {
|
|
int x = 8;
|
|
do {
|
|
FILL_BITS;
|
|
if (!transpCheck || color != _transparentColor)
|
|
writeRoomColor(dst, color);
|
|
dst += _vm->_bytesPerPixel;
|
|
if (!READ_BIT) {
|
|
} else if (!READ_BIT) {
|
|
FILL_BITS;
|
|
color = bits & _decomp_mask;
|
|
bits >>= _decomp_shr;
|
|
cl -= _decomp_shr;
|
|
inc = -1;
|
|
} else if (!READ_BIT) {
|
|
color += inc;
|
|
} else {
|
|
inc = -inc;
|
|
color += inc;
|
|
}
|
|
} while (--x);
|
|
dst += dstPitch - 8 * _vm->_bytesPerPixel;
|
|
} while (--height);
|
|
}
|
|
|
|
void Gdi::drawStripBasicV(byte *dst, int dstPitch, const byte *src, int height, const bool transpCheck) const {
|
|
byte color = *src++;
|
|
uint bits = *src++;
|
|
byte cl = 8;
|
|
byte bit;
|
|
int8 inc = -1;
|
|
|
|
int x = 8;
|
|
do {
|
|
int h = height;
|
|
do {
|
|
FILL_BITS;
|
|
if (!transpCheck || color != _transparentColor)
|
|
writeRoomColor(dst, color);
|
|
dst += dstPitch;
|
|
if (!READ_BIT) {
|
|
} else if (!READ_BIT) {
|
|
FILL_BITS;
|
|
color = bits & _decomp_mask;
|
|
bits >>= _decomp_shr;
|
|
cl -= _decomp_shr;
|
|
inc = -1;
|
|
} else if (!READ_BIT) {
|
|
color += inc;
|
|
} else {
|
|
inc = -inc;
|
|
color += inc;
|
|
}
|
|
} while (--h);
|
|
dst -= _vertStripNextInc;
|
|
} while (--x);
|
|
}
|
|
|
|
#undef READ_BIT
|
|
#undef FILL_BITS
|
|
|
|
/* Ender - Zak256/Indy256 decoders */
|
|
#define READ_BIT_256 \
|
|
do { \
|
|
if ((mask <<= 1) == 256) { \
|
|
buffer = *src++; \
|
|
mask = 1; \
|
|
} \
|
|
bits = ((buffer & mask) != 0); \
|
|
} while (0)
|
|
|
|
#define READ_N_BITS(n, c) \
|
|
do { \
|
|
c = 0; \
|
|
for (int b = 0; b < n; b++) { \
|
|
READ_BIT_256; \
|
|
c += (bits << b); \
|
|
} \
|
|
} while (0)
|
|
|
|
#define NEXT_ROW \
|
|
do { \
|
|
dst += dstPitch; \
|
|
if (--h == 0) { \
|
|
if (!--x) \
|
|
return; \
|
|
dst -= _vertStripNextInc; \
|
|
h = height; \
|
|
} \
|
|
} while (0)
|
|
|
|
void Gdi::drawStripRaw(byte *dst, int dstPitch, const byte *src, int height, const bool transpCheck) const {
|
|
int x;
|
|
|
|
if (_vm->_game.features & GF_OLD256) {
|
|
uint h = height;
|
|
x = 8;
|
|
for (;;) {
|
|
*dst = _roomPalette[*src++];
|
|
NEXT_ROW;
|
|
}
|
|
} else {
|
|
do {
|
|
for (x = 0; x < 8; x ++) {
|
|
byte color = *src++;
|
|
if (!transpCheck || color != _transparentColor)
|
|
writeRoomColor(dst + x * _vm->_bytesPerPixel, color);
|
|
}
|
|
dst += dstPitch;
|
|
} while (--height);
|
|
}
|
|
}
|
|
|
|
void Gdi::unkDecode8(byte *dst, int dstPitch, const byte *src, int height) const {
|
|
uint h = height;
|
|
|
|
int x = 8;
|
|
for (;;) {
|
|
uint run = (*src++) + 1;
|
|
byte color = *src++;
|
|
|
|
do {
|
|
*dst = _roomPalette[color];
|
|
NEXT_ROW;
|
|
} while (--run);
|
|
}
|
|
}
|
|
|
|
void Gdi::unkDecode9(byte *dst, int dstPitch, const byte *src, int height) const {
|
|
byte c, bits, color, run;
|
|
int i;
|
|
uint buffer = 0, mask = 128;
|
|
int h = height;
|
|
run = 0;
|
|
|
|
int x = 8;
|
|
for (;;) {
|
|
READ_N_BITS(4, c);
|
|
|
|
switch (c >> 2) {
|
|
case 0:
|
|
READ_N_BITS(4, color);
|
|
for (i = 0; i < ((c & 3) + 2); i++) {
|
|
*dst = _roomPalette[run * 16 + color];
|
|
NEXT_ROW;
|
|
}
|
|
break;
|
|
|
|
case 1:
|
|
for (i = 0; i < ((c & 3) + 1); i++) {
|
|
READ_N_BITS(4, color);
|
|
*dst = _roomPalette[run * 16 + color];
|
|
NEXT_ROW;
|
|
}
|
|
break;
|
|
|
|
case 2:
|
|
READ_N_BITS(4, run);
|
|
break;
|
|
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
void Gdi::unkDecode10(byte *dst, int dstPitch, const byte *src, int height) const {
|
|
int i;
|
|
byte local_palette[256], numcolors = *src++;
|
|
uint h = height;
|
|
|
|
for (i = 0; i < numcolors; i++)
|
|
local_palette[i] = *src++;
|
|
|
|
int x = 8;
|
|
|
|
for (;;) {
|
|
byte color = *src++;
|
|
if (color < numcolors) {
|
|
*dst = _roomPalette[local_palette[color]];
|
|
NEXT_ROW;
|
|
} else {
|
|
uint run = color - numcolors + 1;
|
|
color = *src++;
|
|
do {
|
|
*dst = _roomPalette[color];
|
|
NEXT_ROW;
|
|
} while (--run);
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
void Gdi::unkDecode11(byte *dst, int dstPitch, const byte *src, int height) const {
|
|
int bits, i;
|
|
uint buffer = 0, mask = 128;
|
|
byte inc = 1, color = *src++;
|
|
|
|
int x = 8;
|
|
do {
|
|
int h = height;
|
|
do {
|
|
*dst = _roomPalette[color];
|
|
dst += dstPitch;
|
|
for (i = 0; i < 3; i++) {
|
|
READ_BIT_256;
|
|
if (!bits)
|
|
break;
|
|
}
|
|
switch (i) {
|
|
case 1:
|
|
inc = -inc;
|
|
color -= inc;
|
|
break;
|
|
|
|
case 2:
|
|
color -= inc;
|
|
break;
|
|
|
|
case 3:
|
|
inc = 1;
|
|
READ_N_BITS(8, color);
|
|
break;
|
|
|
|
default:
|
|
break;
|
|
}
|
|
} while (--h);
|
|
dst -= _vertStripNextInc;
|
|
} while (--x);
|
|
}
|
|
|
|
#undef NEXT_ROW
|
|
#undef READ_BIT_256
|
|
|
|
#ifdef USE_RGB_COLOR
|
|
void GdiHE16bit::writeRoomColor(byte *dst, byte color) const {
|
|
WRITE_UINT16(dst, READ_LE_UINT16(_vm->_hePalettes + 2048 + color * 2));
|
|
}
|
|
#endif
|
|
|
|
void Gdi::writeRoomColor(byte *dst, byte color) const {
|
|
// As described in bug #2204 "FOA/Amiga: Palette problem (Regression)"
|
|
// the original AMIGA version of Indy4: The Fate of Atlantis allowed
|
|
// overflowing of the palette index. To have the same result in our code,
|
|
// we need to do an logical AND 0xFF here to keep the result in [0, 255].
|
|
*dst = _roomPalette[(color + _paletteMod) & 0xFF];
|
|
}
|
|
|
|
|
|
#pragma mark -
|
|
#pragma mark --- Transition effects ---
|
|
#pragma mark -
|
|
|
|
void ScummEngine::fadeIn(int effect) {
|
|
if (_disableFadeInEffect || (_game.id == GID_MANIAC && _game.platform == Common::kPlatformAmiga)) {
|
|
// fadeIn() calls can be disabled in TheDig after a SMUSH movie
|
|
// has been played. Like the original interpreter, we introduce
|
|
// an extra flag to handle this.
|
|
|
|
// Screen fades are also disabled in the Amiga version of
|
|
// Maniac Mansion, verified on WinUAE
|
|
_disableFadeInEffect = false;
|
|
_doEffect = false;
|
|
_screenEffectFlag = true;
|
|
return;
|
|
}
|
|
towns_waitForScroll(0);
|
|
updatePalette();
|
|
|
|
switch (effect) {
|
|
case 0:
|
|
// seems to do nothing
|
|
break;
|
|
case 1:
|
|
case 2:
|
|
case 3:
|
|
case 4:
|
|
case 5:
|
|
case 6:
|
|
// Some of the transition effects won't work properly unless
|
|
// the screen is marked as clean first. At first I thought I
|
|
// could safely do this every time fadeIn() was called, but
|
|
// that broke the FOA intro. Probably other things as well.
|
|
//
|
|
// Hopefully it's safe to do it at this point, at least.
|
|
_virtscr[kMainVirtScreen].setDirtyRange(0, 0);
|
|
transitionEffect(effect - 1);
|
|
break;
|
|
case 128:
|
|
dissolveEffectSelector();
|
|
break;
|
|
case 129:
|
|
break;
|
|
case 130:
|
|
case 131:
|
|
case 132:
|
|
case 133:
|
|
scrollEffect(133 - effect);
|
|
break;
|
|
case 134:
|
|
dissolveEffect(1, 1);
|
|
break;
|
|
case 135:
|
|
dissolveEffect(1, _virtscr[kMainVirtScreen].h);
|
|
break;
|
|
default:
|
|
error("Unknown screen effect, %d", effect);
|
|
}
|
|
_screenEffectFlag = true;
|
|
}
|
|
|
|
void ScummEngine::fadeOut(int effect) {
|
|
// Screen fades are disabled in the Amiga version of
|
|
// Maniac Mansion, verified on WinUAE
|
|
if (_game.id == GID_MANIAC && _game.platform == Common::kPlatformAmiga) {
|
|
_doEffect = false;
|
|
_screenEffectFlag = false;
|
|
return;
|
|
}
|
|
|
|
towns_waitForScroll(0);
|
|
|
|
VirtScreen *vs = &_virtscr[kMainVirtScreen];
|
|
vs->setDirtyRange(0, 0);
|
|
if (_game.version < 7)
|
|
camera._last.x = camera._cur.x;
|
|
|
|
#ifndef DISABLE_TOWNS_DUAL_LAYER_MODE
|
|
if (_game.version == 3 && _game.platform == Common::kPlatformFMTowns)
|
|
_textSurface.fillRect(Common::Rect(0, vs->topline * _textSurfaceMultiplier, _textSurface.pitch, (vs->topline + vs->h) * _textSurfaceMultiplier), 0);
|
|
#endif
|
|
|
|
// V0 wipes the text area before fading out
|
|
if (_game.version == 0) {
|
|
updateDirtyScreen(kTextVirtScreen);
|
|
}
|
|
|
|
// TheDig can disable fadeIn(), and may call fadeOut() several times
|
|
// successively. Disabling the _screenEffectFlag check forces the screen
|
|
// to get cleared. This fixes glitches, at least, in the first cutscenes
|
|
// when bypassed of FT and TheDig.
|
|
if ((_game.version == 7 || _screenEffectFlag) && effect != 0) {
|
|
// Fill screen 0 with black
|
|
if (_game.platform == Common::kPlatformNES)
|
|
memset(vs->getPixels(0, 0), 0x1d, vs->pitch * vs->h);
|
|
else
|
|
memset(vs->getPixels(0, 0), 0, vs->pitch * vs->h);
|
|
|
|
// Fade to black with the specified effect, if any.
|
|
switch (effect) {
|
|
case 1:
|
|
case 2:
|
|
case 3:
|
|
case 4:
|
|
case 5:
|
|
case 6:
|
|
transitionEffect(effect - 1);
|
|
break;
|
|
case 128:
|
|
dissolveEffectSelector();
|
|
break;
|
|
case 129:
|
|
// Just blit screen 0 to the display (i.e. display will be black)
|
|
vs->setDirtyRange(0, vs->h);
|
|
updateDirtyScreen(kMainVirtScreen);
|
|
towns_updateGfx();
|
|
break;
|
|
case 134:
|
|
dissolveEffect(1, 1);
|
|
break;
|
|
case 135:
|
|
dissolveEffect(1, _virtscr[kMainVirtScreen].h);
|
|
break;
|
|
default:
|
|
error("fadeOut: default case %d", effect);
|
|
}
|
|
}
|
|
|
|
_screenEffectFlag = false;
|
|
}
|
|
|
|
/**
|
|
* Perform a transition effect. There are four different effects possible:
|
|
* 0: Iris effect
|
|
* 1: Box wipe (a black box expands from the upper-left corner to the lower-right corner)
|
|
* 2: Box wipe (a black box expands from the lower-right corner to the upper-left corner)
|
|
* 3: Inverse box wipe
|
|
* All effects operate on 8x8 blocks of the screen. These blocks are updated
|
|
* in a certain order; the exact order determines how the effect appears to the user.
|
|
* @param a the transition effect to perform
|
|
*/
|
|
void ScummEngine::transitionEffect(int a) {
|
|
int delta[16]; // Offset applied during each iteration
|
|
int tab_2[16];
|
|
int i, j;
|
|
int bottom;
|
|
int l, t, r, b;
|
|
int delay, numOfIterations;
|
|
const int height = MIN((int)_virtscr[kMainVirtScreen].h, _screenHeight);
|
|
|
|
if (VAR_FADE_DELAY == 0xFF) {
|
|
if (_game.platform == Common::kPlatformC64) {
|
|
delay = kC64Delay;
|
|
} else if (_game.version >= 2) {
|
|
delay = kPictureDelay;
|
|
} else {
|
|
delay = kNoDelay;
|
|
}
|
|
} else {
|
|
delay = VAR(VAR_FADE_DELAY);
|
|
}
|
|
|
|
// Older Amiga games handle transition timing a whole frame
|
|
// at a time instead of using quarter frames; the following
|
|
// code gives my best approximation of that behavior
|
|
// and the resulting timing.
|
|
if (_game.platform == Common::kPlatformAmiga) {
|
|
if (_game.id == GID_ZAK) {
|
|
delay = kPictureDelay;
|
|
} else {
|
|
int amigaRest = (delay % 4);
|
|
delay = (delay / 4);
|
|
if (amigaRest > 0) {
|
|
delay += 1;
|
|
}
|
|
delay *= 10;
|
|
}
|
|
}
|
|
|
|
// V3+ games have the number of iterations hardcoded; we also
|
|
// have that number hardcoded for MM NES, so let's target that too:
|
|
if (_game.version >= 3 || _game.platform == Common::kPlatformNES) {
|
|
numOfIterations = transitionEffects[a].numOfIterations;
|
|
} else {
|
|
numOfIterations = (a == 0 || a == 4) ? ceil((double)height / 16.0) : height / 8;
|
|
}
|
|
|
|
for (i = 0; i < 16; i++) {
|
|
delta[i] = transitionEffects[a].deltaTable[i];
|
|
j = transitionEffects[a].stripTable[i];
|
|
if (j == 24)
|
|
j = height / 8 - 1;
|
|
tab_2[i] = j;
|
|
}
|
|
|
|
bottom = height / 8;
|
|
for (j = 0; j < numOfIterations; j++) {
|
|
for (i = 0; i < 4; i++) {
|
|
l = tab_2[i * 4];
|
|
t = tab_2[i * 4 + 1];
|
|
r = tab_2[i * 4 + 2];
|
|
b = tab_2[i * 4 + 3];
|
|
|
|
if (t == b) {
|
|
while (l <= r) {
|
|
if (l >= 0 && l < _gdi->_numStrips && t < bottom) {
|
|
_virtscr[kMainVirtScreen].tdirty[l] = _screenTop + t * 8;
|
|
_virtscr[kMainVirtScreen].bdirty[l] = _screenTop + (b + 1) * 8;
|
|
}
|
|
l++;
|
|
}
|
|
} else {
|
|
if (l < 0 || l >= _gdi->_numStrips || b <= t)
|
|
continue;
|
|
if (b > bottom)
|
|
b = bottom;
|
|
if (t < 0)
|
|
t = 0;
|
|
_virtscr[kMainVirtScreen].tdirty[l] = _screenTop + t * 8;
|
|
_virtscr[kMainVirtScreen].bdirty[l] = _screenTop + (b + 1) * 8;
|
|
}
|
|
updateDirtyScreen(kMainVirtScreen);
|
|
}
|
|
|
|
for (i = 0; i < 16; i++)
|
|
tab_2[i] += delta[i];
|
|
|
|
// Draw the current state to the screen and wait
|
|
// for the appropriate number of quarter frames
|
|
if (!_fastMode) {
|
|
waitForTimer(delay, true);
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Update width*height areas of the screen, in random order, until the whole
|
|
* screen has been updated. For instance:
|
|
*
|
|
* dissolveEffect(1, 1) produces a pixel-by-pixel dissolve
|
|
* dissolveEffect(8, 8) produces a square-by-square dissolve
|
|
* dissolveEffect(virtsrc[0].width, 1) produces a line-by-line dissolve
|
|
*/
|
|
void ScummEngine::dissolveEffect(int width, int height) {
|
|
VirtScreen *vs = &_virtscr[kMainVirtScreen];
|
|
int *offsets;
|
|
int blitsBeforeRefresh, blits, blitsToFreeze;
|
|
int x, y;
|
|
int w, h;
|
|
int i;
|
|
bool canHalt = false;
|
|
bool is1x1Pattern = (width == 1 && height == 1);
|
|
// There's probably some less memory-hungry way of doing this. But
|
|
// since we're only dealing with relatively small images, it shouldn't
|
|
// be too bad.
|
|
|
|
w = vs->w / width;
|
|
h = vs->h / height;
|
|
|
|
// When used correctly, vs->width % width and vs->height % height
|
|
// should both be zero, but just to be safe...
|
|
|
|
if (vs->w % width)
|
|
w++;
|
|
|
|
if (vs->h % height)
|
|
h++;
|
|
|
|
offsets = (int *) malloc(w * h * sizeof(int));
|
|
if (offsets == nullptr)
|
|
error("dissolveEffect: out of memory");
|
|
|
|
// Create a permutation of offsets into the frame buffer
|
|
|
|
if (width == 1 && height == 1) {
|
|
// Optimized case for pixel-by-pixel dissolve
|
|
|
|
for (i = 0; i < vs->w * vs->h; i++)
|
|
offsets[i] = i;
|
|
|
|
for (i = 1; i < w * h; i++) {
|
|
int j;
|
|
|
|
j = _rnd.getRandomNumber(i - 1);
|
|
offsets[i] = offsets[j];
|
|
offsets[j] = i;
|
|
}
|
|
} else {
|
|
int *offsets2;
|
|
|
|
for (i = 0, x = 0; x < vs->w; x += width)
|
|
for (y = 0; y < vs->h; y += height)
|
|
offsets[i++] = y * vs->pitch + x;
|
|
|
|
offsets2 = (int *) malloc(w * h * sizeof(int));
|
|
if (offsets2 == nullptr)
|
|
error("dissolveEffect: out of memory");
|
|
|
|
memcpy(offsets2, offsets, w * h * sizeof(int));
|
|
|
|
for (i = 1; i < w * h; i++) {
|
|
int j;
|
|
|
|
j = _rnd.getRandomNumber(i - 1);
|
|
offsets[i] = offsets[j];
|
|
offsets[j] = offsets2[i];
|
|
}
|
|
|
|
free(offsets2);
|
|
}
|
|
|
|
// The whole effect has a variable duration, depending on
|
|
// the host machine speed. We want to be accurate, but we
|
|
// also want to actually *see* the effect, given that in modern
|
|
// machines this code would run at lightspeed, so let's declare
|
|
// a blitsBeforeRefresh variable, which serves as a threshold
|
|
// value allowing us to pause rendering every N blits.
|
|
//
|
|
// Pattern 1x1:
|
|
// The original construct the image piece by piece but blits it
|
|
// every 8 iterations of a loop with duration h. We try to imitate
|
|
// this behavior by using blitsBeforeRefresh and by assigning it a value
|
|
// which is a factor of blitsToFreeze.
|
|
//
|
|
// Pattern NxM:
|
|
// The original construct the image piece by piece but blits it
|
|
// every time it finds an offset smaller than the height of the virtual
|
|
// screen. This is trivial to do in our code, so we just sleep for a
|
|
// quarter frame everytime the condition above is met.
|
|
//
|
|
// If we ever get a blitsToFreeze == 0, we will use 18 in its place
|
|
// since it's the most typical value got out of the calculations.
|
|
|
|
blits = 0;
|
|
blitsToFreeze = (h / 8); // The number of blits between which we delay rendering for pattern 1x1
|
|
blitsBeforeRefresh = (w * h) / (blitsToFreeze == 0 ? 18 : blitsToFreeze);
|
|
|
|
for (i = 0; i < w * h; i++) {
|
|
x = offsets[i] % vs->pitch;
|
|
y = offsets[i] / vs->pitch;
|
|
|
|
#ifndef DISABLE_TOWNS_DUAL_LAYER_MODE
|
|
if (_game.platform == Common::kPlatformFMTowns)
|
|
towns_drawStripToScreen(vs, x, y + vs->topline, x, y, width, height);
|
|
else
|
|
#endif
|
|
if (_macScreen)
|
|
mac_drawStripToScreen(vs, y, x, y + vs->topline, width, height);
|
|
else if (IS_ALIGNED(width, 4))
|
|
drawStripToScreen(vs, x, width, y, y + height);
|
|
else {
|
|
const byte *src = vs->getPixels(x, y);
|
|
int pitch = vs->pitch;
|
|
y += vs->topline;
|
|
int wd = width;
|
|
int ht = height;
|
|
|
|
if (_enableEGADithering) {
|
|
if (is1x1Pattern) {
|
|
*_compositeBuf = *src;
|
|
} else {
|
|
for (int ii = 0; ii < height; ++ii) {
|
|
memcpy(_compositeBuf + width * ii, src, width);
|
|
src += pitch;
|
|
}
|
|
}
|
|
pitch = width;
|
|
src = ditherVGAtoEGA(pitch, x, y, wd, ht);
|
|
}
|
|
|
|
_system->copyRectToScreen(src, pitch, x, y, wd, ht);
|
|
}
|
|
|
|
// Test for 1x1 pattern...
|
|
canHalt |= is1x1Pattern && ++blits >= blitsBeforeRefresh;
|
|
|
|
// If that is true, then reset the blits var...
|
|
if (canHalt) {
|
|
blits = 0;
|
|
}
|
|
|
|
// Test for NxM pattern...
|
|
canHalt |= !is1x1Pattern && (offsets[i] < vs->h);
|
|
|
|
// Halt rendering for a quarter frame (or a whole frame in case of Amiga)...
|
|
if (canHalt) {
|
|
canHalt = false;
|
|
if (_game.platform == Common::kPlatformAmiga) {
|
|
waitForTimer(4, true);
|
|
} else {
|
|
waitForTimer(1, true);
|
|
}
|
|
}
|
|
}
|
|
|
|
free(offsets);
|
|
}
|
|
|
|
void ScummEngine::scrollEffect(int dir) {
|
|
#ifndef DISABLE_TOWNS_DUAL_LAYER_MODE
|
|
// The FM-Towns versions use smooth scrolling here, but only for left and right.
|
|
if (_enableSmoothScrolling && dir > 1) {
|
|
towns_scriptScrollEffect((dir & 1) * 2 - 1);
|
|
return;
|
|
}
|
|
#endif
|
|
VirtScreen *vs = &_virtscr[kMainVirtScreen];
|
|
|
|
int x, y;
|
|
const int step = 8;
|
|
|
|
// Keep in mind: this effect is only present in v5 and v6, so VAR_FADE_DELAY is
|
|
// never uninitialized. The following check is here for good measure only.
|
|
int delay = (VAR_FADE_DELAY != 0xFF) ? VAR(VAR_FADE_DELAY) : kPictureDelay;
|
|
|
|
// Amiga handles timing a whole frame at a time
|
|
// instead of using quarter frames; the following
|
|
// code gives my best approximation of that behavior
|
|
// and the resulting timing
|
|
if (_game.platform == Common::kPlatformAmiga) {
|
|
int amigaRest = (delay % 4);
|
|
delay = (delay / 4);
|
|
if (amigaRest > 0) {
|
|
delay += 1;
|
|
}
|
|
delay *= 10;
|
|
}
|
|
|
|
byte *src;
|
|
int m = _textSurfaceMultiplier;
|
|
int vsPitch = vs->pitch;
|
|
|
|
switch (dir) {
|
|
case 0:
|
|
//up
|
|
y = 1 + step;
|
|
while (y < vs->h) {
|
|
moveScreen(0, -step * m, vs->h * m);
|
|
#ifndef DISABLE_TOWNS_DUAL_LAYER_MODE
|
|
if (_townsScreen) {
|
|
towns_drawStripToScreen(vs, 0, vs->topline + vs->h - step, 0, y - step, vs->w, step);
|
|
} else
|
|
#endif
|
|
{
|
|
src = vs->getPixels(0, y - step);
|
|
_system->copyRectToScreen(src,
|
|
vsPitch * m,
|
|
0, (vs->h - step) * m,
|
|
vs->w * m, step * m);
|
|
}
|
|
|
|
waitForTimer(delay, true);
|
|
y += step;
|
|
}
|
|
break;
|
|
case 1:
|
|
// down
|
|
y = 1 + step;
|
|
while (y < vs->h) {
|
|
moveScreen(0, step * m, vs->h * m);
|
|
#ifndef DISABLE_TOWNS_DUAL_LAYER_MODE
|
|
if (_townsScreen) {
|
|
towns_drawStripToScreen(vs, 0, vs->topline, 0, vs->h - y, vs->w, step);
|
|
} else
|
|
#endif
|
|
{
|
|
src = vs->getPixels(0, vs->h - y);
|
|
_system->copyRectToScreen(src,
|
|
vsPitch * m,
|
|
0, 0,
|
|
vs->w * m, step * m);
|
|
}
|
|
|
|
waitForTimer(delay, true);
|
|
y += step;
|
|
}
|
|
break;
|
|
case 2:
|
|
// left
|
|
x = 1 + step;
|
|
while (x < vs->w) {
|
|
moveScreen(-step * m, 0, vs->h * m);
|
|
#ifndef DISABLE_TOWNS_DUAL_LAYER_MODE
|
|
if (_townsScreen) {
|
|
towns_drawStripToScreen(vs, vs->w - step, vs->topline, x - step, vs->topline, step, vs->h);
|
|
} else
|
|
#endif
|
|
{
|
|
src = vs->getPixels(x - step, 0);
|
|
_system->copyRectToScreen(src, vsPitch * m, (vs->w - step) * m, 0, step * m, vs->h * m);
|
|
}
|
|
waitForTimer(delay, true);
|
|
x += step;
|
|
}
|
|
break;
|
|
case 3:
|
|
// right
|
|
x = 1 + step;
|
|
while (x < vs->w) {
|
|
moveScreen(step * m, 0, vs->h * m);
|
|
#ifndef DISABLE_TOWNS_DUAL_LAYER_MODE
|
|
if (_townsScreen) {
|
|
towns_drawStripToScreen(vs, 0, vs->topline, vs->w - x, vs->topline, step, vs->h);
|
|
} else
|
|
#endif
|
|
{
|
|
src = vs->getPixels(vs->w - x, 0);
|
|
_system->copyRectToScreen(src, vsPitch * m, 0, 0, step * m, vs->h * m);
|
|
}
|
|
|
|
waitForTimer(delay, true);
|
|
x += step;
|
|
}
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
|
|
void ScummEngine::dissolveEffectSelector() {
|
|
// CD Loom (but not EGA Loom!) uses a more fine-grained dissolve
|
|
if (_game.id == GID_LOOM && _game.version == 4)
|
|
dissolveEffect(1, 1);
|
|
else if (_game.id == GID_LOOM && _game.platform == Common::kPlatformPCEngine)
|
|
dissolveEffect(8, 8);
|
|
else
|
|
dissolveEffect(8, 4);
|
|
}
|
|
|
|
void ScummEngine::updateScreenShakeEffect() {
|
|
if (!_shakeEnabled) {
|
|
if (_shakeFrame) {
|
|
_shakeFrame = 0;
|
|
_system->setShakePos(0, 0);
|
|
}
|
|
_shakeNextTick = _shakeTickCounter = 0;
|
|
return;
|
|
}
|
|
|
|
uint32 now = _system->getMillis();
|
|
if (!_shakeNextTick)
|
|
_shakeNextTick = now;
|
|
|
|
while (now >= _shakeNextTick) {
|
|
_shakeFrame = (_shakeFrame + 1) % NUM_SHAKE_POSITIONS;
|
|
_system->setShakePos(0, -shake_positions[_shakeFrame] * _textSurfaceMultiplier);
|
|
// In DOTT (and probably all other imuse games) this runs on the imuse timer which is a PIT 0 Timer at 291.304 Hz.
|
|
// Apparently it is the same timer setting for all sound drivers although it is set up not in the main executable
|
|
// but inside each respective ims driver during the driver load/init process. The screen shakes update every 8 ticks.
|
|
// LOOM uses either 236.696 Hz at 8 ticks delay or 473.297 Hz at 16 ticks delay, depending on the sound card selection.
|
|
// The outcome is the same...
|
|
_shakeTickCounter += ((1000000 / _shakeTimerRate) * 8);
|
|
_shakeNextTick += (_shakeTickCounter / 1000);
|
|
_shakeTickCounter %= 1000;
|
|
}
|
|
}
|
|
|
|
void MajMinCodec::setupBitReader(byte shift, const byte *src) {
|
|
_majMinData.repeatMode = false;
|
|
_majMinData.numBits = 16;
|
|
_majMinData.shift = shift;
|
|
_majMinData.color = *src;
|
|
_majMinData.bits = (*(src + 1) | *(src + 2) << 8);
|
|
_majMinData.dataPtr = src + 3;
|
|
}
|
|
|
|
#define MAJMIN_FILL_BITS() \
|
|
if (_majMinData.numBits <= 8) { \
|
|
_majMinData.bits |= (*_majMinData.dataPtr++) << _majMinData.numBits; \
|
|
_majMinData.numBits += 8; \
|
|
}
|
|
|
|
#define MAJMIN_EAT_BITS(n) \
|
|
_majMinData.numBits -= (n); \
|
|
_majMinData.bits >>= (n);
|
|
|
|
byte MajMinCodec::readBits(byte n) {
|
|
MAJMIN_FILL_BITS();
|
|
byte _value = _majMinData.bits & ((1 << n) - 1);
|
|
MAJMIN_EAT_BITS(n);
|
|
return _value;
|
|
}
|
|
|
|
void MajMinCodec::skipData(int32 numbytes) {
|
|
decodeLine(nullptr, numbytes, 0);
|
|
}
|
|
|
|
void MajMinCodec::decodeLine(byte *buf, int32 numbytes, int32 dir) {
|
|
byte diff;
|
|
|
|
while (numbytes != 0) {
|
|
if (buf) {
|
|
*buf = _majMinData.color;
|
|
buf += dir;
|
|
}
|
|
|
|
if (!_majMinData.repeatMode) {
|
|
if (readBits(1)) {
|
|
if (readBits(1)) {
|
|
diff = readBits(3) - 4;
|
|
if (diff) {
|
|
// A color change
|
|
_majMinData.color += diff;
|
|
} else {
|
|
// Color does not change, but rather identical pixels get repeated
|
|
_majMinData.repeatMode = true;
|
|
_majMinData.repeatCount = readBits(8) - 1;
|
|
}
|
|
} else {
|
|
_majMinData.color = readBits(_majMinData.shift);
|
|
}
|
|
}
|
|
} else {
|
|
if (--_majMinData.repeatCount == 0) {
|
|
_majMinData.repeatMode = false;
|
|
}
|
|
}
|
|
numbytes--;
|
|
}
|
|
}
|
|
|
|
} // End of namespace Scumm
|