Initial commit

This commit is contained in:
2026-02-02 04:50:13 +01:00
commit 5b11698731
22592 changed files with 7677434 additions and 0 deletions

View File

@@ -0,0 +1,307 @@
/* 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 "sherlock/scalpel/scalpel_screen.h"
#include "sherlock/scalpel/scalpel.h"
#include "sherlock/scalpel/3do/scalpel_3do_screen.h"
namespace Sherlock {
namespace Scalpel {
Scalpel3DOScreen::Scalpel3DOScreen(SherlockEngine *vm): ScalpelScreen(vm) {
}
void Scalpel3DOScreen::SHblitFrom(const Graphics::Surface &src) {
SHblitFrom(src, Common::Point(0, 0));
}
void Scalpel3DOScreen::SHblitFrom(const Graphics::Surface &src, const Common::Point &destPos) {
SHblitFrom(src, destPos, Common::Rect(0, 0, src.w, src.h));
}
void Scalpel3DOScreen::SHblitFrom(const Graphics::Surface &src, const Common::Point &pt, const Common::Rect &srcBounds) {
if (!_vm->_isScreenDoubled) {
ScalpelScreen::SHblitFrom(src, pt, srcBounds);
return;
}
Common::Rect srcRect = srcBounds;
srcRect.clip(320, 200);
Common::Rect destRect(pt.x, pt.y, pt.x + srcRect.width(), pt.y + srcRect.height());
if (!srcRect.isValidRect() || !clip(srcRect, destRect))
return;
// Add dirty area remapped to the 640x200 surface
addDirtyRect(Common::Rect(destRect.left * 2, destRect.top * 2, destRect.right * 2, destRect.bottom * 2));
// Transfer the area, doubling each pixel
for (int yp = 0; yp < srcRect.height(); ++yp) {
const uint16 *srcP = (const uint16 *)src.getBasePtr(srcRect.left, srcRect.top + yp);
uint16 *destP = (uint16 *)getBasePtr(destRect.left * 2, (destRect.top + yp) * 2);
for (int xp = srcRect.left; xp < srcRect.right; ++xp, ++srcP, destP += 2) {
*destP = *srcP;
*(destP + 1) = *srcP;
*(destP + 640) = *srcP;
*(destP + 640 + 1) = *srcP;
}
}
}
void Scalpel3DOScreen::transBlitFromUnscaled(const Graphics::Surface &src, const Common::Point &pt,
bool flipped, int overrideColor) {
error("TODO: Refactor");
#if 0
if (!_vm->_isScreenDoubled) {
ScalpelScreen::transBlitFromUnscaled(src, pt, flipped, overrideColor);
return;
}
Common::Rect drawRect(0, 0, src.w, src.h);
Common::Rect destRect(pt.x, pt.y, pt.x + src.w, pt.y + src.h);
// Clip the display area to on-screen
if (!clip(drawRect, destRect))
// It's completely off-screen
return;
if (flipped)
drawRect = Common::Rect(src.w - drawRect.right, src.h - drawRect.bottom,
src.w - drawRect.left, src.h - drawRect.top);
Common::Point destPt(destRect.left, destRect.top);
addDirtyRect(Common::Rect(destPt.x * 2, destPt.y * 2, (destPt.x + drawRect.width()) * 2,
(destPt.y + drawRect.height()) * 2));
assert(src.format.bytesPerPixel == 2 && _surface.format.bytesPerPixel == 2);
for (int yp = 0; yp < drawRect.height(); ++yp) {
const uint16 *srcP = (const uint16 *)src.getBasePtr(
flipped ? drawRect.right - 1 : drawRect.left, drawRect.top + yp);
uint16 *destP = (uint16 *)getBasePtr(destPt.x * 2, (destPt.y + yp) * 2);
for (int xp = 0; xp < drawRect.width(); ++xp, destP += 2) {
// RGB 0, 0, 0 -> transparent on 3DO
if (*srcP) {
*destP = *srcP;
*(destP + 1) = *srcP;
*(destP + 640) = *srcP;
*(destP + 640 + 1) = *srcP;
}
srcP = flipped ? srcP - 1 : srcP + 1;
}
}
#endif
}
void Scalpel3DOScreen::SHfillRect(const Common::Rect &r, uint color) {
if (_vm->_isScreenDoubled)
ScalpelScreen::fillRect(Common::Rect(r.left * 2, r.top * 2, r.right * 2, r.bottom * 2), color);
else
ScalpelScreen::fillRect(r, color);
}
void Scalpel3DOScreen::SHtransBlitFrom(const ImageFrame &src, const Common::Point &pt,
bool flipped, int scaleVal) {
ScalpelScreen::SHtransBlitFrom(src, pt, flipped,
_vm->_isScreenDoubled ? scaleVal / 2 : scaleVal);
}
void Scalpel3DOScreen::SHtransBlitFrom(const Graphics::Surface &src, const Common::Point &pt,
bool flipped, int scaleVal) {
ScalpelScreen::SHtransBlitFrom(src, pt, flipped,
_vm->_isScreenDoubled ? scaleVal / 2 : scaleVal);
}
void Scalpel3DOScreen::fadeIntoScreen3DO(int speed) {
Events &events = *_vm->_events;
uint16 *currentScreenBasePtr = (uint16 *)getPixels();
uint16 *targetScreenBasePtr = (uint16 *)_backBuffer.getPixels();
uint16 currentScreenPixel = 0;
uint16 targetScreenPixel = 0;
uint16 currentScreenPixelRed = 0;
uint16 currentScreenPixelGreen = 0;
uint16 currentScreenPixelBlue = 0;
uint16 targetScreenPixelRed = 0;
uint16 targetScreenPixelGreen = 0;
uint16 targetScreenPixelBlue = 0;
uint16 screenWidth = SHERLOCK_SCREEN_WIDTH;
uint16 screenHeight = SHERLOCK_SCREEN_HEIGHT;
uint16 screenX = 0;
uint16 screenY = 0;
uint16 pixelsChanged = 0;
clearDirtyRects();
do {
pixelsChanged = 0;
uint16 *currentScreenPtr = currentScreenBasePtr;
uint16 *targetScreenPtr = targetScreenBasePtr;
for (screenY = 0; screenY < screenHeight; screenY++) {
for (screenX = 0; screenX < screenWidth; screenX++) {
currentScreenPixel = *currentScreenPtr;
targetScreenPixel = *targetScreenPtr;
if (currentScreenPixel != targetScreenPixel) {
// pixel doesn't match, adjust accordingly
currentScreenPixelRed = currentScreenPixel & 0xF800;
currentScreenPixelGreen = currentScreenPixel & 0x07E0;
currentScreenPixelBlue = currentScreenPixel & 0x001F;
targetScreenPixelRed = targetScreenPixel & 0xF800;
targetScreenPixelGreen = targetScreenPixel & 0x07E0;
targetScreenPixelBlue = targetScreenPixel & 0x001F;
if (currentScreenPixelRed != targetScreenPixelRed) {
if (currentScreenPixelRed < targetScreenPixelRed) {
currentScreenPixelRed += 0x0800;
} else {
currentScreenPixelRed -= 0x0800;
}
}
if (currentScreenPixelGreen != targetScreenPixelGreen) {
// Adjust +2/-2 because we are running RGB555 at RGB565
if (currentScreenPixelGreen < targetScreenPixelGreen) {
currentScreenPixelGreen += 0x0040;
} else {
currentScreenPixelGreen -= 0x0040;
}
}
if (currentScreenPixelBlue != targetScreenPixelBlue) {
if (currentScreenPixelBlue < targetScreenPixelBlue) {
currentScreenPixelBlue += 0x0001;
} else {
currentScreenPixelBlue -= 0x0001;
}
}
uint16 v = currentScreenPixelRed | currentScreenPixelGreen | currentScreenPixelBlue;
*currentScreenPtr = v;
if (_vm->_isScreenDoubled) {
*(currentScreenPtr + 1) = v;
*(currentScreenPtr + 640) = v;
*(currentScreenPtr + 640 + 1) = v;
}
pixelsChanged++;
}
currentScreenPtr += _vm->_isScreenDoubled ? 2 : 1;
targetScreenPtr++;
}
if (_vm->_isScreenDoubled)
currentScreenPtr += 640;
}
// Too much considered dirty at the moment
if (_vm->_isScreenDoubled)
addDirtyRect(Common::Rect(0, 0, screenWidth * 2, screenHeight * 2));
else
addDirtyRect(Common::Rect(0, 0, screenWidth, screenHeight));
events.pollEvents();
events.delay(10 * speed);
} while ((pixelsChanged) && (!_vm->shouldQuit()));
}
void Scalpel3DOScreen::blitFrom3DOcolorLimit(uint16 limitColor) {
uint16 *currentScreenPtr = (uint16 *)getPixels();
uint16 *targetScreenPtr = (uint16 *)_backBuffer.getPixels();
uint16 currentScreenPixel = 0;
uint16 screenWidth = SHERLOCK_SCREEN_WIDTH;
uint16 screenHeight = SHERLOCK_SCREEN_HEIGHT;
uint16 screenX = 0;
uint16 screenY = 0;
uint16 currentScreenPixelRed = 0;
uint16 currentScreenPixelGreen = 0;
uint16 currentScreenPixelBlue = 0;
uint16 limitPixelRed = limitColor & 0xF800;
uint16 limitPixelGreen = limitColor & 0x07E0;
uint16 limitPixelBlue = limitColor & 0x001F;
for (screenY = 0; screenY < screenHeight; screenY++) {
for (screenX = 0; screenX < screenWidth; screenX++) {
currentScreenPixel = *targetScreenPtr;
currentScreenPixelRed = currentScreenPixel & 0xF800;
currentScreenPixelGreen = currentScreenPixel & 0x07E0;
currentScreenPixelBlue = currentScreenPixel & 0x001F;
if (currentScreenPixelRed < limitPixelRed)
currentScreenPixelRed = limitPixelRed;
if (currentScreenPixelGreen < limitPixelGreen)
currentScreenPixelGreen = limitPixelGreen;
if (currentScreenPixelBlue < limitPixelBlue)
currentScreenPixelBlue = limitPixelBlue;
uint16 v = currentScreenPixelRed | currentScreenPixelGreen | currentScreenPixelBlue;
*currentScreenPtr = v;
if (_vm->_isScreenDoubled) {
*(currentScreenPtr + 1) = v;
*(currentScreenPtr + 640) = v;
*(currentScreenPtr + 640 + 1) = v;
}
currentScreenPtr += _vm->_isScreenDoubled ? 2 : 1;
targetScreenPtr++;
}
if (_vm->_isScreenDoubled)
currentScreenPtr += 640;
}
// Too much considered dirty at the moment
if (_vm->_isScreenDoubled)
addDirtyRect(Common::Rect(0, 0, screenWidth * 2, screenHeight * 2));
else
addDirtyRect(Common::Rect(0, 0, screenWidth, screenHeight));
}
uint16 Scalpel3DOScreen::width() const {
return _vm->_isScreenDoubled ? this->w / 2 : this->w;
}
uint16 Scalpel3DOScreen::height() const {
return _vm->_isScreenDoubled ? this->h / 2 : this->h;
}
void Scalpel3DOScreen::rawBlitFrom(const Graphics::Surface &src, const Common::Point &pt) {
Common::Rect srcRect(0, 0, src.w, src.h);
Common::Rect destRect(pt.x, pt.y, pt.x + src.w, pt.y + src.h);
addDirtyRect(destRect);
copyRectToSurface(src, destRect.left, destRect.top, srcRect);
}
} // End of namespace Scalpel
} // End of namespace Sherlock

View File

@@ -0,0 +1,96 @@
/* 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/>.
*
*/
#ifndef SHERLOCK_SCALPEL_3DO_SCREEN_H
#define SHERLOCK_SCALPEL_3DO_SCREEN_H
#include "sherlock/scalpel/scalpel_screen.h"
namespace Sherlock {
class SherlockEngine;
namespace Scalpel {
class Scalpel3DOScreen : public ScalpelScreen {
protected:
/**
* Draws a surface at a given position within this surface with transparency
*/
virtual void transBlitFromUnscaled(const Graphics::Surface &src, const Common::Point &pt, bool flipped,
int overrideColor);
public:
Scalpel3DOScreen(SherlockEngine *vm);
~Scalpel3DOScreen() override {}
/**
* Draws a sub-section of a surface at a given position within this surface
*/
void rawBlitFrom(const Graphics::Surface &src, const Common::Point &pt);
/**
* Fade backbuffer 1 into screen (3DO RGB!)
*/
void fadeIntoScreen3DO(int speed);
void blitFrom3DOcolorLimit(uint16 color);
/**
* Draws a surface on this surface
*/
void SHblitFrom(const Graphics::Surface &src) override;
/**
* Draws a surface at a given position within this surface
*/
void SHblitFrom(const Graphics::Surface &src, const Common::Point &destPos) override;
/**
* Draws a sub-section of a surface at a given position within this surface
*/
void SHblitFrom(const Graphics::Surface &src, const Common::Point &destPos, const Common::Rect &srcBounds) override;
/**
* Draws an image frame at a given position within this surface with transparency
*/
void SHtransBlitFrom(const ImageFrame &src, const Common::Point &pt,
bool flipped = false, int scaleVal = SCALE_THRESHOLD) override;
/**
* Draws an image frame at a given position within this surface with transparency
*/
void SHtransBlitFrom(const Graphics::Surface &src, const Common::Point &pt,
bool flipped = false, int scaleVal = SCALE_THRESHOLD) override;
/**
* Fill a given area of the surface with a given color
*/
void SHfillRect(const Common::Rect &r, uint color) override;
uint16 width() const override;
uint16 height() const override;
};
} // End of namespace Scalpel
} // End of namespace Sherlock
#endif

View File

@@ -0,0 +1,643 @@
/* 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 "sherlock/sherlock.h"
#include "common/system.h"
#include "common/textconsole.h"
#include "audio/fmopl.h"
#include "audio/mididrv.h"
namespace Sherlock {
#define SHERLOCK_ADLIB_VOICES_COUNT 9
#define SHERLOCK_ADLIB_NOTES_COUNT 96
byte operator1Register[SHERLOCK_ADLIB_VOICES_COUNT] = {
0x00, 0x01, 0x02, 0x08, 0x09, 0x0A, 0x10, 0x11, 0x12
};
byte operator2Register[SHERLOCK_ADLIB_VOICES_COUNT] = {
0x03, 0x04, 0x05, 0x0B, 0x0C, 0x0D, 0x13, 0x14, 0x15
};
struct percussionChannelEntry {
byte requiredNote;
byte replacementNote;
};
// hardcoded, dumped from ADHOM.DRV
const percussionChannelEntry percussionChannelTable[SHERLOCK_ADLIB_VOICES_COUNT] = {
{ 0x00, 0x00 },
{ 0x00, 0x00 },
{ 0x00, 0x00 },
{ 0x00, 0x00 },
{ 0x00, 0x00 },
{ 0x00, 0x00 },
{ 0x24, 0x0C },
{ 0x38, 0x01 },
{ 0x26, 0x1E }
};
struct InstrumentEntry {
byte reg20op1;
byte reg40op1;
byte reg60op1;
byte reg80op1;
byte regE0op1;
byte reg20op2;
byte reg40op2;
byte reg60op2;
byte reg80op2;
byte regE0op2;
byte regC0;
byte frequencyAdjust;
};
// hardcoded, dumped from ADHOM.DRV
const InstrumentEntry instrumentTable[] = {
{ 0x71, 0x89, 0x51, 0x11, 0x00, 0x61, 0x23, 0x42, 0x15, 0x01, 0x02, 0xF4 },
{ 0x22, 0x20, 0x97, 0x89, 0x00, 0xA2, 0x1F, 0x70, 0x07, 0x00, 0x0A, 0xF4 },
{ 0x70, 0x1A, 0x64, 0x13, 0x00, 0x20, 0x1F, 0x53, 0x46, 0x00, 0x0E, 0xF4 },
{ 0xB6, 0x4A, 0xB6, 0x32, 0x00, 0x11, 0x2B, 0xD1, 0x31, 0x00, 0x0E, 0xE8 },
{ 0x71, 0x8B, 0x51, 0x11, 0x00, 0x61, 0x20, 0x32, 0x35, 0x01, 0x02, 0xF4 },
{ 0x71, 0x8A, 0x51, 0x11, 0x00, 0x61, 0x20, 0x32, 0x25, 0x01, 0x02, 0xF4 },
{ 0x23, 0x0F, 0xF4, 0x04, 0x02, 0x2F, 0x25, 0xF0, 0x43, 0x00, 0x06, 0xE8 },
{ 0x71, 0x1C, 0x71, 0x03, 0x00, 0x21, 0x1F, 0x54, 0x17, 0x00, 0x0E, 0xF4 },
{ 0x71, 0x8A, 0x6E, 0x17, 0x00, 0x25, 0x27, 0x6B, 0x0E, 0x00, 0x02, 0xF4 },
{ 0x71, 0x1D, 0x81, 0x03, 0x00, 0x21, 0x1F, 0x64, 0x17, 0x00, 0x0E, 0xF4 },
{ 0x01, 0x4B, 0xF1, 0x50, 0x00, 0x01, 0x23, 0xD2, 0x76, 0x00, 0x06, 0xF4 },
{ 0x2F, 0xCA, 0xF8, 0xE5, 0x00, 0x21, 0x1F, 0xC0, 0xFF, 0x00, 0x00, 0xF4 },
{ 0x29, 0xCD, 0xF0, 0x91, 0x00, 0x21, 0x1F, 0xE0, 0x86, 0x00, 0x02, 0xF4 },
{ 0x24, 0xD0, 0xF0, 0x01, 0x00, 0x21, 0x1F, 0xE0, 0x86, 0x00, 0x02, 0xF4 },
{ 0x23, 0xC8, 0xF0, 0x01, 0x00, 0x21, 0x1F, 0xE0, 0x86, 0x00, 0x02, 0xF4 },
{ 0x64, 0xC9, 0xB0, 0x01, 0x00, 0x61, 0x1F, 0xF0, 0x86, 0x00, 0x02, 0xF4 },
{ 0x33, 0x85, 0xA1, 0x10, 0x00, 0x15, 0x9F, 0x72, 0x23, 0x00, 0x08, 0xF4 },
{ 0x31, 0x85, 0xA1, 0x10, 0x00, 0x15, 0x9F, 0x73, 0x33, 0x00, 0x08, 0xF4 },
{ 0x31, 0x81, 0xA1, 0x30, 0x00, 0x16, 0x9F, 0xC2, 0x74, 0x00, 0x08, 0xF4 },
{ 0x03, 0x8A, 0xF0, 0x7B, 0x00, 0x02, 0x9F, 0xF4, 0x7B, 0x00, 0x08, 0xF4 },
{ 0x03, 0x8A, 0xF0, 0x7B, 0x00, 0x01, 0x9F, 0xF4, 0x7B, 0x00, 0x08, 0xF4 },
{ 0x23, 0x8A, 0xF2, 0x7B, 0x00, 0x01, 0x9F, 0xF4, 0x7B, 0x00, 0x08, 0xF4 },
{ 0x32, 0x80, 0x01, 0x10, 0x00, 0x12, 0x9F, 0x72, 0x33, 0x00, 0x08, 0xF4 },
{ 0x32, 0x80, 0x01, 0x10, 0x00, 0x14, 0x9F, 0x73, 0x33, 0x00, 0x08, 0xF4 },
{ 0x31, 0x16, 0x73, 0x8E, 0x00, 0x21, 0x1F, 0x80, 0x9E, 0x00, 0x0E, 0xF4 },
{ 0x30, 0x16, 0x73, 0x7E, 0x00, 0x21, 0x1F, 0x80, 0x9E, 0x00, 0x0E, 0x00 },
{ 0x31, 0x94, 0x33, 0x73, 0x00, 0x21, 0x1F, 0xA0, 0x97, 0x00, 0x0E, 0xF4 },
{ 0x31, 0x94, 0xD3, 0x73, 0x00, 0x21, 0x20, 0xA0, 0x97, 0x00, 0x0E, 0xF4 },
{ 0x31, 0x45, 0xF1, 0x53, 0x00, 0x32, 0x1F, 0xF2, 0x27, 0x00, 0x06, 0xF4 },
{ 0x13, 0x0C, 0xF2, 0x01, 0x00, 0x15, 0x2F, 0xF2, 0xB6, 0x00, 0x08, 0xF4 },
{ 0x11, 0x0C, 0xF2, 0x01, 0x00, 0x11, 0x1F, 0xF2, 0xB6, 0x00, 0x08, 0xF4 },
{ 0x11, 0x0A, 0xFE, 0x04, 0x00, 0x11, 0x1F, 0xF2, 0xBD, 0x00, 0x08, 0xF4 },
{ 0x16, 0x4D, 0xFA, 0x11, 0x00, 0xE1, 0x20, 0xF1, 0xF1, 0x00, 0x08, 0xF4 },
{ 0x16, 0x40, 0xBA, 0x11, 0x00, 0xF1, 0x20, 0x24, 0x31, 0x00, 0x08, 0xF4 },
{ 0x61, 0xA7, 0x72, 0x8E, 0x00, 0xE1, 0x9F, 0x50, 0x1A, 0x00, 0x02, 0xF4 },
{ 0x18, 0x4D, 0x32, 0x13, 0x00, 0xE1, 0x20, 0x51, 0xE3, 0x00, 0x08, 0xF4 },
{ 0x17, 0xC0, 0x12, 0x41, 0x00, 0x31, 0x9F, 0x13, 0x31, 0x00, 0x06, 0xF4 },
{ 0x03, 0x8F, 0xF5, 0x55, 0x00, 0x21, 0x9F, 0xF3, 0x33, 0x00, 0x00, 0xF4 },
{ 0x13, 0x4D, 0xFA, 0x11, 0x00, 0xE1, 0x20, 0xF1, 0xF1, 0x00, 0x08, 0xF4 },
{ 0x11, 0x43, 0x20, 0x15, 0x00, 0xF1, 0x20, 0x31, 0xF8, 0x00, 0x08, 0xF4 },
{ 0x11, 0x03, 0x82, 0x97, 0x00, 0xE4, 0x60, 0xF0, 0xF2, 0x00, 0x08, 0xF4 },
{ 0x05, 0x40, 0xD1, 0x53, 0x00, 0x14, 0x1F, 0x51, 0x71, 0x00, 0x06, 0xF4 },
{ 0xF1, 0x01, 0x77, 0x17, 0x00, 0x21, 0x1F, 0x81, 0x18, 0x00, 0x02, 0xF4 },
{ 0xF1, 0x18, 0x32, 0x11, 0x00, 0xE1, 0x1F, 0xF1, 0x13, 0x00, 0x00, 0xF4 },
{ 0x73, 0x48, 0xF1, 0x53, 0x00, 0x71, 0x1F, 0xF1, 0x06, 0x00, 0x08, 0xF4 },
{ 0x71, 0x8D, 0x71, 0x11, 0x00, 0x61, 0x5F, 0x72, 0x15, 0x00, 0x06, 0xF4 },
{ 0xD7, 0x4F, 0xF2, 0x61, 0x00, 0xD2, 0x1F, 0xF1, 0xB2, 0x00, 0x08, 0xF4 },
{ 0x01, 0x11, 0xF0, 0xFF, 0x00, 0x01, 0x1F, 0xF0, 0xF8, 0x00, 0x0A, 0xF4 },
{ 0x31, 0x8B, 0x41, 0x11, 0x00, 0x61, 0x1F, 0x22, 0x13, 0x00, 0x06, 0xF4 },
{ 0x71, 0x1C, 0x71, 0x03, 0x00, 0x21, 0x1F, 0x64, 0x07, 0x00, 0x0E, 0xF4 },
{ 0x31, 0x8B, 0x41, 0x11, 0x00, 0x61, 0x1F, 0x32, 0x15, 0x00, 0x02, 0xF4 },
{ 0x71, 0x1C, 0xFD, 0x13, 0x00, 0x21, 0x1F, 0xE7, 0xD6, 0x00, 0x0E, 0xF4 },
{ 0x71, 0x1C, 0x51, 0x03, 0x00, 0x21, 0x1F, 0x54, 0x67, 0x00, 0x0E, 0xF4 },
{ 0x71, 0x1C, 0x51, 0x03, 0x00, 0x21, 0x1F, 0x54, 0x17, 0x00, 0x0E, 0xF4 },
{ 0x71, 0x1C, 0x54, 0x15, 0x00, 0x21, 0x1F, 0x53, 0x49, 0x00, 0x0E, 0xF4 },
{ 0x71, 0x56, 0x51, 0x03, 0x00, 0x61, 0x1F, 0x54, 0x17, 0x00, 0x0E, 0xF4 },
{ 0x71, 0x1C, 0x51, 0x03, 0x00, 0x21, 0x1F, 0x54, 0x17, 0x00, 0x0E, 0xF4 },
{ 0x02, 0x29, 0xF5, 0x75, 0x00, 0x01, 0x9F, 0xF2, 0xF3, 0x00, 0x00, 0xF4 },
{ 0x02, 0x29, 0xF0, 0x75, 0x00, 0x01, 0x9F, 0xF4, 0x33, 0x00, 0x00, 0xF4 },
{ 0x01, 0x49, 0xF1, 0x53, 0x00, 0x11, 0x1F, 0xF1, 0x74, 0x00, 0x06, 0xF4 },
{ 0x01, 0x89, 0xF1, 0x53, 0x00, 0x11, 0x1F, 0xF1, 0x74, 0x00, 0x06, 0xF4 },
{ 0x02, 0x89, 0xF1, 0x53, 0x00, 0x11, 0x1F, 0xF1, 0x74, 0x00, 0x06, 0xF4 },
{ 0x02, 0x80, 0xF1, 0x53, 0x00, 0x11, 0x1F, 0xF1, 0x74, 0x00, 0x06, 0xF4 },
{ 0x01, 0x40, 0xF1, 0x53, 0x00, 0x08, 0x5F, 0xF1, 0x53, 0x00, 0x00, 0xF4 },
{ 0x21, 0x15, 0xD3, 0x2C, 0x00, 0x21, 0x9F, 0xC3, 0x2C, 0x00, 0x0A, 0xF4 },
{ 0x01, 0x18, 0xD4, 0xF2, 0x00, 0x21, 0x9F, 0xC4, 0x8A, 0x00, 0x0A, 0xF4 },
{ 0x01, 0x4E, 0xF0, 0x7B, 0x00, 0x11, 0x1F, 0xF4, 0xC8, 0x00, 0x04, 0xF4 },
{ 0x01, 0x44, 0xF0, 0xAB, 0x00, 0x11, 0x1F, 0xF3, 0xAB, 0x00, 0x04, 0xF4 },
{ 0x53, 0x0E, 0xF4, 0xC8, 0x00, 0x11, 0x1F, 0xF1, 0xBB, 0x00, 0x04, 0xF4 },
{ 0x53, 0x0B, 0xF2, 0xC8, 0x00, 0x11, 0x1F, 0xF2, 0xC5, 0x00, 0x04, 0xF4 },
{ 0x21, 0x15, 0xB4, 0x4C, 0x00, 0x21, 0x1F, 0x94, 0xAC, 0x00, 0x0A, 0xF4 },
{ 0x21, 0x15, 0x94, 0x1C, 0x00, 0x21, 0x1F, 0x64, 0xAC, 0x00, 0x0A, 0xF4 },
{ 0x22, 0x1B, 0x97, 0x89, 0x00, 0xA2, 0x1F, 0x70, 0x07, 0x00, 0x0A, 0xF4 },
{ 0x21, 0x19, 0x77, 0xBF, 0x00, 0xA1, 0x9F, 0x60, 0x2A, 0x00, 0x06, 0xF4 },
{ 0xA1, 0x13, 0xD6, 0xAF, 0x00, 0xE2, 0x9F, 0x60, 0x2A, 0x00, 0x02, 0xF4 },
{ 0xA2, 0x1D, 0x95, 0x24, 0x00, 0xE2, 0x9F, 0x60, 0x2A, 0x00, 0x02, 0xF4 },
{ 0x32, 0x9A, 0x51, 0x19, 0x00, 0x61, 0x9F, 0x60, 0x39, 0x00, 0x0C, 0xF4 },
{ 0xA4, 0x12, 0xF4, 0x30, 0x00, 0xE2, 0x9F, 0x60, 0x2A, 0x00, 0x02, 0xF4 },
{ 0x21, 0x16, 0x63, 0x0E, 0x00, 0x21, 0x1F, 0x63, 0x0E, 0x00, 0x0C, 0xF4 },
{ 0x31, 0x16, 0x63, 0x0A, 0x00, 0x21, 0x1F, 0x63, 0x0B, 0x00, 0x0C, 0xF4 },
{ 0x21, 0x1B, 0x63, 0x0A, 0x00, 0x21, 0x1F, 0x63, 0x0B, 0x00, 0x0C, 0xF4 },
{ 0x20, 0x1B, 0x63, 0x0A, 0x00, 0x21, 0x1F, 0x63, 0x0B, 0x00, 0x0C, 0xF4 },
{ 0x32, 0x1C, 0x82, 0x18, 0x00, 0x61, 0x9F, 0x60, 0x07, 0x00, 0x0C, 0xF4 },
{ 0x32, 0x18, 0x61, 0x14, 0x00, 0xE1, 0x9F, 0x72, 0x16, 0x00, 0x0C, 0xF4 },
{ 0x31, 0xC0, 0x77, 0x17, 0x00, 0x22, 0x1F, 0x6B, 0x09, 0x00, 0x02, 0xF4 },
{ 0x71, 0xC3, 0x8E, 0x17, 0x00, 0x22, 0x24, 0x8B, 0x0E, 0x00, 0x02, 0xF4 },
{ 0x70, 0x8D, 0x6E, 0x17, 0x00, 0x22, 0x1F, 0x6B, 0x0E, 0x00, 0x02, 0xF4 },
{ 0x24, 0x4F, 0xF2, 0x06, 0x00, 0x31, 0x1F, 0x52, 0x06, 0x00, 0x0E, 0xF4 },
{ 0x31, 0x1B, 0x64, 0x07, 0x00, 0x61, 0x1F, 0xD0, 0x67, 0x00, 0x0E, 0xF4 },
{ 0x31, 0x1B, 0x61, 0x06, 0x00, 0x61, 0x1F, 0xD2, 0x36, 0x00, 0x0C, 0xF4 },
{ 0x31, 0x1F, 0x31, 0x06, 0x00, 0x61, 0x1F, 0x50, 0x36, 0x00, 0x0C, 0xF4 },
{ 0x31, 0x1F, 0x41, 0x06, 0x00, 0x61, 0x1F, 0xA0, 0x36, 0x00, 0x0C, 0xF4 },
{ 0x21, 0x9A, 0x53, 0x56, 0x00, 0x21, 0x9F, 0xA0, 0x16, 0x00, 0x0E, 0xF4 },
{ 0x21, 0x9A, 0x53, 0x56, 0x00, 0x21, 0x9F, 0xA0, 0x16, 0x00, 0x0E, 0xF4 },
{ 0x61, 0x19, 0x53, 0x58, 0x00, 0x21, 0x1F, 0xA0, 0x18, 0x00, 0x0C, 0xF4 },
{ 0x61, 0x19, 0x73, 0x57, 0x00, 0x21, 0x1F, 0xA0, 0x17, 0x00, 0x0C, 0xF4 },
{ 0x21, 0x1B, 0x71, 0xA6, 0x00, 0x21, 0x1F, 0xA1, 0x96, 0x00, 0x0E, 0xF4 },
{ 0x85, 0x91, 0xF5, 0x44, 0x00, 0xA1, 0x1F, 0xF0, 0x45, 0x00, 0x06, 0xF4 },
{ 0x07, 0x51, 0xF5, 0x33, 0x00, 0x61, 0x1F, 0xF0, 0x25, 0x00, 0x06, 0xF4 },
{ 0x13, 0x8C, 0xFF, 0x21, 0x00, 0x11, 0x9F, 0xFF, 0x03, 0x00, 0x0E, 0xF4 },
{ 0x38, 0x8C, 0xF3, 0x0D, 0x00, 0xB1, 0x5F, 0xF5, 0x33, 0x00, 0x0E, 0xF4 },
{ 0x87, 0x91, 0xF5, 0x55, 0x00, 0x22, 0x1F, 0xF0, 0x54, 0x00, 0x06, 0xF4 },
{ 0xB6, 0x4A, 0xB6, 0x32, 0x00, 0x11, 0x2B, 0xD1, 0x31, 0x00, 0x0E, 0xF4 },
{ 0x04, 0x00, 0xFE, 0xF0, 0x00, 0xC2, 0x1F, 0xF6, 0xB5, 0x00, 0x0E, 0xF4 },
{ 0x05, 0x4E, 0xDA, 0x15, 0x00, 0x01, 0x9F, 0xF0, 0x13, 0x00, 0x0A, 0xF4 },
{ 0x31, 0x44, 0xF2, 0x9A, 0x00, 0x32, 0x1F, 0xF0, 0x27, 0x00, 0x06, 0xF4 },
{ 0xB0, 0xC4, 0xA4, 0x02, 0x00, 0xD7, 0x9F, 0x40, 0x42, 0x00, 0x00, 0xF4 },
{ 0xCA, 0x84, 0xF0, 0xF0, 0x00, 0xCF, 0x1F, 0x59, 0x62, 0x00, 0x0C, 0xF4 },
{ 0x30, 0x35, 0xF5, 0xF0, 0x00, 0x35, 0x1F, 0xF0, 0x9B, 0x00, 0x02, 0xF4 },
{ 0x63, 0x0F, 0xF4, 0x04, 0x02, 0x6F, 0x1F, 0xF0, 0x43, 0x00, 0x06, 0xF4 },
{ 0x07, 0x40, 0x09, 0x53, 0x00, 0x05, 0x1F, 0xF6, 0x94, 0x00, 0x0E, 0xF4 },
{ 0x09, 0x4E, 0xDA, 0x25, 0x00, 0x01, 0x1F, 0xF1, 0x15, 0x00, 0x0A, 0xF4 },
{ 0x04, 0x00, 0xF3, 0xA0, 0x02, 0x04, 0x1F, 0xF8, 0x46, 0x00, 0x0E, 0xF4 },
{ 0x07, 0x00, 0xF0, 0xF0, 0x00, 0x00, 0x1F, 0x5C, 0xDC, 0x00, 0x0E, 0xF4 },
{ 0x1F, 0x1E, 0xE5, 0x5B, 0x00, 0x0F, 0x1F, 0x5D, 0xFA, 0x00, 0x0E, 0xF4 },
{ 0x11, 0x8A, 0xF1, 0x11, 0x00, 0x01, 0x5F, 0xF1, 0xB3, 0x00, 0x06, 0xF4 },
{ 0x00, 0x40, 0xD1, 0x53, 0x00, 0x00, 0x1F, 0xF2, 0x56, 0x00, 0x0E, 0xF4 },
{ 0x32, 0x44, 0xF8, 0xFF, 0x00, 0x11, 0x1F, 0xF5, 0x7F, 0x00, 0x0E, 0xF4 },
{ 0x00, 0x40, 0x09, 0x53, 0x00, 0x02, 0x1F, 0xF7, 0x94, 0x00, 0x0E, 0xF4 },
{ 0x11, 0x86, 0xF2, 0xA8, 0x00, 0x01, 0x9F, 0xA0, 0xA8, 0x00, 0x08, 0xF4 },
{ 0x00, 0x50, 0xF2, 0x70, 0x00, 0x13, 0x1F, 0xF2, 0x72, 0x00, 0x0E, 0xF4 },
{ 0xF0, 0x00, 0x11, 0x11, 0x00, 0xE0, 0xDF, 0x11, 0x11, 0x00, 0x0E, 0xF4 }
};
// hardcoded, dumped from ADHOM.DRV
uint16 frequencyLookUpTable[SHERLOCK_ADLIB_NOTES_COUNT] = {
0x0158, 0x016C, 0x0182, 0x0199, 0x01B1, 0x01CB, 0x01E6, 0x0203, 0x0222, 0x0242,
0x0265, 0x0289, 0x0558, 0x056C, 0x0582, 0x0599, 0x05B1, 0x05CB, 0x05E6, 0x0603,
0x0622, 0x0642, 0x0665, 0x0689, 0x0958, 0x096C, 0x0982, 0x0999, 0x09B1, 0x09CB,
0x09E6, 0x0A03, 0x0A22, 0x0A42, 0x0A65, 0x0A89, 0x0D58, 0x0D6C, 0x0D82, 0x0D99,
0x0DB1, 0x0DCB, 0x0DE6, 0x0E03, 0x0E22, 0x0E42, 0x0E65, 0x0E89, 0x1158, 0x116C,
0x1182, 0x1199, 0x11B1, 0x11CB, 0x11E6, 0x1203, 0x1222, 0x1242, 0x1265, 0x1289,
0x1558, 0x156C, 0x1582, 0x1599, 0x15B1, 0x15CB, 0x15E6, 0x1603, 0x1622, 0x1642,
0x1665, 0x1689, 0x1958, 0x196C, 0x1982, 0x1999, 0x19B1, 0x19CB, 0x19E6, 0x1A03,
0x1A22, 0x1A42, 0x1A65, 0x1A89, 0x1D58, 0x1D6C, 0x1D82, 0x1D99, 0x1DB1, 0x1DCB,
0x1DE6, 0x1E03, 0x1E22, 0x1E42, 0x1E65, 0x1E89
};
class MidiDriver_SH_AdLib : public MidiDriver {
public:
MidiDriver_SH_AdLib(Audio::Mixer *mixer)
: _masterVolume(15), _opl(nullptr),
_adlibTimerProc(nullptr), _adlibTimerParam(nullptr), _isOpen(false) {
memset(_voiceChannelMapping, 0, sizeof(_voiceChannelMapping));
}
~MidiDriver_SH_AdLib() override { }
// MidiDriver
int open() override;
void close() override;
void send(uint32 b) override;
MidiChannel *allocateChannel() override { return nullptr; }
MidiChannel *getPercussionChannel() override { return nullptr; }
bool isOpen() const override { return _isOpen; }
uint32 getBaseTempo() override { return 1000000 / OPL::OPL::kDefaultCallbackFrequency; }
int getPolyphony() const { return SHERLOCK_ADLIB_VOICES_COUNT; }
bool hasRhythmChannel() const { return false; }
void setTimerCallback(void *timerParam, Common::TimerManager::TimerProc timerProc) override;
void setVolume(byte volume);
uint32 property(int prop, uint32 param) override;
void newMusicData(byte *musicData, int32 musicDataSize);
private:
struct adlib_ChannelEntry {
bool inUse;
uint16 inUseTimer;
const InstrumentEntry *currentInstrumentPtr;
byte currentNote;
byte currentA0hReg;
byte currentB0hReg;
adlib_ChannelEntry() : inUse(false), inUseTimer(0), currentInstrumentPtr(nullptr), currentNote(0),
currentA0hReg(0), currentB0hReg(0) { }
};
OPL::OPL *_opl;
int _masterVolume;
Common::TimerManager::TimerProc _adlibTimerProc;
void *_adlibTimerParam;
bool _isOpen;
// points to a MIDI channel for each of the new voice channels
byte _voiceChannelMapping[SHERLOCK_ADLIB_VOICES_COUNT];
// stores information about all FM voice channels
adlib_ChannelEntry _channels[SHERLOCK_ADLIB_VOICES_COUNT];
void onTimer();
void resetAdLib();
void resetAdLibOperatorRegisters(byte baseRegister, byte value);
void resetAdLibFMVoiceChannelRegisters(byte baseRegister, byte value);
void programChange(byte MIDIchannel, byte parameter);
void setRegister(int reg, int value);
void noteOn(byte MIDIchannel, byte note, byte velocity);
void noteOff(byte MIDIchannel, byte note);
void voiceOnOff(byte FMVoiceChannel, bool KeyOn, byte note, byte velocity);
void pitchBendChange(byte MIDIchannel, byte parameter1, byte parameter2);
};
int MidiDriver_SH_AdLib::open() {
debugC(kDebugLevelAdLibDriver, "AdLib: starting driver");
_opl = OPL::Config::create(OPL::Config::kOpl2);
if (!_opl)
return -1;
_opl->init();
_isOpen = true;
_opl->start(new Common::Functor0Mem<void, MidiDriver_SH_AdLib>(this, &MidiDriver_SH_AdLib::onTimer));
return 0;
}
void MidiDriver_SH_AdLib::close() {
// Stop the OPL timer
_opl->stop();
delete _opl;
}
void MidiDriver_SH_AdLib::setVolume(byte volume) {
_masterVolume = volume;
//renewNotes(-1, true);
}
// this should/must get called per tick
// original driver did this before MIDI data processing on each tick
// we do it atm after MIDI data processing
void MidiDriver_SH_AdLib::onTimer() {
if (_adlibTimerProc)
(*_adlibTimerProc)(_adlibTimerParam);
// this should/must get called per tick
// original driver did this before MIDI data processing on each tick
// we do it atm after MIDI data processing
for (byte FMvoiceChannel = 0; FMvoiceChannel < SHERLOCK_ADLIB_VOICES_COUNT; FMvoiceChannel++) {
if (_channels[FMvoiceChannel].inUse) {
_channels[FMvoiceChannel].inUseTimer++;
}
}
}
// Called when a music track got loaded into memory
void MidiDriver_SH_AdLib::newMusicData(byte *musicData, int32 musicDataSize) {
assert(musicDataSize >= 0x7F);
// MIDI Channel <-> FM Voice Channel mapping at offset 0x22 of music data
memcpy(&_voiceChannelMapping, musicData + 0x22, 9);
// reset OPL
resetAdLib();
// reset current channel data
memset(&_channels, 0, sizeof(_channels));
}
void MidiDriver_SH_AdLib::resetAdLib() {
setRegister(0x01, 0x20); // enable waveform control on both operators
setRegister(0x04, 0xE0); // Timer control
setRegister(0x08, 0); // select FM music mode
setRegister(0xBD, 0); // disable Rhythm
// reset FM voice instrument data
resetAdLibOperatorRegisters(0x20, 0);
resetAdLibOperatorRegisters(0x60, 0);
resetAdLibOperatorRegisters(0x80, 0);
resetAdLibFMVoiceChannelRegisters(0xA0, 0);
resetAdLibFMVoiceChannelRegisters(0xB0, 0);
resetAdLibFMVoiceChannelRegisters(0xC0, 0);
resetAdLibOperatorRegisters(0xE0, 0);
resetAdLibOperatorRegisters(0x40, 0x3F);
}
void MidiDriver_SH_AdLib::resetAdLibOperatorRegisters(byte baseRegister, byte value) {
byte operatorIndex;
for (operatorIndex = 0; operatorIndex < 0x16; operatorIndex++) {
switch (operatorIndex) {
case 0x06:
case 0x07:
case 0x0E:
case 0x0F:
break;
default:
setRegister(baseRegister + operatorIndex, value);
}
}
}
void MidiDriver_SH_AdLib::resetAdLibFMVoiceChannelRegisters(byte baseRegister, byte value) {
byte FMvoiceChannel;
for (FMvoiceChannel = 0; FMvoiceChannel < SHERLOCK_ADLIB_VOICES_COUNT; FMvoiceChannel++) {
setRegister(baseRegister + FMvoiceChannel, value);
}
}
// MIDI messages can be found at https://web.archive.org/web/20120128110425/http://www.midi.org/techspecs/midimessages.php
void MidiDriver_SH_AdLib::send(uint32 b) {
byte command = b & 0xf0;
byte channel = b & 0xf;
byte op1 = (b >> 8) & 0xff;
byte op2 = (b >> 16) & 0xff;
switch (command) {
case 0x80:
noteOff(channel, op1);
break;
case 0x90:
noteOn(channel, op1, op2);
break;
case 0xb0: // Control change
// Doesn't seem to be implemented in the Sherlock Holmes adlib driver
break;
case 0xc0: // Program Change
programChange(channel, op1);
break;
case 0xa0: // Polyphonic key pressure (aftertouch)
case 0xd0: // Channel pressure (aftertouch)
// Aftertouch doesn't seem to be implemented in the Sherlock Holmes adlib driver
break;
case 0xe0:
debugC(kDebugLevelAdLibDriver, "AdLib: pitch bend change");
pitchBendChange(channel, op1, op2);
break;
case 0xf0: // SysEx
warning("ADLIB: SysEx: %x", b);
break;
default:
warning("ADLIB: Unknown event %02x", command);
}
}
void MidiDriver_SH_AdLib::noteOn(byte MIDIchannel, byte note, byte velocity) {
int16 oldestInUseChannel = -1;
uint16 oldestInUseTimer = 0;
if (velocity == 0)
return noteOff(MIDIchannel, note);
if (MIDIchannel != 9) {
// Not Percussion
for (byte FMvoiceChannel = 0; FMvoiceChannel < SHERLOCK_ADLIB_VOICES_COUNT; FMvoiceChannel++) {
if (_voiceChannelMapping[FMvoiceChannel] == MIDIchannel) {
if (!_channels[FMvoiceChannel].inUse) {
_channels[FMvoiceChannel].inUse = true;
_channels[FMvoiceChannel].currentNote = note;
voiceOnOff(FMvoiceChannel, true, note, velocity);
return;
}
}
}
// Look for oldest in-use channel
for (byte FMvoiceChannel = 0; FMvoiceChannel < SHERLOCK_ADLIB_VOICES_COUNT; FMvoiceChannel++) {
if (_voiceChannelMapping[FMvoiceChannel] == MIDIchannel) {
if (_channels[FMvoiceChannel].inUseTimer > oldestInUseTimer) {
oldestInUseTimer = _channels[FMvoiceChannel].inUseTimer;
oldestInUseChannel = FMvoiceChannel;
}
}
}
if (oldestInUseChannel >= 0) {
// channel found
debugC(kDebugLevelAdLibDriver, "AdLib: used In-Use channel");
// original driver used note 0, we use the current note
// because using note 0 could create a bad note (out of index) and we check that. Original driver didn't.
voiceOnOff(oldestInUseChannel, false, _channels[oldestInUseChannel].currentNote, 0);
_channels[oldestInUseChannel].inUse = true;
_channels[oldestInUseChannel].inUseTimer = 0; // safety, original driver also did this
_channels[oldestInUseChannel].currentNote = note;
voiceOnOff(oldestInUseChannel, true, note, velocity);
return;
}
debugC(kDebugLevelAdLibDriver, "AdLib: MIDI channel not mapped/all FM voice channels busy %d", MIDIchannel);
} else {
// Percussion channel
for (byte FMvoiceChannel = 0; FMvoiceChannel < SHERLOCK_ADLIB_VOICES_COUNT; FMvoiceChannel++) {
if (_voiceChannelMapping[FMvoiceChannel] == MIDIchannel) {
if (note == percussionChannelTable[FMvoiceChannel].requiredNote) {
_channels[FMvoiceChannel].inUse = true;
_channels[FMvoiceChannel].currentNote = note;
voiceOnOff(FMvoiceChannel, true, percussionChannelTable[FMvoiceChannel].replacementNote, velocity);
return;
}
}
}
debugC(kDebugLevelAdLibDriver, "AdLib: percussion MIDI channel not mapped/all FM voice channels busy");
}
}
void MidiDriver_SH_AdLib::noteOff(byte MIDIchannel, byte note) {
for (byte FMvoiceChannel = 0; FMvoiceChannel < SHERLOCK_ADLIB_VOICES_COUNT; FMvoiceChannel++) {
if (_voiceChannelMapping[FMvoiceChannel] == MIDIchannel) {
if (_channels[FMvoiceChannel].currentNote == note) {
_channels[FMvoiceChannel].inUse = false;
_channels[FMvoiceChannel].inUseTimer = 0;
_channels[FMvoiceChannel].currentNote = 0;
if (MIDIchannel != 9) {
// not-percussion
voiceOnOff(FMvoiceChannel, false, note, 0);
} else {
voiceOnOff(FMvoiceChannel, false, percussionChannelTable[FMvoiceChannel].replacementNote, 0);
}
return;
}
}
}
}
void MidiDriver_SH_AdLib::voiceOnOff(byte FMvoiceChannel, bool keyOn, byte note, byte velocity) {
byte frequencyOffset = 0;
uint16 frequency = 0;
byte op2RegAdjust = 0;
byte regValue40h = 0;
byte regValueA0h = 0;
byte regValueB0h = 0;
// Look up frequency
if (_channels[FMvoiceChannel].currentInstrumentPtr) {
frequencyOffset = note + _channels[FMvoiceChannel].currentInstrumentPtr->frequencyAdjust;
} else {
frequencyOffset = note;
}
if (frequencyOffset >= SHERLOCK_ADLIB_NOTES_COUNT) {
warning("CRITICAL - AdLib driver: bad note!!!");
return;
}
frequency = frequencyLookUpTable[frequencyOffset];
if (keyOn) {
// adjust register 40h
if (_channels[FMvoiceChannel].currentInstrumentPtr) {
regValue40h = _channels[FMvoiceChannel].currentInstrumentPtr->reg40op2;
}
regValue40h = regValue40h - (velocity >> 3);
op2RegAdjust = operator2Register[FMvoiceChannel];
setRegister(0x40 + op2RegAdjust, regValue40h);
}
regValueA0h = frequency & 0xFF;
regValueB0h = frequency >> 8;
if (keyOn) {
regValueB0h |= 0x20; // set Key-On flag
}
setRegister(0xA0 + FMvoiceChannel, regValueA0h);
setRegister(0xB0 + FMvoiceChannel, regValueB0h);
_channels[FMvoiceChannel].currentA0hReg = regValueA0h;
_channels[FMvoiceChannel].currentB0hReg = regValueB0h;
}
void MidiDriver_SH_AdLib::pitchBendChange(byte MIDIchannel, byte parameter1, byte parameter2) {
uint16 channelFrequency = 0;
byte channelRegB0hWithoutFrequency = 0;
uint16 parameter = 0;
byte regValueA0h = 0;
byte regValueB0h = 0;
for (byte FMvoiceChannel = 0; FMvoiceChannel < SHERLOCK_ADLIB_VOICES_COUNT; FMvoiceChannel++) {
if (_voiceChannelMapping[FMvoiceChannel] == MIDIchannel) {
if (_channels[FMvoiceChannel].inUse) {
// FM voice channel found and it's currently in use -> apply pitch bend change
// Remove frequency bits from current channel B0h-register
channelFrequency = ((_channels[FMvoiceChannel].currentB0hReg << 8) | (_channels[FMvoiceChannel].currentA0hReg)) & 0x3FF;
channelRegB0hWithoutFrequency = _channels[FMvoiceChannel].currentB0hReg & 0xFC;
if (parameter2 < 0x40) {
channelFrequency = channelFrequency / 2;
} else {
parameter2 = parameter2 - 0x40;
}
parameter1 = parameter1 * 2;
parameter = parameter1 | (parameter2 << 8);
parameter = parameter * 4;
parameter = (parameter >> 8) + 0xFF;
channelFrequency = channelFrequency * parameter;
channelFrequency = (channelFrequency >> 8) | (parameter << 8);
regValueA0h = channelFrequency & 0xFF;
regValueB0h = (channelFrequency >> 8) | channelRegB0hWithoutFrequency;
setRegister(0xA0 + FMvoiceChannel, regValueA0h);
setRegister(0xB0 + FMvoiceChannel, regValueB0h);
}
}
}
}
void MidiDriver_SH_AdLib::programChange(byte MIDIchannel, byte op1) {
const InstrumentEntry *instrumentPtr;
byte op1Reg = 0;
byte op2Reg = 0;
// setup instrument
instrumentPtr = &instrumentTable[op1];
//warning("program change for MIDI channel %d, instrument id %d", MIDIchannel, op1);
for (byte FMvoiceChannel = 0; FMvoiceChannel < SHERLOCK_ADLIB_VOICES_COUNT; FMvoiceChannel++) {
if (_voiceChannelMapping[FMvoiceChannel] == MIDIchannel) {
op1Reg = operator1Register[FMvoiceChannel];
op2Reg = operator2Register[FMvoiceChannel];
setRegister(0x20 + op1Reg, instrumentPtr->reg20op1);
setRegister(0x40 + op1Reg, instrumentPtr->reg40op1);
setRegister(0x60 + op1Reg, instrumentPtr->reg60op1);
setRegister(0x80 + op1Reg, instrumentPtr->reg80op1);
setRegister(0xE0 + op1Reg, instrumentPtr->regE0op1);
setRegister(0x20 + op2Reg, instrumentPtr->reg20op2);
setRegister(0x40 + op2Reg, instrumentPtr->reg40op2);
setRegister(0x60 + op2Reg, instrumentPtr->reg60op2);
setRegister(0x80 + op2Reg, instrumentPtr->reg80op2);
setRegister(0xE0 + op2Reg, instrumentPtr->regE0op2);
setRegister(0xC0 + FMvoiceChannel, instrumentPtr->regC0);
// Remember instrument
_channels[FMvoiceChannel].currentInstrumentPtr = instrumentPtr;
}
}
}
void MidiDriver_SH_AdLib::setRegister(int reg, int value) {
_opl->write(0x220, reg);
_opl->write(0x221, value);
}
uint32 MidiDriver_SH_AdLib::property(int prop, uint32 param) {
return 0;
}
void MidiDriver_SH_AdLib::setTimerCallback(void *timerParam, Common::TimerManager::TimerProc timerProc) {
_adlibTimerProc = timerProc;
_adlibTimerParam = timerParam;
}
MidiDriver *MidiDriver_SH_AdLib_create() {
return new MidiDriver_SH_AdLib(g_system->getMixer());
}
void MidiDriver_SH_AdLib_newMusicData(MidiDriver *driver, byte *musicData, int32 musicDataSize) {
static_cast<MidiDriver_SH_AdLib *>(driver)->newMusicData(musicData, musicDataSize);
}
} // End of namespace Sherlock

View File

@@ -0,0 +1,40 @@
/* 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/>.
*
*/
#ifndef SHERLOCK_SCALPEL_DRIVERS_MIDIDRIVER_H
#define SHERLOCK_SCALPEL_DRIVERS_MIDIDRIVER_H
#include "sherlock/sherlock.h"
#include "audio/mididrv.h"
#include "common/error.h"
namespace Sherlock {
extern MidiDriver *MidiDriver_SH_AdLib_create();
extern void MidiDriver_SH_AdLib_newMusicData(MidiDriver *driver, byte *musicData, int32 musicDataSize);
extern MidiDriver *MidiDriver_MT32_create();
extern void MidiDriver_MT32_uploadPatches(MidiDriver *driver, byte *driverData, int32 driverSize);
extern void MidiDriver_MT32_newMusicData(MidiDriver *driver, byte *musicData, int32 musicDataSize);
} // End of namespace Sherlock
#endif // SHERLOCK_SCALPEL_DRIVERS_MIDIDRIVER_H

View File

@@ -0,0 +1,281 @@
/* 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 "sherlock/sherlock.h"
#include "sherlock/scalpel/drivers/mididriver.h"
#include "common/config-manager.h"
#include "common/file.h"
#include "common/system.h"
#include "common/textconsole.h"
//#include "audio/mididrv.h"
namespace Sherlock {
#define SHERLOCK_MT32_CHANNEL_COUNT 16
const byte mt32ReverbDataSysEx[] = {
0x10, 0x00, 0x01, 0x01, 0x05, 0x05, 0xFF
};
class MidiDriver_MT32 : public MidiDriver {
public:
MidiDriver_MT32() {
_driver = nullptr;
_isOpen = false;
_nativeMT32 = false;
_baseFreq = 250;
memset(_MIDIchannelActive, 1, sizeof(_MIDIchannelActive));
}
~MidiDriver_MT32() override;
// MidiDriver
int open() override;
void close() override;
bool isOpen() const override { return _isOpen; }
void send(uint32 b) override;
void newMusicData(byte *musicData, int32 musicDataSize);
MidiChannel *allocateChannel() override {
if (_driver)
return _driver->allocateChannel();
return nullptr;
}
MidiChannel *getPercussionChannel() override {
if (_driver)
return _driver->getPercussionChannel();
return nullptr;
}
void setTimerCallback(void *timer_param, Common::TimerManager::TimerProc timer_proc) override {
if (_driver)
_driver->setTimerCallback(timer_param, timer_proc);
}
uint32 getBaseTempo() override {
if (_driver) {
return _driver->getBaseTempo();
}
return 1000000 / _baseFreq;
}
protected:
Common::Mutex _mutex;
MidiDriver *_driver;
bool _nativeMT32;
bool _isOpen;
int _baseFreq;
private:
// points to a MIDI channel for each of the new voice channels
byte _MIDIchannelActive[SHERLOCK_MT32_CHANNEL_COUNT];
public:
void uploadMT32Patches(byte *driverData, int32 driverSize);
void mt32SysEx(const byte *&dataPtr, int32 &bytesLeft);
};
MidiDriver_MT32::~MidiDriver_MT32() {
Common::StackLock lock(_mutex);
if (_driver) {
_driver->setTimerCallback(nullptr, nullptr);
_driver->close();
delete _driver;
}
_driver = nullptr;
}
int MidiDriver_MT32::open() {
assert(!_driver);
debugC(kDebugLevelMT32Driver, "MT32: starting driver");
// Setup midi driver
MidiDriver::DeviceHandle dev = MidiDriver::detectDevice(MDT_MIDI | MDT_PREFER_MT32);
MusicType musicType = MidiDriver::getMusicType(dev);
switch (musicType) {
case MT_MT32:
_nativeMT32 = true;
break;
case MT_GM:
if (ConfMan.getBool("native_mt32")) {
_nativeMT32 = true;
}
break;
default:
break;
}
_driver = MidiDriver::createMidi(dev);
if (!_driver)
return 255;
if (_nativeMT32)
_driver->property(MidiDriver::PROP_CHANNEL_MASK, 0x03FE);
int ret = _driver->open();
if (ret)
return ret;
if (_nativeMT32)
_driver->sendMT32Reset();
else
_driver->sendGMReset();
return 0;
}
void MidiDriver_MT32::close() {
if (_driver) {
_driver->close();
}
}
// Called when a music track got loaded into memory
void MidiDriver_MT32::newMusicData(byte *musicData, int32 musicDataSize) {
assert(musicDataSize >= 0x7F); // Security check
// MIDI Channel Enable/Disable bytes at offset 0x2 of music data
memcpy(&_MIDIchannelActive, musicData + 0x2, SHERLOCK_MT32_CHANNEL_COUNT);
// Send 16 bytes from offset 0x12 to MT32
// All the music tracks of Sherlock seem to contain dummy data
// probably a feature, that was used in the game "Ski or Die"
// that's why we don't implement this
// Also send these bytes to MT32 (SysEx) - seems to be reverb configuration
if (_nativeMT32) {
const byte *reverbData = mt32ReverbDataSysEx;
int32 reverbDataSize = sizeof(mt32ReverbDataSysEx);
mt32SysEx(reverbData, reverbDataSize);
}
}
void MidiDriver_MT32::uploadMT32Patches(byte *driverData, int32 driverSize) {
if (!_driver)
return;
if (!_nativeMT32)
return;
// patch data starts at offset 0x863
assert(driverSize == 0x13B9); // Security check
assert(driverData[0x863] == 0x7F); // another security check
const byte *patchPtr = driverData + 0x863;
int32 bytesLeft = driverSize - 0x863;
while(1) {
mt32SysEx(patchPtr, bytesLeft);
assert(bytesLeft);
if (*patchPtr == 0x80) // List terminator
break;
}
}
void MidiDriver_MT32::mt32SysEx(const byte *&dataPtr, int32 &bytesLeft) {
byte sysExMessage[270];
uint16 sysExPos = 0;
byte sysExByte = 0;
uint16 sysExChecksum = 0;
memset(&sysExMessage, 0, sizeof(sysExMessage));
sysExMessage[0] = 0x41; // Roland
sysExMessage[1] = 0x10;
sysExMessage[2] = 0x16; // Model MT32
sysExMessage[3] = 0x12; // Command DT1
sysExPos = 4;
sysExChecksum = 0;
while (1) {
assert(bytesLeft);
sysExByte = *dataPtr++;
bytesLeft--;
if (sysExByte == 0xff)
break; // Message done
assert(sysExPos < sizeof(sysExMessage));
sysExMessage[sysExPos++] = sysExByte;
sysExChecksum -= sysExByte;
}
// Calculate checksum
assert(sysExPos < sizeof(sysExMessage));
sysExMessage[sysExPos++] = sysExChecksum & 0x7f;
debugC(kDebugLevelMT32Driver, "MT32: uploading patch data, size %d", sysExPos);
// Send SysEx
_driver->sysEx(sysExMessage, sysExPos);
// Wait the time it takes to send the SysEx data
uint32 delay = (sysExPos + 2) * 1000 / 3125;
// Plus an additional delay for the MT-32 rev00
if (_nativeMT32)
delay += 40;
g_system->delayMillis(delay);
}
// MIDI messages can be found at https://web.archive.org/web/20120128110425/http://www.midi.org/techspecs/midimessages.php
void MidiDriver_MT32::send(uint32 b) {
byte command = b & 0xf0;
byte channel = b & 0xf;
if (command == 0xF0) {
if (_driver) {
_driver->send(b);
}
return;
}
if (_MIDIchannelActive[channel]) {
// Only forward MIDI-data in case the channel is currently enabled via music-data
if (_driver) {
_driver->send(b);
}
}
}
MidiDriver *MidiDriver_MT32_create() {
return new MidiDriver_MT32();
}
void MidiDriver_MT32_newMusicData(MidiDriver *driver, byte *musicData, int32 musicDataSize) {
static_cast<MidiDriver_MT32 *>(driver)->newMusicData(musicData, musicDataSize);
}
void MidiDriver_MT32_uploadPatches(MidiDriver *driver, byte *driverData, int32 driverSize) {
static_cast<MidiDriver_MT32 *>(driver)->uploadMT32Patches(driverData, driverSize);
}
} // End of namespace Sherlock

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,159 @@
/* 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/>.
*
*/
#ifndef SHERLOCK_SCALPEL_H
#define SHERLOCK_SCALPEL_H
#include "sherlock/sherlock.h"
#include "sherlock/scalpel/scalpel_darts.h"
namespace Sherlock {
namespace Scalpel {
extern uint BUTTON_TOP;
extern uint BUTTON_MIDDLE;
extern uint BUTTON_BOTTOM;
extern uint COMMAND_FOREGROUND;
extern uint COMMAND_HIGHLIGHTED;
extern uint COMMAND_NULL;
extern uint INFO_FOREGROUND;
extern uint INFO_BACKGROUND;
extern uint INV_FOREGROUND;
extern uint INV_BACKGROUND;
extern uint PEN_COLOR;
extern uint INFO_BLACK;
extern uint BORDER_COLOR;
extern uint COMMAND_BACKGROUND;
extern uint BUTTON_BACKGROUND;
extern uint TALK_FOREGROUND;
extern uint TALK_NULL;
class ScalpelEngine : public SherlockEngine {
private:
Darts *_darts;
int _mapResult;
/**
* Initialize graphics mode
*/
void setupGraphics();
/**
* Show the 3DO splash screen
*/
bool show3DOSplash();
/**
* Show the starting city cutscene which shows the game title
*/
bool showCityCutscene();
bool showCityCutscene3DO();
/**
* Show the back alley where the initial murder takes place
*/
bool showAlleyCutscene();
bool showAlleyCutscene3DO();
/**
* Show the Baker Street outside cutscene
*/
bool showStreetCutscene();
bool showStreetCutscene3DO();
/**
* Show Holmes and Watson at the breakfast table, lestrade's note, and then the scrolling credits
*/
bool showOfficeCutscene();
bool showOfficeCutscene3DO();
/**
* Show the game credits
*/
bool scrollCredits();
/**
* Load the default inventory for the game, which includes both the initial active inventory,
* as well as special pending inventory items which can appear automatically in the player's
* inventory once given required flags are set
*/
void loadInventory();
/**
* Transition to show an image
*/
void showLBV(const Common::Path &filename);
protected:
/**
* Game initialization
*/
void initialize() override;
/**
* Show the opening sequence
*/
void showOpening() override;
/**
* Starting a scene within the game
*/
void startScene() override;
public:
ScalpelEngine(OSystem *syst, const SherlockGameDescription *gameDesc);
~ScalpelEngine() override;
/**
* Takes care of clearing the mirror in scene 12 (mansion drawing room), in case anything drew over it
*/
void eraseBrumwellMirror();
/**
* Takes care of drawing Holme's reflection onto the mirror in scene 12 (mansion drawing room)
*/
void doBrumwellMirror();
/**
* This clears the mirror in scene 12 (mansion drawing room) in case anything messed draw over it
*/
void flushBrumwellMirror();
/**
* Show the ScummVM restore savegame dialog
*/
void showScummVMSaveDialog();
/**
* Show the ScummVM restore savegame dialog
*/
void showScummVMRestoreDialog();
/**
* Play back a 3do movie
*/
bool play3doMovie(const Common::Path &filename, const Common::Point &pos, bool isPortrait = false);
};
} // End of namespace Scalpel
} // End of namespace Sherlock
#endif

View File

@@ -0,0 +1,562 @@
/* 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 "sherlock/scalpel/scalpel_darts.h"
#include "sherlock/scalpel/scalpel.h"
#include "backends/keymapper/keymapper.h"
namespace Sherlock {
namespace Scalpel {
enum {
STATUS_INFO_X = 218,
STATUS_INFO_Y = 53,
DART_INFO_X = 218,
DART_INFO_Y = 103,
DARTBARHX = 35,
DARTHORIZY = 190,
DARTBARVX = 1,
DARTHEIGHTY = 25,
DARTBARSIZE = 150,
DART_BAR_FORE = 8
};
enum {
DART_COL_FORE = 5,
PLAYER_COLOR = 11
};
#define OPPONENTS_COUNT 4
const char *const OPPONENT_NAMES[OPPONENTS_COUNT] = {
"Skipper", "Willy", "Micky", "Tom"
};
/*----------------------------------------------------------------*/
Darts::Darts(ScalpelEngine *vm) : _vm(vm) {
_dartImages = nullptr;
_level = 0;
_computerPlayer = 1;
_playerDartMode = false;
_dartScore1 = _dartScore2 = 0;
_roundNumber = 0;
_playerDartMode = false;
_roundScore = 0;
_oldDartButtons = false;
}
void Darts::playDarts() {
Events &events = *_vm->_events;
Screen &screen = *_vm->_screen;
int playerNumber = 0;
int lastDart;
// Change the font
int oldFont = screen.fontNumber();
screen.setFont(2);
loadDarts();
initDarts();
Common::Keymapper *keymapper = g_system->getEventManager()->getKeymapper();
keymapper->getKeymap("scalpel")->setEnabled(false);
keymapper->getKeymap("scalpel-quit")->setEnabled(false);
keymapper->getKeymap("scalpel-darts")->setEnabled(true);
bool done = false;
do {
int score, roundStartScore;
roundStartScore = score = playerNumber == 0 ? _dartScore1 : _dartScore2;
// Show player details
showNames(playerNumber);
showStatus(playerNumber);
_roundScore = 0;
if (_vm->shouldQuit())
return;
for (int idx = 0; idx < 3; ++idx) {
// Throw a single dart
if (_computerPlayer == 1)
lastDart = throwDart(idx + 1, playerNumber * 2);
else if (_computerPlayer == 2)
lastDart = throwDart(idx + 1, playerNumber + 1);
else
lastDart = throwDart(idx + 1, 0);
score -= lastDart;
_roundScore += lastDart;
screen._backBuffer1.SHblitFrom(screen._backBuffer2, Common::Point(DART_INFO_X, DART_INFO_Y - 1),
Common::Rect(DART_INFO_X, DART_INFO_Y - 1, SHERLOCK_SCREEN_WIDTH, SHERLOCK_SCREEN_HEIGHT));
screen.print(Common::Point(DART_INFO_X, DART_INFO_Y), DART_COL_FORE, "Dart # %d", idx + 1);
screen.print(Common::Point(DART_INFO_X, DART_INFO_Y + 10), DART_COL_FORE, "Scored %d points", lastDart);
if (score != 0 && playerNumber == 0)
screen.print(Common::Point(DART_INFO_X, DART_INFO_Y + 30), DART_COL_FORE, "Press a key");
if (score == 0) {
// Some-one has won
screen.print(Common::Point(DART_INFO_X, DART_INFO_Y + 20), PLAYER_COLOR, "GAME OVER!");
if (playerNumber == 0) {
screen.print(Common::Point(DART_INFO_X, DART_INFO_Y + 30), PLAYER_COLOR, "Holmes Wins!");
if (_level < OPPONENTS_COUNT)
_vm->setFlagsDirect(318 + _level);
} else {
screen.print(Common::Point(DART_INFO_X, DART_INFO_Y + 30), PLAYER_COLOR, "%s Wins!", _opponent.c_str());
}
screen.print(Common::Point(DART_INFO_X, DART_INFO_Y + 40), DART_COL_FORE, "Press a key");
idx = 10;
done = true;
} else if (score < 0) {
screen.print(Common::Point(DART_INFO_X, DART_INFO_Y + 20), PLAYER_COLOR, "BUSTED!");
idx = 10;
score = roundStartScore;
}
if (playerNumber == 0)
_dartScore1 = score;
else
_dartScore2 = score;
showStatus(playerNumber);
events.clearKeyboard();
if ((playerNumber == 0 && _computerPlayer == 1) || _computerPlayer == 0 || done) {
int dartKey;
while (!(dartKey = dartHit()) && !_vm->shouldQuit())
events.delay(10);
if (dartKey == kActionScalpelDartsExit) {
idx = 10;
done = true;
}
} else {
events.wait(20);
}
screen._backBuffer1.SHblitFrom(screen._backBuffer2, Common::Point(DART_INFO_X, DART_INFO_Y - 1),
Common::Rect(DART_INFO_X, DART_INFO_Y - 1, SHERLOCK_SCREEN_WIDTH, SHERLOCK_SCREEN_HEIGHT));
screen.slamArea(0, 0, SHERLOCK_SCREEN_WIDTH, SHERLOCK_SCREEN_HEIGHT);
}
playerNumber ^= 1;
if (!playerNumber)
++_roundNumber;
done |= _vm->shouldQuit();
if (!done) {
screen._backBuffer2.SHblitFrom((*_dartImages)[0], Common::Point(0, 0));
screen._backBuffer1.SHblitFrom(screen._backBuffer2);
screen.slamArea(0, 0, SHERLOCK_SCREEN_WIDTH, SHERLOCK_SCREEN_HEIGHT);
}
} while (!done);
closeDarts();
screen.fadeToBlack();
keymapper->getKeymap("scalpel-darts")->setEnabled(false);
keymapper->getKeymap("scalpel")->setEnabled(true);
keymapper->getKeymap("scalpel-quit")->setEnabled(true);
// Restore font
screen.setFont(oldFont);
}
void Darts::loadDarts() {
Screen &screen = *_vm->_screen;
_dartImages = new ImageFile("darts.vgs");
screen.setPalette(_dartImages->_palette);
screen._backBuffer1.SHblitFrom((*_dartImages)[0], Common::Point(0, 0));
screen.slamArea(0, 0, SHERLOCK_SCREEN_WIDTH, SHERLOCK_SCREEN_HEIGHT);
}
void Darts::initDarts() {
_dartScore1 = _dartScore2 = 301;
_roundNumber = 1;
if (_level == 9) {
// No computer players
_computerPlayer = 0;
_level = 0;
} else if (_level == 8) {
_level = _vm->getRandomNumber(3);
_computerPlayer = 2;
} else {
// Check flags for opponents
for (int idx = 0; idx < OPPONENTS_COUNT; ++idx) {
if (_vm->readFlags(314 + idx))
_level = idx;
}
}
_opponent = OPPONENT_NAMES[_level];
}
void Darts::closeDarts() {
delete _dartImages;
_dartImages = nullptr;
}
void Darts::showNames(int playerNum) {
Screen &screen = *_vm->_screen;
byte color = playerNum == 0 ? PLAYER_COLOR : DART_COL_FORE;
// Print Holmes first
if (playerNum == 0)
screen.print(Common::Point(STATUS_INFO_X, STATUS_INFO_Y), PLAYER_COLOR + 3, "Holmes");
else
screen.print(Common::Point(STATUS_INFO_X, STATUS_INFO_Y), color, "Holmes");
screen._backBuffer1.fillRect(Common::Rect(STATUS_INFO_X, STATUS_INFO_Y + 10,
STATUS_INFO_X + 31, STATUS_INFO_Y + 12), color);
screen.slamArea(STATUS_INFO_X, STATUS_INFO_Y + 10, 31, 12);
// Second player
color = playerNum == 1 ? PLAYER_COLOR : DART_COL_FORE;
if (playerNum != 0)
screen.print(Common::Point(STATUS_INFO_X + 50, STATUS_INFO_Y), PLAYER_COLOR + 3,
"%s", _opponent.c_str());
else
screen.print(Common::Point(STATUS_INFO_X + 50, STATUS_INFO_Y), color,
"%s", _opponent.c_str());
screen._backBuffer1.fillRect(Common::Rect(STATUS_INFO_X + 50, STATUS_INFO_Y + 10,
STATUS_INFO_X + 81, STATUS_INFO_Y + 12), color);
screen.slamArea(STATUS_INFO_X + 50, STATUS_INFO_Y + 10, 81, 12);
// Make a copy of the back buffer to the secondary one
screen._backBuffer2.SHblitFrom(screen._backBuffer1);
}
void Darts::showStatus(int playerNum) {
Screen &screen = *_vm->_screen;
byte color;
// Copy scoring screen from secondary back buffer. This will erase any previously displayed status/score info
screen._backBuffer1.SHblitFrom(screen._backBuffer2, Common::Point(STATUS_INFO_X, STATUS_INFO_Y + 10),
Common::Rect(STATUS_INFO_X, STATUS_INFO_Y + 10, SHERLOCK_SCREEN_WIDTH, STATUS_INFO_Y + 48));
color = (playerNum == 0) ? PLAYER_COLOR : DART_COL_FORE;
screen.print(Common::Point(STATUS_INFO_X + 6, STATUS_INFO_Y + 13), color, "%d", _dartScore1);
color = (playerNum == 1) ? PLAYER_COLOR : DART_COL_FORE;
screen.print(Common::Point(STATUS_INFO_X + 56, STATUS_INFO_Y + 13), color, "%d", _dartScore2);
screen.print(Common::Point(STATUS_INFO_X, STATUS_INFO_Y + 25), PLAYER_COLOR, "Round: %d", _roundNumber);
screen.print(Common::Point(STATUS_INFO_X, STATUS_INFO_Y + 35), PLAYER_COLOR, "Turn Total: %d", _roundScore);
screen.slamRect(Common::Rect(STATUS_INFO_X, STATUS_INFO_Y + 10, SHERLOCK_SCREEN_WIDTH, STATUS_INFO_Y + 48));
}
int Darts::throwDart(int dartNum, int computer) {
Events &events = *_vm->_events;
Screen &screen = *_vm->_screen;
Common::Point targetNum;
int width, height;
events.clearKeyboard();
erasePowerBars();
screen.print(Common::Point(DART_INFO_X, DART_INFO_Y), DART_COL_FORE, "Dart # %d", dartNum);
if (!computer) {
screen.print(Common::Point(DART_INFO_X, DART_INFO_Y + 10), DART_COL_FORE, "Hit a key");
screen.print(Common::Point(DART_INFO_X, DART_INFO_Y + 18), DART_COL_FORE, "to start");
}
if (!computer) {
while (!_vm->shouldQuit() && !dartHit())
;
} else {
events.delay(10);
}
if (_vm->shouldQuit())
return 0;
screen._backBuffer1.SHblitFrom(screen._backBuffer2, Common::Point(DART_INFO_X, DART_INFO_Y - 1),
Common::Rect(DART_INFO_X, DART_INFO_Y - 1, SHERLOCK_SCREEN_WIDTH, SHERLOCK_SCREEN_HEIGHT));
screen.slamRect(Common::Rect(DART_INFO_X, DART_INFO_Y - 1, SHERLOCK_SCREEN_WIDTH, SHERLOCK_SCREEN_HEIGHT));
// If it's a computer player, choose a dart destination
if (computer)
targetNum = getComputerDartDest(computer - 1);
width = doPowerBar(Common::Point(DARTBARHX, DARTHORIZY), DART_BAR_FORE, targetNum.x, false);
height = 101 - doPowerBar(Common::Point(DARTBARVX, DARTHEIGHTY), DART_BAR_FORE, targetNum.y, true);
// For human players, slight y adjustment
if (computer == 0)
height += 2;
// Copy the bars to the secondary back buffer so that they remain fixed at their selected values
// whilst the dart is being animated at being thrown at the board
screen._backBuffer2.SHblitFrom(screen._backBuffer1, Common::Point(DARTBARHX - 1, DARTHORIZY - 1),
Common::Rect(DARTBARHX - 1, DARTHORIZY - 1, DARTBARHX + DARTBARSIZE + 3, DARTHORIZY + 10));
screen._backBuffer2.SHblitFrom(screen._backBuffer1, Common::Point(DARTBARVX - 1, DARTHEIGHTY - 1),
Common::Rect(DARTBARVX - 1, DARTHEIGHTY - 1, DARTBARVX + 11, DARTHEIGHTY + DARTBARSIZE + 3));
// Convert height and width to relative range of -50 to 50, where 0,0 is the exact centre of the board
height -= 50;
width -= 50;
Common::Point dartPos(111 + width * 2, 99 + height * 2);
drawDartThrow(dartPos);
return dartScore(dartPos);
}
void Darts::drawDartThrow(const Common::Point &pt) {
Events &events = *_vm->_events;
Screen &screen = *_vm->_screen;
Common::Point pos(pt.x, pt.y + 2);
Common::Rect oldDrawBounds;
int delta = 9;
for (int idx = 4; idx < 23; ++idx) {
ImageFrame &frame = (*_dartImages)[idx];
// Adjust draw position for animating dart
if (idx < 13)
pos.y -= delta--;
else if (idx == 13)
delta = 1;
else
pos.y += delta++;
// Draw the dart
Common::Point drawPos(pos.x - frame._width / 2, pos.y - frame._height);
screen._backBuffer1.SHtransBlitFrom(frame, drawPos);
screen.slamArea(drawPos.x, drawPos.y, frame._width, frame._height);
// Handle erasing old dart frame area
if (!oldDrawBounds.isEmpty())
screen.slamRect(oldDrawBounds);
oldDrawBounds = Common::Rect(drawPos.x, drawPos.y, drawPos.x + frame._width, drawPos.y + frame._height);
screen._backBuffer1.SHblitFrom(screen._backBuffer2, drawPos, oldDrawBounds);
events.wait(2);
}
// Draw dart in final "stuck to board" form
screen._backBuffer1.SHtransBlitFrom((*_dartImages)[22], Common::Point(oldDrawBounds.left, oldDrawBounds.top));
screen._backBuffer2.SHtransBlitFrom((*_dartImages)[22], Common::Point(oldDrawBounds.left, oldDrawBounds.top));
screen.slamRect(oldDrawBounds);
}
void Darts::erasePowerBars() {
Screen &screen = *_vm->_screen;
screen._backBuffer1.fillRect(Common::Rect(DARTBARHX, DARTHORIZY, DARTBARHX + DARTBARSIZE, DARTHORIZY + 10), BLACK);
screen._backBuffer1.fillRect(Common::Rect(DARTBARVX, DARTHEIGHTY, DARTBARVX + 10, DARTHEIGHTY + DARTBARSIZE), BLACK);
screen._backBuffer1.SHtransBlitFrom((*_dartImages)[2], Common::Point(DARTBARHX - 1, DARTHORIZY - 1));
screen._backBuffer1.SHtransBlitFrom((*_dartImages)[3], Common::Point(DARTBARVX - 1, DARTHEIGHTY - 1));
screen.slamArea(DARTBARHX - 1, DARTHORIZY - 1, DARTBARSIZE + 3, 11);
screen.slamArea(DARTBARVX - 1, DARTHEIGHTY - 1, 11, DARTBARSIZE + 3);
}
int Darts::doPowerBar(const Common::Point &pt, byte color, int goToPower, bool isVertical) {
Events &events = *_vm->_events;
Screen &screen = *_vm->_screen;
bool done;
int idx = 0;
events.clearEvents();
events.delay(100);
// Display loop
do {
done = _vm->shouldQuit() || idx >= DARTBARSIZE;
if (idx == (goToPower - 1))
// Reached target power for a computer player
done = true;
else if (goToPower == 0) {
// Check for press
if (dartHit())
done = true;
}
if (isVertical) {
screen._backBuffer1.hLine(pt.x, pt.y + DARTBARSIZE - 1 - idx, pt.x + 8, color);
screen._backBuffer1.SHtransBlitFrom((*_dartImages)[3], Common::Point(pt.x - 1, pt.y - 1));
screen.slamArea(pt.x, pt.y + DARTBARSIZE - 1 - idx, 8, 2);
} else {
screen._backBuffer1.vLine(pt.x + idx, pt.y, pt.y + 8, color);
screen._backBuffer1.SHtransBlitFrom((*_dartImages)[2], Common::Point(pt.x - 1, pt.y - 1));
screen.slamArea(pt.x + idx, pt.y, 1, 8);
}
if (!(idx % 8))
events.wait(1);
++idx;
} while (!done);
return MIN(idx * 100 / DARTBARSIZE, 100);
}
int Darts::dartHit() {
Events &events = *_vm->_events;
// Process pending events
events.pollEvents();
if (events.actionHit()) {
// Action was pressed, so return it
Common::CustomEventType action = events.getAction();
return action;
}
if (events.kbHit()) {
// Key was pressed, so return it
Common::KeyState keyState = events.getKey();
return keyState.keycode;
}
_oldDartButtons = events._pressed;
events.setButtonState();
// Only return true if the mouse button is newly pressed
return (events._pressed && !_oldDartButtons) ? 1 : 0;
}
int Darts::dartScore(const Common::Point &pt) {
Common::Point pos(pt.x - 37, pt.y - 33);
Graphics::Surface &scoreImg = (*_dartImages)[1]._frame;
if (pos.x < 0 || pos.y < 0 || pos.x >= scoreImg.w || pos.y >= scoreImg.h)
// Not on the board
return 0;
// On board, so get the score from the pixel at that position
int score = *(const byte *)scoreImg.getBasePtr(pos.x, pos.y);
return score;
}
Common::Point Darts::getComputerDartDest(int playerNum) {
Common::Point target;
int score = playerNum == 0 ? _dartScore1 : _dartScore2;
if (score > 50) {
// Aim for the bullseye
target.x = target.y = 76;
if (_level <= 1 && _vm->getRandomNumber(1) == 1) {
// Introduce margin of error
target.x += _vm->getRandomNumber(21) - 10;
target.y += _vm->getRandomNumber(21) - 10;
}
} else {
int aim = score;
bool done;
Common::Point pt;
do {
done = findNumberOnBoard(aim, pt);
--aim;
} while (!done);
target.x = 75 + ((pt.x - 75) * 20 / 27);
target.y = 75 + ((pt.y - 75) * 2 / 3);
}
// Pick a level of accuracy. The higher the level, the more accurate their throw will be
int accuracy = _vm->getRandomNumber(10) + _level * 2;
if (accuracy <= 2) {
target.x += _vm->getRandomNumber(71) - 35;
target.y += _vm->getRandomNumber(71) - 35;
} else if (accuracy <= 4) {
target.x += _vm->getRandomNumber(51) - 25;
target.y += _vm->getRandomNumber(51) - 25;
} else if (accuracy <= 6) {
target.x += _vm->getRandomNumber(31) - 15;
target.y += _vm->getRandomNumber(31) - 15;
} else if (accuracy <= 8) {
target.x += _vm->getRandomNumber(21) - 10;
target.y += _vm->getRandomNumber(21) - 10;
} else if (accuracy <= 10) {
target.x += _vm->getRandomNumber(11) - 5;
target.y += _vm->getRandomNumber(11) - 5;
}
if (target.x < 1)
target.x = 1;
if (target.y < 1)
target.y = 1;
return target;
}
bool Darts::findNumberOnBoard(int aim, Common::Point &pt) {
ImageFrame &board = (*_dartImages)[1];
// Scan board image for the special "center" pixels
bool done = false;
for (int yp = 0; yp < 132 && !done; ++yp) {
const byte *srcP = (const byte *)board._frame.getBasePtr(0, yp);
for (int xp = 0; xp < 147 && !done; ++xp, ++srcP) {
int score = *srcP;
// Check for match
if (score == aim) {
done = true;
// Aim at non-double/triple numbers where possible
if (aim < 21) {
pt.x = xp + 5;
pt.y = yp + 5;
score = *(const byte *)board._frame.getBasePtr(xp + 10, yp + 10);
if (score != aim)
// Not aiming at non-double/triple number yet
done = false;
} else {
// Aiming at a double or triple
pt.x = xp + 3;
pt.y = yp + 3;
}
}
}
}
if (aim == 3)
pt.x += 15;
pt.y = 132 - pt.y;
return done;
}
} // End of namespace Scalpel
} // End of namespace Sherlock

View File

@@ -0,0 +1,129 @@
/* 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/>.
*
*/
#ifndef SHERLOCK_SCALPEL_DARTS_H
#define SHERLOCK_SCALPEL_DARTS_H
#include "sherlock/image_file.h"
namespace Sherlock {
namespace Scalpel {
class ScalpelEngine;
class Darts {
private:
ScalpelEngine *_vm;
ImageFile *_dartImages;
int _dartScore1, _dartScore2;
int _roundNumber;
int _level;
int _computerPlayer;
Common::String _opponent;
bool _playerDartMode;
int _roundScore;
bool _oldDartButtons;
/**
* Load the graphics needed for the dart game
*/
void loadDarts();
/**
* Initializes the variables needed for the dart game
*/
void initDarts();
/**
* Frees the images used by the dart game
*/
void closeDarts();
/**
* Show the names of the people playing, Holmes and his opponent
*/
void showNames(int playerNum);
/**
* Show the player score and game status
*/
void showStatus(int playerNum);
/**
* Throws a single dart.
* @param dartNum Dart number
* @param computer 0 = Player, 1 = 1st player computer, 2 = 2nd player computer
* @returns Score for what dart hit
*/
int throwDart(int dartNum, int computer);
/**
* Draw a dart moving towards the board
*/
void drawDartThrow(const Common::Point &pt);
/**
* Erases the power bars
*/
void erasePowerBars();
/**
* Show a gradually incrementing incrementing power that bar. If goToPower is provided, it will
* increment to that power level ignoring all keyboard input (ie. for computer throws).
* Otherwise, it will increment until either a key/mouse button is pressed, or it reaches the end
*/
int doPowerBar(const Common::Point &pt, byte color, int goToPower, bool isVertical);
/**
* Returns true if a mouse button or key is pressed.
*/
int dartHit();
/**
* Return the score of the given location on the dart-board
*/
int dartScore(const Common::Point &pt);
/**
* Calculates where a computer player is trying to throw their dart, and choose the actual
* point that was hit with some margin of error
*/
Common::Point getComputerDartDest(int playerNum);
/**
* Returns the center position for the area of the dartboard with a given number
*/
bool findNumberOnBoard(int aim, Common::Point &pt);
public:
Darts(ScalpelEngine *vm);
/**
* Main method for playing darts game
*/
void playDarts();
};
} // End of namespace Scalpel
} // End of namespace Sherlock
#endif

View File

@@ -0,0 +1,90 @@
/* 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 "sherlock/scalpel/scalpel_debugger.h"
#include "sherlock/sherlock.h"
#include "audio/audiostream.h"
#include "audio/mixer.h"
#include "audio/decoders/aiff.h"
namespace Sherlock {
namespace Scalpel {
ScalpelDebugger::ScalpelDebugger(SherlockEngine *vm) : Debugger(vm) {
registerCmd("3do_playmovie", WRAP_METHOD(ScalpelDebugger, cmd3DO_PlayMovie));
registerCmd("3do_playaudio", WRAP_METHOD(ScalpelDebugger, cmd3DO_PlayAudio));
}
bool ScalpelDebugger::cmd3DO_PlayMovie(int argc, const char **argv) {
if (argc != 2) {
debugPrintf("Format: 3do_playmovie <3do-movie-file>\n");
return true;
}
// play gets postponed until debugger is closed
Common::String filename = argv[1];
_3doPlayMovieFile = filename;
return cmdExit(0, nullptr);
}
bool ScalpelDebugger::cmd3DO_PlayAudio(int argc, const char **argv) {
if (argc != 2) {
debugPrintf("Format: 3do_playaudio <3do-audio-file>\n");
return true;
}
Common::File *file = new Common::File();
if (!file->open(argv[1])) {
debugPrintf("can not open specified audio file\n");
delete file;
return true;
}
Audio::AudioStream *testStream;
Audio::SoundHandle testHandle;
// Try to load the given file as AIFF/AIFC
testStream = Audio::makeAIFFStream(file, DisposeAfterUse::YES);
if (testStream) {
g_system->getMixer()->playStream(Audio::Mixer::kPlainSoundType, &testHandle, testStream);
_vm->_events->clearEvents();
while ((!_vm->shouldQuit()) && g_system->getMixer()->isSoundHandleActive(testHandle)) {
_vm->_events->pollEvents();
g_system->delayMillis(10);
if (_vm->_events->kbHit()) {
break;
}
}
debugPrintf("playing completed\n");
g_system->getMixer()->stopHandle(testHandle);
}
return true;
}
} // End of namespace Scalpel
} // End of namespace Sherlock

View File

@@ -0,0 +1,53 @@
/* 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/>.
*
*/
#ifndef SHERLOCK_SCALPEL_DEBUGGER_H
#define SHERLOCK_SCALPEL_DEBUGGER_H
#include "sherlock/debugger.h"
namespace Sherlock {
class SherlockEngine;
namespace Scalpel {
class ScalpelDebugger : public Debugger {
private:
/**
* Plays a 3DO movie
*/
bool cmd3DO_PlayMovie(int argc, const char **argv);
/**
* Plays a 3DO audio
*/
bool cmd3DO_PlayAudio(int argc, const char **argv);
public:
ScalpelDebugger(SherlockEngine *vm);
~ScalpelDebugger() override {}
};
} // End of namespace Scalpel
} // End of namespace Sherlock
#endif /* SHERLOCK_DEBUGGER_H */

View File

@@ -0,0 +1,889 @@
/* 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 "sherlock/scalpel/scalpel_fixed_text.h"
#include "sherlock/sherlock.h"
namespace Sherlock {
namespace Scalpel {
static const char *const fixedTextEN[] = {
// Game hotkeys
"LMTPOCIUGJFS",
// SH1: Window buttons
"EExit",
"UUp",
"DDown",
// SH1: Inventory buttons
"EExit",
"LLook",
"UUse",
"GGive",
// SH1: Journal text
"Watson's Journal",
"Page %d",
// SH1: Journal buttons
"EExit",
"BBack 10",
"UUp",
"DDown",
"AAhead 10",
"SSearch",
"FFirst Page",
"LLast Page",
"PPrint Text",
// SH1: Journal search
"EExit",
"BBackward",
"FForward",
"Text Not Found !",
// SH1: Settings
"EExit",
"MMusic on",
"MMusic off",
"PPortraits on",
"PPortraits off",
"JJoystick off",
"NNew Font Style",
"SSound Effects on",
"SSound Effects off",
"WWindows Slide",
"WWindows Appear",
"CCalibrate Joystick",
"AAuto Help left",
"AAuto Help right",
"VVoices on",
"VVoices off",
"FFade by Pixel",
"FFade Directly",
"KKey Pad Slow",
"KKey Pad Fast",
// Load/Save
"EExit",
"LLoad",
"SSave",
"UUp",
"DDown",
"QQuit",
// Quit Game
"Are you sure you wish to Quit ?",
"YYes",
"NNo",
// SH1: Press key text
"PPress any Key for More.",
"PPress any Key to Continue.",
// SH1: Initial Inventory
"A message requesting help",
"A number of business cards",
"Opera Tickets",
"Cuff Link",
"Wire Hook",
"Note",
"An open pocket watch",
"A piece of paper with numbers on it",
"A letter folded many times",
"Tarot Cards",
"An ornate key",
"A pawn ticket",
// SH1: User Interface
"No, thank you.",
"You can't do that.",
"Done...",
"Use ",
" on %s",
"Give ",
" to %s",
// SH1: People names
"Sherlock Holmes",
"Dr. Watson",
"Inspector Lestrade",
"Constable O'Brien",
"Constable Lewis",
"Sheila Parker",
"Henry Carruthers",
"Lesley",
"An Usher",
"Fredrick Epstein",
"Mrs. Worthington",
"The Coach",
"A Player",
"Tim",
"James Sanders",
"Belle",
"Cleaning Girl",
"Wiggins",
"Paul",
"The Bartender",
"A Dirty Drunk",
"A Shouting Drunk",
"A Staggering Drunk",
"The Bouncer",
"The Coroner",
"Reginald Snipes",
"George Blackwood",
"Lars",
"The Chemist",
"Inspector Gregson",
"Jacob Farthington",
"Mycroft",
"Old Sherman",
"Richard",
"The Barman",
"A Dandy Player",
"A Rough-looking Player",
"A Spectator",
"Robert Hunt",
"Violet",
"Pettigrew",
"Augie",
"Anna Carroway",
"A Guard",
"Antonio Caruso",
"Toby the Dog",
"Simon Kingsley",
"Alfred",
"Lady Brumwell",
"Madame Rosa",
"Joseph Moorehead",
"Mrs. Beale",
"Felix",
"Hollingston",
"Constable Callaghan",
"Sergeant Duncan",
"Lord Brumwell",
"Nigel Jaimeson",
"Jonas",
"Constable Dugan"
};
// sharp-s : 0xE1 / octal 341
// small a-umlaut: 0x84 / octal 204
// small o-umlaut: 0x94 / octal 224
// small u-umlaut: 0x81 / octal 201
static const char *const fixedTextDE[] = {
// Game hotkeys
"SBRNOCTEGADU", // original: did not support hotkeys for actions
// SH1: Window buttons
"ZZur\201ck",
"HHoch",
"RRunter",
// SH1: Inventory buttons
"ZZur\201ck",
"SSchau",
"BBenutze",
"GGib",
// SH1: Journal text
"Watsons Tagebuch",
"Seite %d",
// SH1: Journal buttons
"ZZur\201ck", // original: "Zur\201ck"
"o10 hoch",
"HHoch",
"RRunter",
"u10 runter", // original: "10 runter"
"SSuche",
"EErste Seite",
"LLetzte Seite",
"DDrucke Text",
// SH1: Journal search
"ZZur\201ck",
"RR\201ckw\204rts", // original: "Backward"
"VVorw\204rts", // original: "Forward"
"Text nicht gefunden!",
// SH1: Settings
"ZZur\201ck", // original interpreter: "Exit"
"MMusik an",
"MMusik aus",
"PPortr\204ts an", // original interpreter: "Portraits"
"PPortr\204ts aus",
"JJoystick aus",
"NNeue Schrift",
"GGer\204uscheffekte on", // original interpreter: "Effekte"
"GGer\204uscheffekte off",
"FFenster gleitend",
"FFenster direkt",
"JJustiere Joystick",
"HHilfe links",
"HHilfe rechts",
"SSprache an",
"SSprache aus",
"cSchnitt",
"BBlende",
"CCursor langsam",
"CCursor schnell",
// Load/Save
"ZZur\201ck",
"LLaden",
"SSichern",
"HHoch",
"RRunter",
"EEnde",
// Quit Game
"Das Spiel verlassen ?",
"JJa",
"NNein",
// SH1: Press key text
"MMehr auf Tastendruck...",
"BBeliebige Taste dr\201cken.",
// SH1: Initial Inventory
"Ein Hilferuf von Lestrade",
"Holmes' Visitenkarten",
"Karten f\201rs Opernhaus",
"Manschettenkn\224pfe",
"Zum Haken verbogener Drahtkorb",
"Mitteilung am Epstein",
"Eine offene Taschenuhr",
"Ein Zettel mit Zahlen drauf",
"Ein mehrfach gefalteter Briefbogen",
"Ein Tarot-Kartenspiel", // original interpreter: "Ein Tarock-Kartenspiel" [sic]
"Ein verzierter Schl\201ssel",
"Ein Pfandschein",
// SH1: User Interface
"Nein, vielen Dank.",
"Nein, das geht wirklich nicht.", // original: "Nein, das geht wirklich nicht"
"Fertig...",
"Benutze ",
" mit %s",
"Gib ", // original: "Gebe "
" an %s", // original: " zu %s"
// SH1: People names
"Sherlock Holmes",
"Dr. Watson",
"Inspektor Lestrade",
"Konstabler O'Brien",
"Konstabler Lewis",
"Sheila Parker",
"Henry Carruthers",
"Lesley",
"Platzanweiser",
"Fredrick Epstein",
"Mrs. Worthington",
"Der Trainer",
"Ein Spieler",
"Tim",
"James Sanders",
"Belle",
"Putzm\204dchen",
"Wiggins",
"Paul",
"Gastwirt",
"Schmutziger Betrunkener",
"Lallender Betrunkener",
"Torkelnder Betrunkener",
"The Bouncer",
"Der Leichenbeschauer",
"Reginald Snipes",
"George Blackwood",
"Lars",
"Apotheker",
"Inspektor Gregson",
"Jacob Farthington",
"Mycroft",
"Old Sherman",
"Richard",
"Barkeeper",
"Jock Mahoney",
"Nobby Charleton",
"Zuschauer",
"Robert Hunt",
"Violet",
"Pettigrew",
"Augie",
"Anna Carroway",
"Wache",
"Antonio Caruso",
"Toby the Dog",
"Simon Kingsley",
"Alfred",
"Lady Brumwell",
"Madame Rosa",
"Joseph Moorehead",
"Mrs. Beale",
"Felix",
"Hollingston",
"Konstabler Callaghan",
"Sergeant Duncan",
"Lord Brumwell",
"Nigel Jaimeson",
"Jonas",
"Konstabler Dugan"
};
// up-side down exclamation mark - 0xAD / octal 255
// up-side down question mark - 0xA8 / octal 250
// n with a wave on top - 0xA4 / octal 244
// more characters see engines/sherlock/fixed_text.cpp
static const char *const fixedTextES[] = {
// Game hotkeys
"VMHTACIUDNFO",
// SH1: Window buttons
"aSalir", // original interpreter: "Exit"
"SSubir",
"BBajar",
// SH1: Inventory buttons
"SSalir", // original interpreter: "Exit"
"MMirar",
"UUsar",
"DDar",
// SH1: Journal text
"Diario de Watson",
"Pagina %d",
// SH1: Journal buttons
"aSalir", // original interpreter: "Exit"
"RRetroceder",
"SSubir",
"JbaJar",
"AAdelante",
"BBuscar",
"11a pagina",
"UUlt pagina",
"IImprimir",
// SH1: Journal search
"SSalir", // original interpreter: "Exit"
"RRetroceder",
"AAvanzar",
"Texto no encontrado!",
// SH1: Settings
"aSalir", // original interpreter: "Exit"
"MMusica si",
"MMusica no",
"RRetratos si",
"RRetratos no",
"JJoystick no",
"NNuevo fuente",
"Sefectos Sonido si",
"Sefectos Sonido no",
"Tven Tanas desliz.",
"Tven Tanas aparecen",
"CCalibrar Joystick",
"yAyuda lzq", // TODO: check this
"yAyuda Dcha",
"VVoces si",
"VVoces no",
"FFundido a pixel",
"FFundido directo",
"eTeclado lento",
"eTeclado rapido",
// Load/Save
"aSalir", // original interpreter: "Exit"
"CCargar",
"GGrabar",
"SSubir",
"BBajar",
"AAcabar",
// Quit Game
"\250Seguro que quieres Acabar?",
"SSi",
"NNo",
// SH1: Press key text
"TTecla para ver mas",
"TTecla para continuar",
// SH1: Initial Inventory
"Un mensaje solicitando ayuda",
"Unas cuantas tarjetas de visita",
"Entradas para la opera",
"Unos gemelos",
"Un gancho de alambre",
"Una nota",
"Un reloj de bolsillo abierto",
"Un trozo de papel con unos numeros",
"Un carta muy plegada",
"Unas cartas de Tarot",
"Una llave muy vistosa",
"Una papeleta de empe\244o",
// SH1: User Interface
"No, gracias.",
"No puedes hacerlo.", // original: "No puedes hacerlo"
"Hecho...",
"Usar ",
" sobre %s",
"Dar ",
" a %s",
// SH1: People names
"Sherlock Holmes",
"Dr. Watson",
"El inspector Lestrade",
"El agente O'Brien",
"El agente Lewis",
"Sheila Parker",
"Henry Carruthers",
"Lesley",
"Un ujier",
"Fredrick Epstein",
"Mrs. Worthington",
"El entrenador",
"El jugador",
"Tim",
"James Sanders",
"Belle",
"La chica de la limpieza",
"Wiggins",
"Paul",
"El barman",
"Un sucio borracho",
"Un borracho griton",
"Un tambaleante borracho",
"El gorila",
"El forense",
"Reginald Snipes",
"George Blackwood",
"Lars",
"El quimico",
"El inspector Gregson",
"Jacob Farthington",
"Mycroft",
"Old Sherman",
"Richard",
"El barman",
"Un jugador dandy",
"Un duro jugador",
"Un espectador",
"Robert Hunt",
"Violeta",
"Pettigrew",
"Augie",
"Anna Carroway",
"Un guarda",
"Antonio Caruso",
"El perro Toby",
"Simon Kingsley",
"Alfred",
"Lady Brumwell",
"Madame Rosa",
"Joseph Moorehead",
"Mrs. Beale",
"Felix",
"Hollingston",
"El agente Callaghan",
"El sargento Duncan",
"Lord Brumwell",
"Nigel Jaimeson",
"Jonas",
"El agente Dugan"
};
static const char *const fixedTextZH[] = {
// Game hotkeys
"LMTPOCIUGJFS",
// SH1: Window buttons
"E\xc2\xf7\xb6\x7d(E)", /* "E離開"; "EExit" */
"U\xa4\x57(U)", /* "U上"; "UUp" */
"D\xa4\x55(D)", /* "D下"; "DDown" */
// SH1: Inventory buttons
"E\xc2\xf7\xb6\x7d(E)", /* "E離開"; "EExit" */
"L\xac\x64\xac\xdd(L)", /* "L查看"; "LLook" */
"U\xa8\xcf\xa5\xce(U)", /* "U使用"; "UUse" */
"G\xb5\xb9\xbb\x50(G)", /* "G給與"; "GGive" */
// TODO: Inventorty next/prev buttons:
//"\xa5\xaa\xad\xb6", /* "左頁"; */
//"\xa5\xaa\xa4\x40", /* "左一"; */
//"\xa5\x6b\xa4\x40", /* "右一"; */
//"\xa5\x6b\xad\xb6", /* "右頁"; */
// SH1: Journal text
"\xb5\xd8\xa5\xcd\xaa\xba\xb5\xa7\xb0\x4f", /* "華生的筆記"; "Watson's Journal" */
"\xb2\xc4\x25\x64\xad\xb6", /* "第%d頁"; "Page %d" */
// SH1: Journal buttons
"E\xc2\xf7\xb6\x7d(E)", /* "E離開"; "EExit" */
"B\xab\x65\xa4\x51\xad\xb6(B)", /* "B前十頁"; "BBack 10" */
"U\xa4\x57(U)", /* "U上"; "UUp" */
"D\xa4\x55(D)", /* "D下"; "DDown" */
"A\xab\xe1\xa4\x51\xad\xb6(A)", /* "A後十頁"; "AAhead 10" */
"S\xb4\x4d\xa7\xe4(S)", /* "S尋找"; "SSearch" */
"F\xad\xba\xad\xb6(F)", /* "F首頁"; "FFirst Page" */
"L\xa9\xb3\xad\xb6(L)", /* "L底頁"; "LLast Page" */
"P\xa6\x43\xa6\x4c(P)", /* "列印"; "PPrint Text" */
// SH1: Journal search
"\xc2\xf7\xb6\x7d", /* "E離開"; "Exit" */
"\xab\x65\xb4\x4d", /* "前尋"; "Backward" */
"\xab\xe1\xb4\x4d", /* "後尋"; "Forward" */
"\xa8\x53\xa6\xb3\xa7\xe4\xa8\xec\x21", /* "沒有找到!"; "Text Not Found !" */
// SH1: Settings
"E\xc2\xf7\xb6\x7d(E)", /* "離開"; "EExit" */
"M\xad\xb5\xbc\xd6\xb6\x7d(M)", /* "M音樂開"; "MMusic on" */
"M\xad\xb5\xbc\xd6\xc3\xf6(M)", /* "M音樂關"; "MMusic off" */
"P\xa8\x76\xb9\xb3\xb6\x7d(P)", /* "P肖像開"; "PPortrait on" */
"P\xa8\x76\xb9\xb3\xc3\xf6(P)", /* "P肖像關"; "PPortrait off" */
"JJoystick off", // Not used in Chinese as this button is skipped
"NNew Font Style", // Not used in Chinese as only one font is available
"S\xad\xb5\xae\xc4\xb6\x7d(S)", /* "S音效開"; "SSound Effects on" */
"S\xad\xb5\xae\xc4\xc3\xf6(S)", /* "S音效關"; "SSound Effects off" */
"W\xb5\xf8\xb5\xa1\xb7\xc6\xb1\xb2(W)", /* "W視窗滑捲"; "WWindow Slide Scroll" */
"W\xb5\xf8\xb5\xa1\xa8\x71\xa5\x58(W)", /* "W視窗秀出"; "WWindow Show" */
"C\xbd\xd5\xbe\xe3\xb7\x6e\xb1\xec(C)", /* "調整搖桿"; "CCalibrate Joystick" */
"A\xbb\xb2\xa7\x55\xa5\xaa(A)", /* "A輔助左"; "AAuto Help left" */
"A\xbb\xb2\xa7\x55\xa5\x6b(A)", /* "A輔助右"; "AAuto Help right" */
"VVoices on", // Not used in Chinese as no voices are available
"VVoices off", // Not used in Chinese as no voices are available
"F\xb2\x48\xa5\x58\xc2\x49\xaa\xac(F)", /* "F淡出點狀"; "FFade by Pixel" */
"F\xb2\x48\xa5\x58\xaa\xbd\xb1\xb5(F)", /* "F淡出直接"; "FFade Directly" */
"K\xc1\xe4\xaa\xa9\xba\x43(K)", /* "K鍵版慢"; "KKey Pad Slow" */
"K\xc1\xe4\xaa\xa9\xa7\xd6(K)", /* "K鍵版快"; "KKey Pad Fast" */
// Load/Save
"EExit", // TODO
"L\xb8\xfc\xa4\x4a(L)", /* "L載入"; "LLoad" */
"S\xc0\x78\xa6\x73(S)", /* "S儲存"; "SSave" */
"U\xa4\x57(U)", /* "U上"; "UUp" */
"D\xa4\x55(D)", /* "D下"; "DDown" */
"Q\xb5\xb2\xa7\xf4(Q)", /* "Q結束"; "QQuit" */
// Quit Game
"\xb1\x7a\xbd\x54\xa9\x77\xad\x6e\xb5\xb2\xa7\xf4\xb9\x43\xc0\xb8\xb6\xdc\x3f", /* "您確定要結束遊戲嗎?"; "Are you sure you wish to Quit ?" */
"Y\xac\x4f(Y)", /* "Y是"; "YYes" */
"N\xa4\xa3(N)", /* "N不"; "NNo" */
// SH1: Press key text
"P\xbd\xd0\xab\xf6\xa5\xf4\xb7\x4e\xc1\xe4\xc4\x7e\xc4\xf2\xa4\x55\xad\xb6\xa4\xba\xae\x65.(P)", /* "P請按任意鍵繼續下頁內容."; "PPress any Key for More." */
"P\xbd\xd0\xab\xf6\xa5\xf4\xb7\x4e\xc1\xe4\xc4\x7e\xc4\xf2.(P)", /* "P請按任意鍵繼續."; "PPress any Key to Continue." */
// SH1: Initial Inventory
"\xab\x4b\xb1\xf8\xaf\xc8", /* "便條紙"; "A message requesting help" */
"\xba\xd6\xba\xb8\xbc\xaf\xb4\xb5\xaa\xba\xa6\x57\xa4\xf9", /* "福爾摩斯的名片"; "A number of business cards" */
"\xba\x71\xbc\x40\xb0\x7c\xc1\x70\xb2\xbc", /* "歌劇院聯票"; "Opera Tickets" */
"\xb3\x53\xb3\xa7", /* "袖釦"; "Cuff Link" */
"\xc5\x4b\xb5\xb7\xa4\xc4", /* "鐵絲勾"; "Wire Hook" */
"\xa9\xf1\xa6\xe6\xb1\xf8", /* "放行條"; "Note" */
"\xa5\xb4\xb6\x7d\xaa\xba\xc3\x68\xbf\xf6", /* "打開的懷錶"; "An open pocket watch" */
"\xaf\xc8", /* "紙"; "A piece of paper with numbers on it" */
"\xab\x48", /* "信"; "A letter folded many times" */
"\xaf\xc8\xb5\x50", /* "紙牌"; "Tarot Cards" */
"\xb5\xd8\xc4\x52\xaa\xba\xc6\x5f\xb0\xcd", /* "華麗的鑰匙"; "An ornate key" */
"\xb7\xed\xb2\xbc", /* "當票"; "A pawn ticket" */
// SH1: User Interface
"\xa4\xa3\x2c\xc1\xc2\xc1\xc2\xb1\x7a\x2e", /* "不,謝謝您."; "No, thank you." */
"You can't do that.", // TODO
"\xa7\xb9\xb2\xa6\x2e\x2e\x2e", /* "完畢..."; "Done..." */
"Use ", // TODO
" on %s", // TODO
"Give ", // TODO
" to %s", // TODO
// SH1: People names
"\xba\xd6\xba\xb8\xbc\xaf\xb4\xb5", /* "福爾摩斯"; "Sherlock Holmes" */
"\xb5\xd8\xa5\xcd\xc2\xe5\xa5\xcd", /* "華生醫生"; "Dr. Watson" */
"\xb5\xdc\xb4\xb5\xb1\x5a\xbc\x77\xb1\xb4\xaa\xf8", /* "萊斯崔德探長"; "Inspector Lestrade" */
"\xb6\xf8\xa5\xac\xb5\xdc\xa6\x77\xa8\xb5\xa6\xf5", /* "奧布萊安巡佐"; "Constable O'Brien" */
"\xb9\x70\xba\xfb\xb4\xb5\xa8\xb5\xa6\xf5", /* "雷維斯巡佐"; "Constable Lewis" */
"\xdf\xc4\xdb\x69\x2e\xa9\xac\xa7\x4a", /* "葸菈.帕克"; "Sheila Parker" */
"\xa6\xeb\xa7\x51\x2e\xa5\x64\xcd\xba\xb7\xe6", /* "亨利.卡芮瑟"; "Henry Carruthers" */
"\xb5\xdc\xb5\xb7\xb2\xfa", /* "萊絲莉"; "Lesley" */
"\xa4\xde\xae\x79\xad\xfb", /* "引座員"; "An Usher" */
"\xa5\xb1\xb7\xe7\xbc\x77\x2e\xa6\xe3\xa7\x42\xb4\xb5\xa5\xc5", /* "弗瑞德.艾伯斯汀"; "Fredrick Epstein" */
"\xb4\xec\xa8\xaf\xb9\x79\xa4\xd3\xa4\xd3", /* "渥辛頓太太"; "Mrs. Worthington" */
"\xb1\xd0\xbd\x6d", /* "教練"; "The Coach" */
"\xa4\x40\xa6\x57\xb6\xa4\xad\xfb", /* "一名隊員"; "A Player" */
"\xb4\xa3\xa9\x69", /* "提姆"; "Tim", */
"\xa9\x69\xa4\x68\x2e\xae\xe1\xbc\x77\xb4\xb5", /* "姆士.桑德斯"; "James Sanders" */
"\xa8\xa9\xb2\xfa", /* "貝莉"; "Belle" */
"\xb2\x4d\xbc\xe4\xa4\x6b\xa4\x75", /* "清潔女工"; "Cleaning Girl" */
"\xc3\x51\xaa\xf7\xb4\xb5", /* "魏金斯"; "Wiggins" */
"\xab\x4f\xc3\xb9", /* "保羅"; "Paul" */
"\xb0\x73\xab\x4f", /* "酒保"; "The Bartender" */
"\xa4\x40\xad\xd3\xbb\xea\xc5\xbc\xaa\xba\xb0\x73\xb0\xad", /* "一個骯髒的酒鬼"; "A Dirty Drunk" */
"\xa4\x40\xad\xd3\xa4\x6a\xc1\x6e\xbb\xa1\xb8\xdc\xaa\xba\xb0\x73\xb0\xad", /* "一個大聲說話的酒鬼"; "A Shouting Drunk" */
"\xa4\x40\xad\xd3\xa8\xab\xb8\xf4\xb7\x6e\xb7\x45\xaa\xba\xb0\x73\xb0\xad", /* "一個走路搖幌的酒鬼"; "A Staggering Drunk" */
"\xab\x4f\xc3\xf0", /* "保鏢"; "The Bouncer" */
"\xc5\xe7\xab\xcd\xa9\x78", /* "驗屍官"; "The Coroner" */
"\xc3\x4d\xa4\x68\xaa\x41\xa9\xb1\xaa\xba\xb9\xd9\xad\x70", /* "騎士服店的夥計"; "Reginald Snipes" lit. "The clerk of the knight clothing store" */
"\xb3\xec\xaa\x76\x2e\xa5\xac\xb5\xdc\xa7\x4a\xa5\xee", /* "喬治.布萊克伍"; "George Blackwood" */
"\xbf\xe0\xa6\xd5\xb4\xb5", /* "賴耳斯"; "Lars" */
"\xc3\xc4\xa9\xd0\xa6\xd1\xaa\x4f", /* "藥房老板"; "The Chemist" lit "Pharmacy owner" */
"\xb8\xaf\xb7\xe7\xb4\xcb\xb1\xb4\xaa\xf8", /* "葛瑞森探長"; "Inspector Gregson" */
"\xb8\xeb\xa5\x69\xa7\x42\x2e\xaa\x6b\xa8\xaf\xb9\x79", /* "賈可伯.法辛頓"; "Jacob Farthington" */
"\xb3\xc1\xa6\xd2\xa4\xd2", /* "麥考夫"; "Mycroft" */
"\xa6\xd1\xb3\xb7\xb0\xd2", /* "老雪曼"; "Old Sherman" */
"\xb2\x7a\xac\x64", /* "理查"; "Richard" */
"\xbd\xd5\xb0\x73\xae\x76", /* "調酒師"; "The Barman" */
"\xa4\x40\xad\xd3\xa4\x40\xac\x79\xaa\xba\xaa\xb1\xaa\xcc", /* "一個一流的玩者"; "A Dandy Player" */
"\xa4\x40\xad\xd3\xa4\x54\xac\x79\xaa\xba\xaa\xb1\xaa\xcc", /* "一個三流的玩者"; "A Rough-looking Player" lit "A third-rate player" */
"\xae\xc7\xc6\x5b\xaa\xcc", /* "旁觀者"; "A Spectator" */
"\xc3\xb9\xa7\x42\x2e\xba\x7e\xaf\x53", /* "羅伯.漢特"; "Robert Hunt" */
"Violet", // TODO, Maybe "\xcb\xa2\xb5\xdc\xaf\x53", /* "芃萊特" */
"\xa8\xa9\xab\xd2\xae\xe6\xbe\x7c", /* "貝帝格魯"; "Pettigrew" */
"\xb6\xf8\xa6\x4e", /* "奧吉"; "Augie" */
"\xa6\x77\xae\x52\x2e\xa5\x64\xac\xa5\xc1\xa8", /* "安娜.卡洛薇"; "Anna Carroway" */
"\xc4\xb5\xbd\xc3", /* "警衛"; "A Guard" */
"\xa6\x77\xaa\x46\xa5\xa7\xb6\xf8\x2e\xa5\x64", /* "安東尼奧.卡"; "Antonio Caruso" */
"\xa6\xab\xa4\xf1", /* "托比"; "Toby the Dog" lit "Toby" */
"\xa6\xe8\xbb\x58\x2e\xaa\xf7\xb4\xb5\xb5\xdc", /* "西蒙.金斯萊"; "Simon Kingsley" */
"\xa8\xc8\xa6\xf2\xa6\x43\xbc\x77", /* "亞佛列德"; "Alfred" */
"\xa5\xac\xaa\xf9\xab\xc2\xba\xb8\xa4\xd2\xa4\x48", /* "布門威爾夫人"; "Lady Brumwell" */
"\xc3\xb9\xb2\xef\xa4\xd2\xa4\x48", /* "羅莎夫人"; "Madame Rosa" */
"\xac\xf9\xb7\xe6\x2e\xbc\xaf\xba\xb8\xae\xfc\xbc\x77", /* "約瑟.摩爾海德"; "Joseph Moorehead" */
"\xb2\xa6\xba\xb8\xa4\xd3\xa4\xd3", /* "畢爾太太"; "Mrs. Beale" */
"\xb5\xe1\xa7\x51\xa7\x4a\xb4\xb5", /* "菲利克斯"; "Felix" */
"\xb2\xfc\xc6\x46\xb9\x79", /* "荷靈頓"; "Hollingston" */
"\xa5\x64\xb5\xdc\xba\x7e\xa8\xb5\xa6\xf5", /* "卡萊漢巡佐"; "Constable Callaghan" */
"\xbe\x48\xaa\xd6\xa8\xb5\xa6\xf5", /* "鄧肯巡佐"; "Sergeant Duncan" */
"\xa5\xac\xaa\xf9\xab\xc2\xba\xb8\xc0\xef\xa4\x68", /* "布門威爾爵士"; "Lord Brumwell" */
"\xa5\xa7\xae\xe6\x2e\xb3\xc7\xa9\x69\xb4\xcb", /* "尼格.傑姆森"; "Nigel Jaimeson" */
"\xc1\xe9\xaf\xc7\xb4\xb5\x2e\xb7\xe7\xa7\x4a", /* "鍾納斯.瑞克"; "Jonas" */
"\xbe\x48\xae\xda\xa8\xb5\xa6\xf5" /* "鄧根巡佐"; "Constable Dugan" */
};
// =========================================
// === Sherlock Holmes 1: Serrated Scalpel ===
static const char *const fixedTextEN_ActionOpen[] = {
"This cannot be opened",
"It is already open",
"It is locked",
"Wait for Watson",
" ",
"."
};
static const char *const fixedTextDE_ActionOpen[] = {
"Das kann man nicht \224ffnen",
"Ist doch schon offen!",
"Leider verschlossen",
"Warte auf Watson",
" ",
"."
};
static const char *const fixedTextES_ActionOpen[] = {
"No puede ser abierto",
"Ya esta abierto",
"Esta cerrado",
"Espera a Watson",
" ",
"."
};
static const char *const fixedTextZH_ActionOpen[] = {
"\xb3\x6f\xb5\x4c\xaa\x6b\xa5\xb4\xb6\x7d\xaa\xba", /* "這無法打開的"; "This cannot be opened" */
"\xa5\xa6\xa4\x77\xb8\x67\xa5\xb4\xb6\x7d\xa4\x46", /* "它已經打開了"; "It is already open" */
"\xa5\xa6\xb3\x51\xc2\xea\xa6\xed\xa4\x46", /* "它被鎖住了"; "It is locked" */
"\xb5\xa5\xab\xdd\xb5\xd8\xa5\xcd", /* "等待華生"; "Wait for Watson" */
" ",
"."
};
static const char *const fixedTextEN_ActionClose[] = {
"This cannot be closed",
"It is already closed",
"The safe door is in the way"
};
static const char *const fixedTextDE_ActionClose[] = {
"Das kann man nicht schlie\341en",
"Ist doch schon zu!",
"Die safet\201r ist Weg"
};
static const char *const fixedTextES_ActionClose[] = {
"No puede ser cerrado",
"Ya esta cerrado",
"La puerta de seguridad esta entre medias"
};
static const char *const fixedTextZH_ActionClose[] = {
"\xb3\x6f\xb5\x4c\xaa\x6b\xc3\xf6\xa6\xed\xaa\xba", /* "這無法關住的"; "This cannot be closed" */
"\xa5\xa6\xa4\x77\xb8\x67\xc3\xf6\xb0\x5f\xa8\xd3\xa4\x46", /* "它已經關起來了"; "It is already closed" */
"The safe door is in the way", // TODO
};
static const char *const fixedTextEN_ActionMove[] = {
"This cannot be moved",
"It is bolted to the floor",
"It is too heavy",
"The other crate is in the way"
};
static const char *const fixedTextDE_ActionMove[] = {
"L\204\341t sich nicht bewegen",
"Festged\201belt in der Erde...",
"Oha, VIEL zu schwer",
"Die andere Kiste ist im Weg" // original: "Der andere Kiste ist im Weg"
};
static const char *const fixedTextES_ActionMove[] = {
"No puede moverse",
"Esta sujeto a la pared",
"Es demasiado pesado",
"El otro cajon esta en mitad"
};
static const char *const fixedTextZH_ActionMove[] = {
"\xb3\x6f\xb5\x4c\xaa\x6b\xb2\xbe\xb0\xca\xaa\xba", /* "這無法移動的"; "This cannot be moved" */
"It is bolted to the floor", // TODO
"\xb3\x6f\xaa\x46\xa6\xe8\xa4\xd3\xad\xab\xa4\x46", /* "這東西太重了"; "It is too heavy" */
"\xb3\x51\xa8\xe4\xa5\xa6\xaa\xba\xa4\xec\xbd\x63\xbe\xd7\xa6\xed\xb8\xf4\xa4\x46", /* "被其它的木箱擋住路了"; "The other crate is in the way" */
};
static const char *const fixedTextEN_ActionPick[] = {
"Nothing of interest here",
"It is bolted down",
"It is too big to carry",
"It is too heavy",
"I think a girl would be more your type",
"Those flowers belong to Penny",
"She's far too young for you!",
"I think a girl would be more your type!",
"Government property for official use only"
};
static const char *const fixedTextDE_ActionPick[] = {
"Nichts Interessantes da",
"Zu gut befestigt",
"Ist ja wohl ein bi\341chen zu gro\341, oder ?",
"Oha, VIEL zu schwer",
"Ich denke, Du stehst mehr auf M\204dchen ?",
"Diese Blumen geh\224ren Penny",
"Sie ist doch viel zu jung f\201r Dich!",
"Ich denke, Du stehst mehr auf M\204dchen ?",
"Staatseigentum - Nur f\201r den Dienstgebrauch !"
};
static const char *const fixedTextES_ActionPick[] = {
"No hay nada interesante",
"Esta anclado al suelo",
"Es muy grande para llevarlo",
"Pesa demasiado",
"Creo que una chica sera mas tu tipo",
"Esas flores pertenecen a Penny",
"\255Es demasiado joven para ti!",
"\255Creo que una chica sera mas tu tipo!",
"Propiedad del gobierno para uso oficial"
};
static const char *const fixedTextZH_ActionPick[] = {
"\xa8\x53\xa6\xb3\xa4\xb0\xbb\xf2\xa5\x69\xad\xc8\xb1\x6f\xae\xb3", /* "沒有什麼可值得拿"; "Nothing of interest here" */
"It is bolted down", // TODO
"It is too big to carry", // TODO
"\xa8\xba\xa4\xd3\xad\xab\xa4\x46", /* "那太重了"; "It is too heavy" */
"I think a girl would be more your type", // TODO
"Those flowers belong to Penny", // TODO
"She's far too young for you!", // TODO
"I think a girl would be more your type!", // TODO
"\xac\x46\xa9\xb2\xa9\xd2\xa6\xb3\x2c\xb6\xc8\xaf\xe0\xa8\xd1\xa9\x78\xa4\xe8\xa8\xcf\xa5\xce" /* "政府所有,僅能供官方使用"; "Government property for official use only" */
};
static const char *const fixedTextEN_ActionUse[] = {
"You can't do that",
"It had no effect",
"You can't reach it",
"OK, the door looks bigger! Happy?",
"Doors don't smoke"
};
static const char *const fixedTextDE_ActionUse[] = {
"Nein, das geht wirklich nicht",
"Tja keinerlei Wirkung",
"Da kommst du nicht dran",
"Na gut, die T\201r sieht jetzt gr\224\341er aus. Zufrieden?",
"T\201ren sind Nichtraucher!"
};
static const char *const fixedTextES_ActionUse[] = {
"No puedes hacerlo",
"No tuvo ningun efecto",
"No puedes alcanzarlo",
"Bien, \255es enorme! \250Feliz?",
"Las puertas no fuman"
};
static const char *const fixedTextZH_ActionUse[] = {
"\xb1\x7a\xb5\x4c\xaa\x6b\xa8\xba\xbc\xcb\xa8\xcf\xa5\xce", /* "您無法那樣使用"; "You can't do that" */
"\xa5\xa6\xac\x4f\xa8\x53\xa6\xb3\xae\xc4\xaa\x47\xaa\xba", /* "它是沒有效果的"; "It had no effect" */
"\xb1\x7a\xb5\x4c\xaa\x6b\xa8\xec\xb9\x46\xa8\xba\xc3\xe4", /* "您無法到達那邊"; "You can't reach it" */
"\xa6\x6e\xa4\x46\x21\xaa\xf9\xa4\x77\xb8\x67\xb6\x7d\xa4\x46\x2c\xb0\xaa\xbf\xb3\xb6\xdc\x3f", /* "好了!門已經開了,高興嗎?"; "OK, the door looks bigger! Happy?" */
"\xaa\xf9\xb5\x4c\xaa\x6b\xa9\xe2\xb7\xcf" /* "門無法抽煙"; "Doors don't smoke" */
};
#define FIXEDTEXT_GETCOUNT(_name_) sizeof(_name_) / sizeof(byte *)
#define FIXEDTEXT_ENTRY(_name_) _name_, FIXEDTEXT_GETCOUNT(_name_)
static const FixedTextActionEntry fixedTextEN_Actions[] = {
{ FIXEDTEXT_ENTRY(fixedTextEN_ActionOpen) },
{ FIXEDTEXT_ENTRY(fixedTextEN_ActionClose) },
{ FIXEDTEXT_ENTRY(fixedTextEN_ActionMove) },
{ FIXEDTEXT_ENTRY(fixedTextEN_ActionPick) },
{ FIXEDTEXT_ENTRY(fixedTextEN_ActionUse) }
};
static const FixedTextActionEntry fixedTextDE_Actions[] = {
{ FIXEDTEXT_ENTRY(fixedTextDE_ActionOpen) },
{ FIXEDTEXT_ENTRY(fixedTextDE_ActionClose) },
{ FIXEDTEXT_ENTRY(fixedTextDE_ActionMove) },
{ FIXEDTEXT_ENTRY(fixedTextDE_ActionPick) },
{ FIXEDTEXT_ENTRY(fixedTextDE_ActionUse) }
};
static const FixedTextActionEntry fixedTextES_Actions[] = {
{ FIXEDTEXT_ENTRY(fixedTextES_ActionOpen) },
{ FIXEDTEXT_ENTRY(fixedTextES_ActionClose) },
{ FIXEDTEXT_ENTRY(fixedTextES_ActionMove) },
{ FIXEDTEXT_ENTRY(fixedTextES_ActionPick) },
{ FIXEDTEXT_ENTRY(fixedTextES_ActionUse) }
};
static const FixedTextActionEntry fixedTextZH_Actions[] = {
{ FIXEDTEXT_ENTRY(fixedTextZH_ActionOpen) },
{ FIXEDTEXT_ENTRY(fixedTextZH_ActionClose) },
{ FIXEDTEXT_ENTRY(fixedTextZH_ActionMove) },
{ FIXEDTEXT_ENTRY(fixedTextZH_ActionPick) },
{ FIXEDTEXT_ENTRY(fixedTextZH_ActionUse) }
};
// =========================================
const FixedTextLanguageEntry fixedTextLanguages[] = {
{ Common::DE_DEU, fixedTextDE, fixedTextDE_Actions },
{ Common::ES_ESP, fixedTextES, fixedTextES_Actions },
{ Common::EN_ANY, fixedTextEN, fixedTextEN_Actions },
{ Common::ZH_TWN, fixedTextZH, fixedTextZH_Actions },
{ Common::UNK_LANG, fixedTextEN, fixedTextEN_Actions }
};
// =========================================
// =========================================
ScalpelFixedText::ScalpelFixedText(SherlockEngine *vm) : FixedText(vm) {
// Figure out which fixed texts to use
Common::Language curLanguage = _vm->getLanguage();
const FixedTextLanguageEntry *curLanguageEntry = fixedTextLanguages;
while (curLanguageEntry->language != Common::UNK_LANG) {
if (curLanguageEntry->language == curLanguage)
break; // found current language
curLanguageEntry++;
}
_curLanguageEntry = curLanguageEntry;
}
const char *ScalpelFixedText::getText(int fixedTextId) {
return _curLanguageEntry->fixedTextArray[fixedTextId];
}
const Common::String ScalpelFixedText::getActionMessage(FixedTextActionId actionId, int messageIndex) {
assert(actionId >= 0);
assert(messageIndex >= 0);
const FixedTextActionEntry *curActionEntry = &_curLanguageEntry->actionArray[actionId];
assert(messageIndex < curActionEntry->fixedTextArrayCount);
return Common::String(curActionEntry->fixedTextArray[messageIndex]);
}
} // End of namespace Scalpel
} // End of namespace Sherlock

View File

@@ -0,0 +1,215 @@
/* 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/>.
*
*/
#ifndef SHERLOCK_SCALPEL_FIXED_TEXT_H
#define SHERLOCK_SCALPEL_FIXED_TEXT_H
#include "sherlock/fixed_text.h"
namespace Sherlock {
namespace Scalpel {
enum FixedTextId {
// Game hotkeys
kFixedText_Game_Hotkeys = 0,
// Window buttons
kFixedText_Window_Exit,
kFixedText_Window_Up,
kFixedText_Window_Down,
// Inventory buttons
kFixedText_Inventory_Exit,
kFixedText_Inventory_Look,
kFixedText_Inventory_Use,
kFixedText_Inventory_Give,
// Journal text
kFixedText_Journal_WatsonsJournal,
kFixedText_Journal_Page,
// Journal buttons
kFixedText_Journal_Exit,
kFixedText_Journal_Back10,
kFixedText_Journal_Up,
kFixedText_Journal_Down,
kFixedText_Journal_Ahead10,
kFixedText_Journal_Search,
kFixedText_Journal_FirstPage,
kFixedText_Journal_LastPage,
kFixedText_Journal_PrintText,
// Journal search
kFixedText_JournalSearch_Exit,
kFixedText_JournalSearch_Backward,
kFixedText_JournalSearch_Forward,
kFixedText_JournalSearch_NotFound,
// Settings
kFixedText_Settings_Exit,
kFixedText_Settings_MusicOn,
kFixedText_Settings_MusicOff,
kFixedText_Settings_PortraitsOn,
kFixedText_Settings_PortraitsOff,
kFixedText_Settings_JoystickOff,
kFixedText_Settings_NewFontStyle,
kFixedText_Settings_SoundEffectsOn,
kFixedText_Settings_SoundEffectsOff,
kFixedText_Settings_WindowsSlide,
kFixedText_Settings_WindowsAppear,
kFixedText_Settings_CalibrateJoystick,
kFixedText_Settings_AutoHelpLeft,
kFixedText_Settings_AutoHelpRight,
kFixedText_Settings_VoicesOn,
kFixedText_Settings_VoicesOff,
kFixedText_Settings_FadeByPixel,
kFixedText_Settings_FadeDirectly,
kFixedText_Settings_KeyPadSlow,
kFixedText_Settings_KeyPadFast,
// Load/Save
kFixedText_LoadSave_Exit,
kFixedText_LoadSave_Load,
kFixedText_LoadSave_Save,
kFixedText_LoadSave_Up,
kFixedText_LoadSave_Down,
kFixedText_LoadSave_Quit,
// Quit Game
kFixedText_QuitGame_Question,
kFixedText_QuitGame_Yes,
kFixedText_QuitGame_No,
// Press key text
kFixedText_PressKey_ForMore,
kFixedText_PressKey_ToContinue,
// Initial inventory
kFixedText_InitInventory_Message,
kFixedText_InitInventory_HolmesCard,
kFixedText_InitInventory_Tickets,
kFixedText_InitInventory_CuffLink,
kFixedText_InitInventory_WireHook,
kFixedText_InitInventory_Note,
kFixedText_InitInventory_OpenWatch,
kFixedText_InitInventory_Paper,
kFixedText_InitInventory_Letter,
kFixedText_InitInventory_Tarot,
kFixedText_InitInventory_OrnateKey,
kFixedText_InitInventory_PawnTicket,
// SH1: User Interface
kFixedText_UserInterface_NoThankYou,
kFixedText_UserInterface_YouCantDoThat,
kFixedText_UserInterface_Done,
kFixedText_UserInterface_Use,
kFixedText_UserInterface_UseOn,
kFixedText_UserInterface_Give,
kFixedText_UserInterface_GiveTo,
// People names
kFixedText_People_SherlockHolmes,
kFixedText_People_DrWatson,
kFixedText_People_InspectorLestrade,
kFixedText_People_ConstableOBrien,
kFixedText_People_ConstableLewis,
kFixedText_People_SheilaParker,
kFixedText_People_HenryCarruthers,
kFixedText_People_Lesley,
kFixedText_People_AnUsher,
kFixedText_People_FredrickEpstein,
kFixedText_People_MrsWorthington,
kFixedText_People_TheCoach,
kFixedText_People_APlayer,
kFixedText_People_Tim,
kFixedText_People_JamesSanders,
kFixedText_People_Belle,
kFixedText_People_CleaningGirl,
kFixedText_People_Wiggins,
kFixedText_People_Paul,
kFixedText_People_TheBartender,
kFixedText_People_ADirtyDrunk,
kFixedText_People_AShoutingDrunk,
kFixedText_People_AStaggeringDrunk,
kFixedText_People_TheBouncer,
kFixedText_People_TheCoroner,
kFixedText_People_ReginaldSnipes,
kFixedText_People_GeorgeBlackwood,
kFixedText_People_Lars,
kFixedText_People_TheChemist,
kFixedText_People_InspectorGregson,
kFixedText_People_JacobFarthington,
kFixedText_People_Mycroft,
kFixedText_People_OldSherman,
kFixedText_People_Richard,
kFixedText_People_TheBarman,
kFixedText_People_ADandyPlayer,
kFixedText_People_ARoughlookingPlayer,
kFixedText_People_ASpectator,
kFixedText_People_RobertHunt,
kFixedText_People_Violet,
kFixedText_People_Pettigrew,
kFixedText_People_Augie,
kFixedText_People_AnnaCarroway,
kFixedText_People_AGuard,
kFixedText_People_AntonioCaruso,
kFixedText_People_TobyTheDog,
kFixedText_People_SimonKingsley,
kFixedText_People_Alfred,
kFixedText_People_LadyBrumwell,
kFixedText_People_MadameRosa,
kFixedText_People_JosephMoorehead,
kFixedText_People_MrsBeale,
kFixedText_People_Felix,
kFixedText_People_Hollingston,
kFixedText_People_ConstableCallaghan,
kFixedText_People_SergeantDuncan,
kFixedText_People_LordBrumwell,
kFixedText_People_NigelJaimeson,
kFixedText_People_Jonas,
kFixedText_People_ConstableDugan
};
struct FixedTextActionEntry {
const char *const *fixedTextArray;
int fixedTextArrayCount;
};
struct FixedTextLanguageEntry {
Common::Language language;
const char *const *fixedTextArray;
const FixedTextActionEntry *actionArray;
};
class ScalpelFixedText: public FixedText {
private:
const FixedTextLanguageEntry *_curLanguageEntry;
public:
ScalpelFixedText(SherlockEngine *vm);
~ScalpelFixedText() override {}
/**
* Gets text
*/
const char *getText(int fixedTextId) override;
/**
* Get action message
*/
const Common::String getActionMessage(FixedTextActionId actionId, int messageIndex) override;
};
extern const FixedTextLanguageEntry fixedTextLanguages[];
} // End of namespace Scalpel
} // End of namespace Sherlock
#endif

View File

@@ -0,0 +1,306 @@
/* 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 "sherlock/scalpel/scalpel_inventory.h"
#include "sherlock/scalpel/scalpel_fixed_text.h"
#include "sherlock/scalpel/scalpel_screen.h"
#include "sherlock/scalpel/scalpel_user_interface.h"
#include "sherlock/scalpel/scalpel.h"
namespace Sherlock {
namespace Scalpel {
ScalpelInventory::ScalpelInventory(SherlockEngine *vm) : Inventory(vm) {
_invShapes.resize(6);
_fixedTextExit = FIXED(Inventory_Exit);
_fixedTextLook = FIXED(Inventory_Look);
_fixedTextUse = FIXED(Inventory_Use);
_fixedTextGive = FIXED(Inventory_Give);
_actionsIndexed[0] = kActionScalpelInvExit;
_actionsIndexed[1] = kActionScalpelInvLook;
_actionsIndexed[2] = kActionScalpelInvUse;
_actionsIndexed[3] = kActionScalpelInvGive;
_actionsIndexed[4] = kActionScalpelInvLeft;
_actionsIndexed[5] = kActionScalpelInvRight;
_actionsIndexed[6] = kActionScalpelInvPageLeft;
_actionsIndexed[7] = kActionScalpelInvPageRight;
}
ScalpelInventory::~ScalpelInventory() {
}
int ScalpelInventory::identifyUserButton(Common::CustomEventType action) {
for (uint16 actionNr = 0; actionNr < ARRAYSIZE(_actionsIndexed); actionNr++) {
if (action == _actionsIndexed[actionNr])
return actionNr;
}
return -1;
}
void ScalpelInventory::drawInventory(InvNewMode mode) {
ScalpelScreen &screen = *(ScalpelScreen *)_vm->_screen;
UserInterface &ui = *_vm->_ui;
InvNewMode tempMode = mode;
loadInv();
if (mode == INVENTORY_DONT_DISPLAY) {
screen.activateBackBuffer2();
}
// Draw the window background
Surface &bb = *screen.getBackBuffer();
bb.fillRect(Common::Rect(0, CONTROLS_Y1, SHERLOCK_SCREEN_WIDTH, CONTROLS_Y1 + 10), BORDER_COLOR);
bb.fillRect(Common::Rect(0, CONTROLS_Y1 + 10, 2, SHERLOCK_SCREEN_HEIGHT), BORDER_COLOR);
bb.fillRect(Common::Rect(SHERLOCK_SCREEN_WIDTH - 2, CONTROLS_Y1 + 10,
SHERLOCK_SCREEN_WIDTH, SHERLOCK_SCREEN_HEIGHT), BORDER_COLOR);
bb.fillRect(Common::Rect(0, SHERLOCK_SCREEN_HEIGHT - 1, SHERLOCK_SCREEN_WIDTH,
SHERLOCK_SCREEN_HEIGHT), BORDER_COLOR);
bb.fillRect(Common::Rect(2, CONTROLS_Y1 + 10, SHERLOCK_SCREEN_WIDTH - 2, SHERLOCK_SCREEN_HEIGHT - 1),
INV_BACKGROUND);
// Draw the buttons
screen.makeButton(Common::Rect(INVENTORY_POINTS[0][0], CONTROLS_Y1, INVENTORY_POINTS[0][1],
CONTROLS_Y1 + 10), INVENTORY_POINTS[0][2], _fixedTextExit);
screen.makeButton(Common::Rect(INVENTORY_POINTS[1][0], CONTROLS_Y1, INVENTORY_POINTS[1][1],
CONTROLS_Y1 + 10), INVENTORY_POINTS[1][2], _fixedTextLook);
screen.makeButton(Common::Rect(INVENTORY_POINTS[2][0], CONTROLS_Y1, INVENTORY_POINTS[2][1],
CONTROLS_Y1 + 10), INVENTORY_POINTS[2][2], _fixedTextUse);
screen.makeButton(Common::Rect(INVENTORY_POINTS[3][0], CONTROLS_Y1, INVENTORY_POINTS[3][1],
CONTROLS_Y1 + 10), INVENTORY_POINTS[3][2], _fixedTextGive);
screen.makeButton(Common::Rect(INVENTORY_POINTS[4][0], CONTROLS_Y1, INVENTORY_POINTS[4][1],
CONTROLS_Y1 + 10), INVENTORY_POINTS[4][2] + 8, "^^", false); // 2 arrows pointing to the left
screen.makeButton(Common::Rect(INVENTORY_POINTS[5][0], CONTROLS_Y1, INVENTORY_POINTS[5][1],
CONTROLS_Y1 + 10), INVENTORY_POINTS[5][2] + 4, "^", false); // 1 arrow pointing to the left
screen.makeButton(Common::Rect(INVENTORY_POINTS[6][0], CONTROLS_Y1, INVENTORY_POINTS[6][1],
CONTROLS_Y1 + 10), INVENTORY_POINTS[6][2] + 4, "_", false); // 1 arrow pointing to the right
screen.makeButton(Common::Rect(INVENTORY_POINTS[7][0], CONTROLS_Y1, INVENTORY_POINTS[7][1],
CONTROLS_Y1 + 10), INVENTORY_POINTS[7][2] + 8, "__", false); // 2 arrows pointing to the right
if (tempMode == INVENTORY_DONT_DISPLAY)
mode = LOOK_INVENTORY_MODE;
_invMode = (InvMode)((int)mode);
if (mode != PLAIN_INVENTORY) {
assert((uint)mode < ARRAYSIZE(_actionsIndexed));
ui._oldAction = _actionsIndexed[mode];
} else {
ui._oldAction = (Common::CustomEventType) -1;
}
invCommands(0);
putInv(SLAM_DONT_DISPLAY);
if (tempMode != INVENTORY_DONT_DISPLAY) {
if (!ui._slideWindows) {
screen.slamRect(Common::Rect(0, CONTROLS_Y1, SHERLOCK_SCREEN_WIDTH, SHERLOCK_SCREEN_HEIGHT));
} else {
ui.summonWindow(false, CONTROLS_Y1);
}
ui._windowOpen = true;
} else {
// Reset the screen back buffer to the first buffer now that drawing is done
screen.activateBackBuffer1();
}
assert(IS_SERRATED_SCALPEL);
((ScalpelUserInterface *)_vm->_ui)->_oldUse = -1;
}
void ScalpelInventory::invCommands(bool slamIt) {
ScalpelScreen &screen = *(ScalpelScreen *)_vm->_screen;
UserInterface &ui = *_vm->_ui;
if (slamIt) {
screen.buttonPrint(Common::Point(INVENTORY_POINTS[0][2], CONTROLS_Y1),
_invMode == INVMODE_EXIT ? COMMAND_HIGHLIGHTED :COMMAND_FOREGROUND,
true, _fixedTextExit);
screen.buttonPrint(Common::Point(INVENTORY_POINTS[1][2], CONTROLS_Y1),
_invMode == INVMODE_LOOK ? COMMAND_HIGHLIGHTED :COMMAND_FOREGROUND,
true, _fixedTextLook);
screen.buttonPrint(Common::Point(INVENTORY_POINTS[2][2], CONTROLS_Y1),
_invMode == INVMODE_USE ? COMMAND_HIGHLIGHTED : COMMAND_FOREGROUND,
true, _fixedTextUse);
screen.buttonPrint(Common::Point(INVENTORY_POINTS[3][2], CONTROLS_Y1),
_invMode == INVMODE_GIVE ? COMMAND_HIGHLIGHTED : COMMAND_FOREGROUND,
true, _fixedTextGive);
screen.print(Common::Point(INVENTORY_POINTS[4][2], CONTROLS_Y1 + 1),
_invIndex == 0 ? COMMAND_NULL : COMMAND_FOREGROUND,
"^^"); // 2 arrows pointing to the left
screen.print(Common::Point(INVENTORY_POINTS[5][2], CONTROLS_Y1 + 1),
_invIndex == 0 ? COMMAND_NULL : COMMAND_FOREGROUND,
"^"); // 2 arrows pointing to the left
screen.print(Common::Point(INVENTORY_POINTS[6][2], CONTROLS_Y1 + 1),
(_holdings - _invIndex <= 6) ? COMMAND_NULL : COMMAND_FOREGROUND,
"_"); // 1 arrow pointing to the right
screen.print(Common::Point(INVENTORY_POINTS[7][2], CONTROLS_Y1 + 1),
(_holdings - _invIndex <= 6) ? COMMAND_NULL : COMMAND_FOREGROUND,
"__"); // 2 arrows pointing to the right
if (_invMode != INVMODE_LOOK)
ui.clearInfo();
} else {
screen.buttonPrint(Common::Point(INVENTORY_POINTS[0][2], CONTROLS_Y1),
_invMode == INVMODE_EXIT ? COMMAND_HIGHLIGHTED : COMMAND_FOREGROUND,
false, _fixedTextExit);
screen.buttonPrint(Common::Point(INVENTORY_POINTS[1][2], CONTROLS_Y1),
_invMode == INVMODE_LOOK ? COMMAND_HIGHLIGHTED : COMMAND_FOREGROUND,
false, _fixedTextLook);
screen.buttonPrint(Common::Point(INVENTORY_POINTS[2][2], CONTROLS_Y1),
_invMode == INVMODE_USE ? COMMAND_HIGHLIGHTED : COMMAND_FOREGROUND,
false, _fixedTextUse);
screen.buttonPrint(Common::Point(INVENTORY_POINTS[3][2], CONTROLS_Y1),
_invMode == INVMODE_GIVE ? COMMAND_HIGHLIGHTED : COMMAND_FOREGROUND,
false, _fixedTextGive);
screen.gPrint(Common::Point(INVENTORY_POINTS[4][2], CONTROLS_Y1),
_invIndex == 0 ? COMMAND_NULL : COMMAND_FOREGROUND,
"^^"); // 2 arrows pointing to the left
screen.gPrint(Common::Point(INVENTORY_POINTS[5][2], CONTROLS_Y1),
_invIndex == 0 ? COMMAND_NULL : COMMAND_FOREGROUND,
"^"); // 1 arrow pointing to the left
screen.gPrint(Common::Point(INVENTORY_POINTS[6][2], CONTROLS_Y1),
(_holdings - _invIndex < 7) ? COMMAND_NULL : COMMAND_FOREGROUND,
"_"); // 1 arrow pointing to the right
screen.gPrint(Common::Point(INVENTORY_POINTS[7][2], CONTROLS_Y1),
(_holdings - _invIndex < 7) ? COMMAND_NULL : COMMAND_FOREGROUND,
"__"); // 2 arrows pointing to the right
}
}
void ScalpelInventory::highlight(int index, byte color) {
Screen &screen = *_vm->_screen;
Surface &bb = *screen.getBackBuffer();
int slot = index - _invIndex;
ImageFrame &frame = (*_invShapes[slot])[0];
bb.fillRect(Common::Rect(8 + slot * 52, 165, (slot + 1) * 52, 194), color);
bb.SHtransBlitFrom(frame, Common::Point(6 + slot * 52 + ((47 - frame._width) / 2),
163 + ((33 - frame._height) / 2)));
screen.slamArea(8 + slot * 52, 165, 44, 30);
}
void ScalpelInventory::refreshInv() {
Screen &screen = *_vm->_screen;
Talk &talk = *_vm->_talk;
ScalpelUserInterface &ui = *(ScalpelUserInterface *)_vm->_ui;
ui._invLookFlag = true;
freeInv();
ui._infoFlag = true;
ui.clearInfo();
screen._backBuffer2.SHblitFrom(screen._backBuffer1, Common::Point(0, CONTROLS_Y),
Common::Rect(0, CONTROLS_Y, SHERLOCK_SCREEN_WIDTH, SHERLOCK_SCREEN_HEIGHT));
ui.examine();
if (!talk._talkToAbort) {
screen._backBuffer2.SHblitFrom((*ui._controlPanel)[0], Common::Point(0, CONTROLS_Y));
loadInv();
}
}
void ScalpelInventory::putInv(InvSlamMode slamIt) {
ScalpelScreen &screen = *(ScalpelScreen *)_vm->_screen;
UserInterface &ui = *_vm->_ui;
// If an inventory item has disappeared (due to using it or giving it),
// a blank space slot may have appeared. If so, adjust the inventory
if (_invIndex > 0 && _invIndex > (_holdings - (int)_invShapes.size())) {
--_invIndex;
freeGraphics();
loadGraphics();
}
if (slamIt != SLAM_SECONDARY_BUFFER) {
screen.makePanel(Common::Rect(6, 163, 54, 197));
screen.makePanel(Common::Rect(58, 163, 106, 197));
screen.makePanel(Common::Rect(110, 163, 158, 197));
screen.makePanel(Common::Rect(162, 163, 210, 197));
screen.makePanel(Common::Rect(214, 163, 262, 197));
screen.makePanel(Common::Rect(266, 163, 314, 197));
}
// Iterate through displaying up to 6 objects at a time
for (int idx = _invIndex; idx < _holdings && (idx - _invIndex) < (int)_invShapes.size(); ++idx) {
int itemNum = idx - _invIndex;
Surface &bb = (slamIt == SLAM_SECONDARY_BUFFER) ? screen._backBuffer2 : *screen.getBackBuffer();
Common::Rect r(8 + itemNum * 52, 165, 51 + itemNum * 52, 194);
// Draw the background
if (idx == ui._selector) {
bb.fillRect(r, BUTTON_BACKGROUND);
}
else if (slamIt == SLAM_SECONDARY_BUFFER) {
bb.fillRect(r, BUTTON_MIDDLE);
}
// Draw the item image
ImageFrame &frame = (*_invShapes[itemNum])[0];
bb.SHtransBlitFrom(frame, Common::Point(6 + itemNum * 52 + ((47 - frame._width) / 2),
163 + ((33 - frame._height) / 2)));
}
if (slamIt == SLAM_DISPLAY)
screen.slamArea(6, 163, 308, 34);
if (slamIt != SLAM_SECONDARY_BUFFER)
ui.clearInfo();
if (slamIt == 0) {
invCommands(0);
}
else if (slamIt == SLAM_SECONDARY_BUFFER) {
screen.activateBackBuffer2();
invCommands(0);
screen.activateBackBuffer1();
}
}
void ScalpelInventory::loadInv() {
// Exit if the inventory names are already loaded
if (_names.size() > 0)
return;
// Load the inventory names
Common::SeekableReadStream *stream = _vm->_res->load("invent.txt");
int streamSize = stream->size();
while (stream->pos() < streamSize) {
Common::String name;
char c;
while ((c = stream->readByte()) != 0)
name += c;
_names.push_back(name);
}
delete stream;
loadGraphics();
}
} // End of namespace Scalpel
} // End of namespace Sherlock

View File

@@ -0,0 +1,87 @@
/* 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/>.
*
*/
#ifndef SHERLOCK_SCALPEL_INVENTORY_H
#define SHERLOCK_SCALPEL_INVENTORY_H
#include "sherlock/inventory.h"
#include "common/events.h"
namespace Sherlock {
namespace Scalpel {
class ScalpelInventory : public Inventory {
public:
ScalpelInventory(SherlockEngine *vm);
~ScalpelInventory() override;
Common::String _fixedTextExit;
Common::String _fixedTextLook;
Common::String _fixedTextUse;
Common::String _fixedTextGive;
Common::CustomEventType _actionsIndexed[8];
/**
* Put the game into inventory mode and open the interface window.
*/
void drawInventory(InvNewMode flag);
/**
* Prints the line of inventory commands at the top of an inventory window with
* the correct highlighting
*/
void invCommands(bool slamIt);
/**
* Set the highlighting color of a given inventory item
*/
void highlight(int index, byte color);
/**
* Support method for refreshing the display of the inventory
*/
void refreshInv();
/**
* Display the character's inventory. The slamIt parameter specifies:
*/
void putInv(InvSlamMode slamIt);
/**
* Load the list of names the inventory items correspond to, if not already loaded,
* and then calls loadGraphics to load the associated graphics
*/
void loadInv() override;
/**
* Identifies a button number according to the action, that the user pressed
*/
int identifyUserButton(Common::CustomEventType action);
};
} // End of namespace Scalpel
} // End of namespace Sherlock
#endif

View File

@@ -0,0 +1,694 @@
/* 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 "sherlock/journal.h"
#include "sherlock/sherlock.h"
#include "sherlock/scalpel/scalpel_fixed_text.h"
#include "sherlock/scalpel/scalpel_journal.h"
#include "sherlock/scalpel/scalpel_screen.h"
#include "sherlock/scalpel/scalpel.h"
#include "sherlock/tattoo/tattoo_journal.h"
#include "graphics/palette.h"
#include "backends/keymapper/keymapper.h"
namespace Sherlock {
namespace Scalpel {
#define JOURNAL_BUTTONS_Y_INTL 178
#define JOURNAL_BUTTONS_Y_ZH 181
#define JOURNAL_SEARCH_LEFT 15
#define JOURNAL_SEARCH_TOP_INTL 186
#define JOURNAL_SEARCH_TOP_ZH 184
#define JOURNAL_SEARCH_RIGHT 296
#define JOURNAL_SEACRH_MAX_CHARS 50
// Positioning of buttons in the journal view
static const int JOURNAL_POINTS_INTL[9][3] = {
{ 6, 68, 37 },
{ 69, 131, 100 },
{ 132, 192, 162 },
{ 193, 250, 221 },
{ 251, 313, 281 },
{ 6, 82, 44 },
{ 83, 159, 121 },
{ 160, 236, 198 },
{ 237, 313, 275 }
};
static const int JOURNAL_POINTS_ZH[9][3] = {
{ 0, 52, 26 },
{ 52, 121, 87 },
{ 122, 157, 140 },
{ 157, 194, 176 },
{ 194, 265, 230 },
{ 265, 320, 293 },
{ 270, 320, 295 },
{ 270, 320, 295 },
{ 0, 0, 0 }
};
static const int SEARCH_POINTS_INTL[3][3] = {
{ 51, 123, 86 },
{ 124, 196, 159 },
{ 197, 269, 232 }
};
static const int SEARCH_POINTS_ZH[3][3] = {
{ 206, 243, 225 },
{ 243, 279, 261 },
{ 279, 315, 297 }
};
/*----------------------------------------------------------------*/
ScalpelJournal::ScalpelJournal(SherlockEngine *vm) : Journal(vm) {
if (_vm->_interactiveFl) {
// Load the journal directory and location names
loadLocations();
}
_fixedTextWatsonsJournal = FIXED(Journal_WatsonsJournal);
_fixedTextExit = FIXED(Journal_Exit);
_fixedTextBack10 = FIXED(Journal_Back10);
_fixedTextUp = FIXED(Journal_Up);
_fixedTextDown = FIXED(Journal_Down);
_fixedTextAhead10 = FIXED(Journal_Ahead10);
_fixedTextSearch = FIXED(Journal_Search);
_fixedTextFirstPage = FIXED(Journal_FirstPage);
_fixedTextLastPage = FIXED(Journal_LastPage);
_fixedTextPrintText = FIXED(Journal_PrintText);
_fixedTextSearchExit = FIXED(JournalSearch_Exit);
_fixedTextSearchBackward = FIXED(JournalSearch_Backward);
_fixedTextSearchForward = FIXED(JournalSearch_Forward);
_fixedTextSearchNotFound = FIXED(JournalSearch_NotFound);
_hotkeySearchExit = toupper(_fixedTextSearchExit[0]);
_hotkeySearchBackward = toupper(_fixedTextSearchBackward[0]);
_hotkeySearchForward = toupper(_fixedTextSearchForward[0]);
}
Common::Rect ScalpelJournal::getButtonRect(JournalButton btn) {
int idx = btn - 1;
if (_vm->getLanguage() == Common::ZH_TWN) {
if (btn >= BTN_FIRST_PAGE) {
return Common::Rect(JOURNAL_POINTS_ZH[idx][0], JOURNAL_BUTTONS_Y_ZH - (btn - BTN_FIRST_PAGE + 1) * 19,
JOURNAL_POINTS_ZH[idx][1], JOURNAL_BUTTONS_Y_ZH + 19 - (btn - BTN_FIRST_PAGE + 1) * 19);
} else
return Common::Rect(JOURNAL_POINTS_ZH[idx][0], JOURNAL_BUTTONS_Y_ZH,
JOURNAL_POINTS_ZH[idx][1], JOURNAL_BUTTONS_Y_ZH + 19);
} else {
if (btn >= BTN_SEARCH)
return Common::Rect(JOURNAL_POINTS_INTL[idx][0], JOURNAL_BUTTONS_Y_INTL + 11,
JOURNAL_POINTS_INTL[idx][1], JOURNAL_BUTTONS_Y_INTL + 21);
else
return Common::Rect(JOURNAL_POINTS_INTL[idx][0], JOURNAL_BUTTONS_Y_INTL,
JOURNAL_POINTS_INTL[idx][1], JOURNAL_BUTTONS_Y_INTL + 10);
}
}
Common::Point ScalpelJournal::getButtonTextPoint(JournalButton btn) {
int idx = btn - 1;
if (_vm->getLanguage() == Common::ZH_TWN) {
if (btn >= BTN_FIRST_PAGE)
return Common::Point(JOURNAL_POINTS_ZH[idx][2], JOURNAL_BUTTONS_Y_ZH + 2 - (btn - BTN_FIRST_PAGE + 1) * 19);
else
return Common::Point(JOURNAL_POINTS_ZH[idx][2], JOURNAL_BUTTONS_Y_ZH + 2);
} else {
if (btn >= BTN_SEARCH)
return Common::Point(JOURNAL_POINTS_INTL[idx][2], JOURNAL_BUTTONS_Y_INTL + 11);
else
return Common::Point(JOURNAL_POINTS_INTL[idx][2], JOURNAL_BUTTONS_Y_INTL);
}
}
Common::Rect ScalpelJournal::getSearchButtonRect(int idx) {
if (_vm->getLanguage() == Common::ZH_TWN) {
return Common::Rect(SEARCH_POINTS_ZH[idx][0], 175, SEARCH_POINTS_ZH[idx][1], 194);
} else {
return Common::Rect(SEARCH_POINTS_INTL[idx][0], 174, SEARCH_POINTS_INTL[idx][1], 184);
}
}
Common::Point ScalpelJournal::getSearchButtonTextPoint(int idx) {
if (_vm->getLanguage() == Common::ZH_TWN) {
return Common::Point(SEARCH_POINTS_ZH[idx][2], 177);
} else {
return Common::Point(SEARCH_POINTS_INTL[idx][2], 174);
}
}
void ScalpelJournal::loadLocations() {
Resources &res = *_vm->_res;
_directory.clear();
_locations.clear();
Common::SeekableReadStream *dir = res.load("talk.lib");
dir->skip(4); // Skip header
// Get the numer of entries
_directory.resize(dir->readUint16LE());
// Read in each entry
char buffer[17];
for (uint idx = 0; idx < _directory.size(); ++idx) {
dir->read(buffer, 17);
buffer[16] = '\0';
_directory[idx] = Common::String(buffer);
}
delete dir;
if (IS_3DO) {
// 3DO: storage of locations is currently unknown TODO
return;
}
// Load in the locations stored in journal.txt
Common::SeekableReadStream *loc = res.load("journal.txt");
while (loc->pos() < loc->size()) {
Common::String line;
char c;
while ((c = loc->readByte()) != 0)
line += c;
// WORKAROUND: Special fixes for faulty translations
// Was obviously not done in the original interpreter
if (_vm->getLanguage() == Common::ES_ESP) {
// Spanish version
// We fix all sorts of typos
// see bug #6931
if (line == "En el cajellon destras del teatro Regency") {
line = "En el callejon detras del teatro Regency";
} else if (line == "En el apartamente de Simon Kingsley") {
line = "En el apartamento de Simon Kingsley";
} else if (line == "Bajo la muelle de Savoy Pier") {
line = "Bajo el muelle de Savoy Pier";
} else if (line == "En le viejo Sherman") {
line = "En el viejo Sherman";
} else if (line == "En la entrada de la cada de Anna Carroway") {
line = "En la entrada de la casa de Anna Carroway";
}
}
_locations.push_back(line);
}
delete loc;
}
void ScalpelJournal::drawFrame() {
Resources &res = *_vm->_res;
ScalpelScreen &screen = *(ScalpelScreen *)_vm->_screen;
byte palette[Graphics::PALETTE_SIZE];
// Load in the journal background
Common::SeekableReadStream *bg = res.load("journal.lbv");
bg->read(screen._backBuffer1.getPixels(), SHERLOCK_SCREEN_WIDTH * SHERLOCK_SCREEN_HEIGHT);
bg->read(palette, Graphics::PALETTE_SIZE);
delete bg;
// Translate the palette for display
for (int idx = 0; idx < Graphics::PALETTE_SIZE; ++idx)
palette[idx] = PALETTE_6BIT_TO_8BIT(palette[idx]);
// Set the palette and print the title
screen.setPalette(palette);
if (_vm->getLanguage() == Common::ZH_TWN) {
screen.gPrint(Common::Point(111, 13), BUTTON_BOTTOM, "%s", _fixedTextWatsonsJournal.c_str());
screen.gPrint(Common::Point(110, 12), INV_FOREGROUND, "%s", _fixedTextWatsonsJournal.c_str());
} else {
screen.gPrint(Common::Point(111, 18), BUTTON_BOTTOM, "%s", _fixedTextWatsonsJournal.c_str());
screen.gPrint(Common::Point(110, 17), INV_FOREGROUND, "%s", _fixedTextWatsonsJournal.c_str());
}
// Draw the buttons
screen.makeButton(getButtonRect(BTN_EXIT), getButtonTextPoint(BTN_EXIT), _fixedTextExit);
screen.makeButton(getButtonRect(BTN_BACK10), getButtonTextPoint(BTN_BACK10), _fixedTextBack10);
screen.makeButton(getButtonRect(BTN_UP), getButtonTextPoint(BTN_UP), _fixedTextUp);
screen.makeButton(getButtonRect(BTN_DOWN), getButtonTextPoint(BTN_DOWN), _fixedTextDown);
screen.makeButton(getButtonRect(BTN_AHEAD110), getButtonTextPoint(BTN_AHEAD110), _fixedTextAhead10);
screen.makeButton(getButtonRect(BTN_SEARCH), getButtonTextPoint(BTN_SEARCH), _fixedTextSearch);
screen.makeButton(getButtonRect(BTN_FIRST_PAGE), getButtonTextPoint(BTN_FIRST_PAGE), _fixedTextFirstPage);
screen.makeButton(getButtonRect(BTN_LAST_PAGE), getButtonTextPoint(BTN_LAST_PAGE), _fixedTextLastPage);
// WORKAROUND: Draw Print Text button as disabled, since we don't support it in ScummVM
// In Chinese version skip it altogether to make space for hotkeys
if (_vm->getLanguage() != Common::ZH_TWN) {
screen.makeButton(getButtonRect(BTN_PRINT_TEXT), getButtonTextPoint(BTN_PRINT_TEXT), _fixedTextPrintText);
screen.buttonPrint(getButtonTextPoint(BTN_PRINT_TEXT), COMMAND_NULL, false, _fixedTextPrintText);
}
}
void ScalpelJournal::drawInterface() {
ScalpelScreen &screen = *(ScalpelScreen *)_vm->_screen;
drawFrame();
if (_journal.empty()) {
_up = _down = 0;
} else {
drawJournal(0, 0);
}
doArrows();
// Show the entire screen
screen.slamArea(0, 0, SHERLOCK_SCREEN_WIDTH, SHERLOCK_SCREEN_HEIGHT);
}
void ScalpelJournal::doArrows() {
ScalpelScreen &screen = *(ScalpelScreen *)_vm->_screen;
byte color;
color = (_page > 1) ? COMMAND_FOREGROUND : COMMAND_NULL;
screen.buttonPrint(getButtonTextPoint(BTN_BACK10), color, false, _fixedTextBack10);
screen.buttonPrint(getButtonTextPoint(BTN_UP), color, false, _fixedTextUp);
color = _down ? COMMAND_FOREGROUND : COMMAND_NULL;
screen.buttonPrint(getButtonTextPoint(BTN_DOWN), color, false, _fixedTextDown);
screen.buttonPrint(getButtonTextPoint(BTN_AHEAD110), color, false, _fixedTextAhead10);
screen.buttonPrint(getButtonTextPoint(BTN_LAST_PAGE), color, false, _fixedTextLastPage);
color = _journal.size() > 0 ? COMMAND_FOREGROUND : COMMAND_NULL;
screen.buttonPrint(getButtonTextPoint(BTN_SEARCH), color, false, _fixedTextSearch);
if (_vm->getLanguage() != Common::ZH_TWN) {
screen.buttonPrint(getButtonTextPoint(BTN_PRINT_TEXT), COMMAND_NULL, false, _fixedTextPrintText);
}
color = _page > 1 ? COMMAND_FOREGROUND : COMMAND_NULL;
screen.buttonPrint(getButtonTextPoint(BTN_FIRST_PAGE), color, false, _fixedTextFirstPage);
}
JournalButton ScalpelJournal::getHighlightedButton(const Common::Point &pt) {
if (getButtonRect(BTN_EXIT).contains(pt))
return BTN_EXIT;
if (getButtonRect(BTN_BACK10).contains(pt) && _page > 1)
return BTN_BACK10;
if (getButtonRect(BTN_UP).contains(pt) && _up)
return BTN_UP;
if (getButtonRect(BTN_DOWN).contains(pt) && _down)
return BTN_DOWN;
if (getButtonRect(BTN_AHEAD110).contains(pt) && _down)
return BTN_AHEAD110;
if (getButtonRect(BTN_SEARCH).contains(pt) && !_journal.empty())
return BTN_SEARCH;
if (getButtonRect(BTN_FIRST_PAGE).contains(pt) && _up)
return BTN_FIRST_PAGE;
if (getButtonRect(BTN_LAST_PAGE).contains(pt) && _down)
return BTN_LAST_PAGE;
if (_vm->getLanguage() != Common::ZH_TWN && getButtonRect(BTN_PRINT_TEXT).contains(pt) && !_journal.empty())
return BTN_PRINT_TEXT;
return BTN_NONE;
}
bool ScalpelJournal::handleEvents(Common::CustomEventType action) {
Events &events = *_vm->_events;
ScalpelScreen &screen = *(ScalpelScreen *)_vm->_screen;
bool doneFlag = false;
Common::Point pt = events.mousePos();
JournalButton btn = getHighlightedButton(pt);
byte color;
if (events._pressed || events._released) {
// Exit button
color = (btn == BTN_EXIT) ? COMMAND_HIGHLIGHTED : COMMAND_FOREGROUND;
screen.buttonPrint(getButtonTextPoint(BTN_EXIT), color, true, _fixedTextExit);
// Back 10 button
if (btn == BTN_BACK10) {
screen.buttonPrint(getButtonTextPoint(BTN_BACK10), COMMAND_HIGHLIGHTED, true, _fixedTextBack10);
} else if (_page > 1) {
screen.buttonPrint(getButtonTextPoint(BTN_BACK10), COMMAND_FOREGROUND, true, _fixedTextBack10);
}
// Up button
if (btn == BTN_UP) {
screen.buttonPrint(getButtonTextPoint(BTN_UP), COMMAND_HIGHLIGHTED, true, _fixedTextUp);
} else if (_up) {
screen.buttonPrint(getButtonTextPoint(BTN_UP), COMMAND_FOREGROUND, true, _fixedTextUp);
}
// Down button
if (btn == BTN_DOWN) {
screen.buttonPrint(getButtonTextPoint(BTN_DOWN), COMMAND_HIGHLIGHTED, true, _fixedTextDown);
} else if (_down) {
screen.buttonPrint(getButtonTextPoint(BTN_DOWN), COMMAND_FOREGROUND, true, _fixedTextDown);
}
// Ahead 10 button
if (btn == BTN_AHEAD110) {
screen.buttonPrint(getButtonTextPoint(BTN_AHEAD110), COMMAND_HIGHLIGHTED, true, _fixedTextAhead10);
} else if (_down) {
screen.buttonPrint(getButtonTextPoint(BTN_AHEAD110), COMMAND_FOREGROUND, true, _fixedTextAhead10);
}
// Search button
if (btn == BTN_SEARCH) {
color = COMMAND_HIGHLIGHTED;
} else if (_journal.empty()) {
color = COMMAND_NULL;
} else {
color = COMMAND_FOREGROUND;
}
screen.buttonPrint(getButtonTextPoint(BTN_SEARCH), color, true, _fixedTextSearch);
// First Page button
if (btn == BTN_FIRST_PAGE) {
color = COMMAND_HIGHLIGHTED;
} else if (_up) {
color = COMMAND_FOREGROUND;
} else {
color = COMMAND_NULL;
}
screen.buttonPrint(getButtonTextPoint(BTN_FIRST_PAGE), color, true, _fixedTextFirstPage);
// Last Page button
if (btn == BTN_LAST_PAGE) {
color = COMMAND_HIGHLIGHTED;
} else if (_down) {
color = COMMAND_FOREGROUND;
} else {
color = COMMAND_NULL;
}
screen.buttonPrint(getButtonTextPoint(BTN_LAST_PAGE), color, true, _fixedTextLastPage);
if (_vm->getLanguage() != Common::ZH_TWN) {
// Print Text button
screen.buttonPrint(getButtonTextPoint(BTN_PRINT_TEXT), COMMAND_NULL, true, _fixedTextPrintText);
}
}
if (btn == BTN_EXIT && events._released) {
// Exit button pressed
doneFlag = true;
} else if (((btn == BTN_BACK10 && events._released) || action == kActionScalpelJournalBack10) && (_page > 1)) {
// Scrolll up 10 pages
if (_page < 11)
drawJournal(1, (_page - 1) * LINES_PER_PAGE);
else
drawJournal(1, 10 * LINES_PER_PAGE);
doArrows();
screen.slamArea(0, 0, SHERLOCK_SCREEN_WIDTH, SHERLOCK_SCREEN_HEIGHT);
} else if (((btn == BTN_UP && events._released) || action == kActionScalpelScrollUp) && _up) {
// Scroll up
drawJournal(1, LINES_PER_PAGE);
doArrows();
screen.slamArea(0, 0, SHERLOCK_SCREEN_WIDTH, SHERLOCK_SCREEN_HEIGHT);
} else if (((btn == BTN_DOWN && events._released) || action == kActionScalpelScrollDown) && _down) {
// Scroll down
drawJournal(2, LINES_PER_PAGE);
doArrows();
screen.slamArea(0, 0, SHERLOCK_SCREEN_WIDTH, SHERLOCK_SCREEN_HEIGHT);
} else if (((btn == BTN_AHEAD110 && events._released) || action == kActionScalpelJournalAhead10) && _down) {
// Scroll down 10 pages
if ((_page + 10) > _maxPage)
drawJournal(2, (_maxPage - _page) * LINES_PER_PAGE);
else
drawJournal(2, 10 * LINES_PER_PAGE);
doArrows();
screen.slamArea(0, 0, SHERLOCK_SCREEN_WIDTH, SHERLOCK_SCREEN_HEIGHT);
} else if (((btn == BTN_SEARCH && events._released) || action == kActionScalpelJournalSearch) && !_journal.empty()) {
screen.buttonPrint(getButtonTextPoint(BTN_SEARCH), COMMAND_FOREGROUND, true, _fixedTextSearch);
bool notFound = false;
Common::Keymapper *keymapper = g_system->getEventManager()->getKeymapper();
keymapper->disableAllGameKeymaps();
do {
int dir;
if ((dir = getSearchString(notFound)) != 0) {
int savedIndex = _index;
int savedSub = _sub;
int savedPage = _page;
if (drawJournal(dir + 2, 1000 * LINES_PER_PAGE) == 0) {
_index = savedIndex;
_sub = savedSub;
_page = savedPage;
drawFrame();
drawJournal(0, 0);
notFound = true;
} else {
doneFlag = true;
}
doArrows();
screen.slamArea(0, 0, SHERLOCK_SCREEN_WIDTH, SHERLOCK_SCREEN_HEIGHT);
} else {
doneFlag = true;
}
} while (!doneFlag);
doneFlag = false;
keymapper->getKeymap("scalpel-journal")->setEnabled(true);
keymapper->getKeymap("scalpel-scroll")->setEnabled(true);
keymapper->getKeymap("scalpel-quit")->setEnabled(true);
} else if (((btn == BTN_FIRST_PAGE && events._released) || action == kActionScalpelJournalFirstPage) && _up) {
// First page
_index = _sub = 0;
_up = _down = false;
_page = 1;
drawFrame();
drawJournal(0, 0);
doArrows();
screen.slamArea(0, 0, SHERLOCK_SCREEN_WIDTH, SHERLOCK_SCREEN_HEIGHT);
} else if (((btn == BTN_LAST_PAGE && events._released) || action == kActionScalpelJournalLastPage) && _down) {
// Last page
if ((_page + 10) > _maxPage)
drawJournal(2, (_maxPage - _page) * LINES_PER_PAGE);
else
drawJournal(2, 1000 * LINES_PER_PAGE);
doArrows();
screen.slamArea(0, 0, SHERLOCK_SCREEN_WIDTH, SHERLOCK_SCREEN_HEIGHT);
}
events.wait(2);
return doneFlag;
}
int ScalpelJournal::getSearchString(bool printError) {
Events &events = *_vm->_events;
ScalpelScreen &screen = *(ScalpelScreen *)_vm->_screen;
Talk &talk = *_vm->_talk;
int xp;
int yp;
bool flag = false;
Common::String name;
int done = 0;
byte color;
bool isChinese = _vm->getLanguage() == Common::ZH_TWN;
// Draw search panel
if (isChinese)
screen.makePanel(Common::Rect(6, 171, 318, 199));
else
screen.makePanel(Common::Rect(6, 171, 313, 199));
screen.makeButton(getSearchButtonRect(0), getSearchButtonTextPoint(0), _fixedTextSearchExit, !isChinese);
screen.makeButton(getSearchButtonRect(1), getSearchButtonTextPoint(1), _fixedTextSearchBackward, !isChinese);
screen.makeButton(getSearchButtonRect(2), getSearchButtonTextPoint(2), _fixedTextSearchForward, !isChinese);
if (isChinese)
screen.makeField(Common::Rect(12, 175, 205, 194));
else
screen.makeField(Common::Rect(12, 185, 307, 196));
if (printError) {
screen.gPrint(Common::Point((SHERLOCK_SCREEN_WIDTH - screen.stringWidth(_fixedTextSearchNotFound)) / 2, 185),
INV_FOREGROUND, "%s", _fixedTextSearchNotFound.c_str());
} else if (!_find.empty()) {
// There's already a search term, display it already
screen.gPrint(Common::Point(15, 185), TALK_FOREGROUND, "%s", _find.c_str());
name = _find;
}
if (_vm->getLanguage() == Common::ZH_TWN)
screen.slamArea(6, 171, 312, 28);
else
screen.slamArea(6, 171, 307, 28);
if (printError) {
// Give time for user to see the message
events.setButtonState();
for (int idx = 0; idx < 40 && !_vm->shouldQuit() && !events.kbHit() && !events._released; ++idx) {
events.pollEvents();
events.setButtonState();
events.wait(2);
}
events.clearKeyboard();
events.clearActions();
screen._backBuffer1.fillRect(Common::Rect(13, 186, 306, 195), BUTTON_MIDDLE);
if (!_find.empty()) {
screen.gPrint(Common::Point(15, 185), TALK_FOREGROUND, "%s", _find.c_str());
name = _find;
}
screen.slamArea(0, 0, SHERLOCK_SCREEN_WIDTH, SHERLOCK_SCREEN_HEIGHT);
}
xp = JOURNAL_SEARCH_LEFT + screen.stringWidth(name);
yp = isChinese ? JOURNAL_SEARCH_TOP_ZH : JOURNAL_SEARCH_TOP_INTL;
do {
events._released = false;
JournalButton found = BTN_NONE;
while (!_vm->shouldQuit() && !events.kbHit() && !events._released) {
found = BTN_NONE;
if (talk._talkToAbort)
return 0;
// Check if key or mouse button press has occurred
events.setButtonState();
Common::Point pt = events.mousePos();
flag = !flag;
screen.vgaBar(Common::Rect(xp, yp, xp + 8, yp + 9), flag ? INV_FOREGROUND : BUTTON_MIDDLE);
if (events._pressed || events._released) {
if (getSearchButtonRect(0).contains(pt)) {
found = BTN_EXIT;
color = COMMAND_HIGHLIGHTED;
} else {
color = COMMAND_FOREGROUND;
}
screen.buttonPrint(getSearchButtonTextPoint(0), color, false, _fixedTextSearchExit, !isChinese);
if (getSearchButtonRect(1).contains(pt)) {
found = BTN_BACKWARD;
color = COMMAND_HIGHLIGHTED;
} else {
color = COMMAND_FOREGROUND;
}
screen.buttonPrint(getSearchButtonTextPoint(1), color, false, _fixedTextSearchBackward, !isChinese);
if (getSearchButtonRect(2).contains(pt)) {
found = BTN_FORWARD;
color = COMMAND_HIGHLIGHTED;
} else {
color = COMMAND_FOREGROUND;
}
screen.buttonPrint(getSearchButtonTextPoint(2), color, false, _fixedTextSearchForward, !isChinese);
}
events.wait(2);
}
if (events.kbHit()) {
Common::KeyState keyState = events.getKey();
if ((keyState.keycode == Common::KEYCODE_BACKSPACE) && (name.size() > 0)) {
screen.vgaBar(Common::Rect(xp - screen.charWidth(name.lastChar()), yp, xp + 8, yp + 9), BUTTON_MIDDLE);
xp -= screen.charWidth(name.lastChar());
screen.vgaBar(Common::Rect(xp, yp, xp + 8, yp + 9), INV_FOREGROUND);
name.deleteLastChar();
} else if (keyState.keycode == Common::KEYCODE_RETURN) {
done = 1;
} else if (keyState.keycode == Common::KEYCODE_ESCAPE) {
screen.vgaBar(Common::Rect(xp, yp, xp + 8, yp + 9), BUTTON_MIDDLE);
done = -1;
} else if (keyState.ascii >= ' ' && keyState.ascii <= 'z' && keyState.keycode != Common::KEYCODE_AT &&
name.size() < JOURNAL_SEACRH_MAX_CHARS && (xp + screen.charWidth(keyState.ascii)) < JOURNAL_SEARCH_RIGHT) {
char ch = toupper(keyState.ascii);
screen.vgaBar(Common::Rect(xp, yp, xp + 8, yp + 9), BUTTON_MIDDLE);
screen.print(Common::Point(xp, yp), TALK_FOREGROUND, "%c", ch);
xp += screen.charWidth(ch);
name += ch;
}
}
if (events._released) {
switch (found) {
case BTN_EXIT:
done = -1; break;
case BTN_BACKWARD:
done = 2; break;
case BTN_FORWARD:
done = 1; break;
default:
break;
}
}
} while (!done && !_vm->shouldQuit());
if (done != -1) {
_find = name;
} else {
done = 0;
}
// Redisplay the journal screen
drawFrame();
drawJournal(0, 0);
screen.slamArea(0, 0, SHERLOCK_SCREEN_WIDTH, SHERLOCK_SCREEN_HEIGHT);
return done;
}
void ScalpelJournal::resetPosition() {
_index = _sub = _up = _down = 0;
_page = 1;
}
void ScalpelJournal::record(int converseNum, int statementNum, bool replyOnly) {
// there seems to be no journal in the 3DO version
if (!IS_3DO)
Journal::record(converseNum, statementNum, replyOnly);
}
} // End of namespace Scalpel
} // End of namespace Sherlock

View File

@@ -0,0 +1,126 @@
/* 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/>.
*
*/
#ifndef SHERLOCK_SCALPEL_JOURNAL_H
#define SHERLOCK_SCALPEL_JOURNAL_H
#include "sherlock/journal.h"
#include "sherlock/saveload.h"
#include "common/scummsys.h"
#include "common/array.h"
#include "common/events.h"
#include "common/rect.h"
#include "common/str-array.h"
#include "common/stream.h"
namespace Sherlock {
namespace Scalpel {
enum JournalButton {
BTN_NONE, BTN_EXIT, BTN_BACK10, BTN_UP, BTN_DOWN, BTN_AHEAD110, BTN_SEARCH,
BTN_FIRST_PAGE, BTN_LAST_PAGE, BTN_PRINT_TEXT, BTN_BACKWARD, BTN_FORWARD
};
class ScalpelJournal: public Journal {
public:
Common::String _fixedTextWatsonsJournal;
Common::String _fixedTextExit;
Common::String _fixedTextBack10;
Common::String _fixedTextUp;
Common::String _fixedTextDown;
Common::String _fixedTextAhead10;
Common::String _fixedTextSearch;
Common::String _fixedTextFirstPage;
Common::String _fixedTextLastPage;
Common::String _fixedTextPrintText;
Common::String _fixedTextSearchExit;
Common::String _fixedTextSearchBackward;
Common::String _fixedTextSearchForward;
Common::String _fixedTextSearchNotFound;
byte _hotkeySearchExit;
byte _hotkeySearchBackward;
byte _hotkeySearchForward;
private:
/**
* Load the list of journal locations
*/
void loadLocations();
/**
* Display the arrows that can be used to scroll up and down pages
*/
void doArrows();
/**
* Show the search submenu and allow the player to enter a search string
*/
int getSearchString(bool printError);
/**
* Returns the button, if any, that is under the specified position
*/
JournalButton getHighlightedButton(const Common::Point &pt);
Common::Rect getSearchButtonRect(int idx);
Common::Point getSearchButtonTextPoint(int idx);
public:
ScalpelJournal(SherlockEngine *vm);
~ScalpelJournal() override {}
/**
* Display the journal
*/
void drawInterface();
/**
* Handle events whilst the journal is being displayed
*/
bool handleEvents(Common::CustomEventType action);
public:
/**
* Draw the journal background, frame, and interface buttons
*/
void drawFrame() override;
/**
* Reset viewing position to the start of the journal
*/
void resetPosition() override;
/**
* Records statements that are said, in the order which they are said. The player
* can then read the journal to review them
*/
void record(int converseNum, int statementNum, bool replyOnly = false) override;
Common::Rect getButtonRect(JournalButton btn);
Common::Point getButtonTextPoint(JournalButton btn);
};
} // End of namespace Scalpel
} // End of namespace Sherlock
#endif

View File

@@ -0,0 +1,621 @@
/* 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 "sherlock/scalpel/scalpel_map.h"
#include "sherlock/events.h"
#include "sherlock/people.h"
#include "sherlock/screen.h"
#include "sherlock/sherlock.h"
#include "backends/keymapper/keymapper.h"
namespace Sherlock {
namespace Scalpel {
MapPaths::MapPaths() {
_numLocations = 0;
}
void MapPaths::load(int numLocations, Common::SeekableReadStream &s) {
_numLocations = numLocations;
_paths.resize(_numLocations * _numLocations);
for (int idx = 0; idx < (numLocations * numLocations); ++idx) {
Common::Array<byte> &path = _paths[idx];
int v;
do {
v = s.readByte();
path.push_back(v);
} while (v && v < 254);
}
}
const byte *MapPaths::getPath(int srcLocation, int destLocation) {
return &_paths[srcLocation * _numLocations + destLocation][0];
}
/*----------------------------------------------------------------*/
ScalpelMap::ScalpelMap(SherlockEngine *vm): Map(vm), _topLine(g_system->getWidth(), 12, g_system->getScreenFormat()) {
_mapCursors = nullptr;
_shapes = nullptr;
_iconShapes = nullptr;
_point = 0;
_placesShown = false;
_cursorIndex = -1;
_drawMap = false;
_overPos = Point32(130 * FIXED_INT_MULTIPLIER, 126 * FIXED_INT_MULTIPLIER);
_frameChangeFlag = false;
// Initialise the initial walk sequence set
_walkSequences.resize(MAX_HOLMES_SEQUENCE);
for (int idx = 0; idx < MAX_HOLMES_SEQUENCE; ++idx) {
_walkSequences[idx]._sequences.resize(MAX_FRAME);
Common::fill(&_walkSequences[idx]._sequences[0], &_walkSequences[idx]._sequences[0] + MAX_FRAME, 0);
}
if (!_vm->isDemo())
loadData();
}
void ScalpelMap::loadPoints(int count, const int *xList, const int *yList, const int *transList) {
for (int idx = 0; idx < count; ++idx, ++xList, ++yList, ++transList) {
_points.push_back(MapEntry(*xList, *yList, *transList));
}
}
void ScalpelMap::loadSequences(int count, const byte *seq) {
for (int idx = 0; idx < count; ++idx, seq += MAX_FRAME)
Common::copy(seq, seq + MAX_FRAME, &_walkSequences[idx]._sequences[0]);
}
void ScalpelMap::loadData() {
// Load the list of location names
Common::SeekableReadStream *txtStream = _vm->_res->load("chess.txt");
int streamSize = txtStream->size();
while (txtStream->pos() < streamSize) {
Common::String line;
char c;
while ((c = txtStream->readByte()) != '\0')
line += c;
// WORKAROUND: Special fixes for faulty translations
// Was obviously not done in the original interpreter
if (_vm->getLanguage() == Common::ES_ESP) {
// Spanish version
if (line == " Alley") {
// The "Alley" location was not translated, we do this now
// see bug #6931
line = " Callejon";
} else if (line == " Alamacen") {
// "Warehouse" location has a typo, we fix it
// see bug #6931
line = " Almacen";
}
}
_locationNames.push_back(line);
}
delete txtStream;
// Load the path data
Common::SeekableReadStream *pathStream = _vm->_res->load("chess.pth");
// Get routes between different locations on the map
_paths.load(31, *pathStream);
// Load in the co-ordinates that the paths refer to
_pathPoints.resize(208);
for (uint idx = 0; idx < _pathPoints.size(); ++idx) {
_pathPoints[idx].x = pathStream->readSint16LE();
_pathPoints[idx].y = pathStream->readSint16LE();
}
delete pathStream;
}
int ScalpelMap::show() {
Debugger &debugger = *_vm->_debugger;
Events &events = *_vm->_events;
People &people = *_vm->_people;
Screen &screen = *_vm->_screen;
bool changed = false, exitFlag = false;
_active = true;
// Set font and custom cursor for the map
int oldFont = screen.fontNumber();
screen.setFont(0);
// Initial screen clear
screen._backBuffer1.clear();
screen.clear();
// Load the entire map
ImageFile *bigMap = nullptr;
if (!IS_3DO) {
// PC
bigMap = new ImageFile("bigmap.vgs");
screen.setPalette(bigMap->_palette);
} else {
// 3DO
bigMap = new ImageFile3DO("overland.cel", kImageFile3DOType_Cel);
}
// Load need sprites
setupSprites();
if (!IS_3DO) {
screen._backBuffer1.SHblitFrom((*bigMap)[0], Common::Point(-_bigPos.x, -_bigPos.y));
screen._backBuffer1.SHblitFrom((*bigMap)[1], Common::Point(-_bigPos.x, SHERLOCK_SCREEN_HEIGHT - _bigPos.y));
screen._backBuffer1.SHblitFrom((*bigMap)[2], Common::Point(SHERLOCK_SCREEN_WIDTH - _bigPos.x, -_bigPos.y));
screen._backBuffer1.SHblitFrom((*bigMap)[3], Common::Point(SHERLOCK_SCREEN_WIDTH - _bigPos.x, SHERLOCK_SCREEN_HEIGHT - _bigPos.y));
} else {
screen._backBuffer1.SHblitFrom((*bigMap)[0], Common::Point(-_bigPos.x, -_bigPos.y));
screen.SHblitFrom((*bigMap)[0], Common::Point(-_bigPos.x, -_bigPos.y));
}
_drawMap = true;
_charPoint = -1;
_point = -1;
people[HOLMES]._position = _lDrawnPos = _overPos;
// Show place icons
showPlaces();
saveTopLine();
_placesShown = true;
Common::Keymapper *keymapper = g_system->getEventManager()->getKeymapper();
keymapper->disableAllGameKeymaps();
keymapper->getKeymap("scalpel-map")->setEnabled(true);
// Keep looping until either a location is picked, or the game is ended
while (!_vm->shouldQuit() && !exitFlag) {
events.pollEventsAndWait();
events.setButtonState();
if (debugger._showAllLocations == LOC_REFRESH) {
showPlaces();
screen.slamArea(screen._currentScroll.x, screen._currentScroll.y, SHERLOCK_SCREEN_WIDTH, SHERLOCK_SCREEN_WIDTH);
}
// Action handling
if (events.actionHit()) {
Common::CustomEventType action = events.getAction();
if (action == kActionScalpelMapSelect) {
// this action simulates a mouse release
events._pressed = false;
events._released = true;
events._oldButtons = 0;
}
}
// Ignore scrolling attempts until the screen is drawn
if (!_drawMap) {
Common::Point pt = events.mousePos();
// Check for vertical map scrolling
if ((pt.y > (SHERLOCK_SCREEN_HEIGHT - 10) && _bigPos.y < 200) || (pt.y < 10 && _bigPos.y > 0)) {
if (pt.y > (SHERLOCK_SCREEN_HEIGHT - 10))
_bigPos.y += 10;
else
_bigPos.y -= 10;
changed = true;
}
// Check for horizontal map scrolling
if ((pt.x > (SHERLOCK_SCREEN_WIDTH - 10) && _bigPos.x < 315) || (pt.x < 10 && _bigPos.x > 0)) {
if (pt.x > (SHERLOCK_SCREEN_WIDTH - 10))
_bigPos.x += 15;
else
_bigPos.x -= 15;
changed = true;
}
}
if (changed) {
// Map has scrolled, so redraw new map view
changed = false;
if (!IS_3DO) {
screen._backBuffer1.SHblitFrom((*bigMap)[0], Common::Point(-_bigPos.x, -_bigPos.y));
screen._backBuffer1.SHblitFrom((*bigMap)[1], Common::Point(-_bigPos.x, SHERLOCK_SCREEN_HEIGHT - _bigPos.y));
screen._backBuffer1.SHblitFrom((*bigMap)[2], Common::Point(SHERLOCK_SCREEN_WIDTH - _bigPos.x, -_bigPos.y));
screen._backBuffer1.SHblitFrom((*bigMap)[3], Common::Point(SHERLOCK_SCREEN_WIDTH - _bigPos.x, SHERLOCK_SCREEN_HEIGHT - _bigPos.y));
} else {
screen._backBuffer1.SHblitFrom((*bigMap)[0], Common::Point(-_bigPos.x, -_bigPos.y));
}
showPlaces();
_placesShown = false;
saveTopLine();
_savedPos.x = -1;
updateMap(true);
} else if (!_drawMap) {
if (!_placesShown) {
showPlaces();
_placesShown = true;
}
if (_cursorIndex == 0) {
Common::Point pt = events.mousePos();
highlightIcon(Common::Point(pt.x - 4 + _bigPos.x, pt.y + _bigPos.y));
}
updateMap(false);
}
if ((events._released || events._rightReleased) && _point != -1) {
if (people[HOLMES]._walkCount == 0) {
people[HOLMES]._walkDest = _points[_point] + Common::Point(4, 9);
_charPoint = _point;
// Start walking to selected location
walkTheStreets();
// Show wait cursor
_cursorIndex = 1;
events.setCursor((*_mapCursors)[_cursorIndex]._frame);
}
}
// Check if a scene has beeen selected and we've finished "moving" to it
if (people[HOLMES]._walkCount == 0) {
if (_charPoint >= 1 && _charPoint < (int)_points.size())
exitFlag = true;
}
if (_drawMap) {
_drawMap = false;
if (screen._fadeStyle)
screen.randomTransition();
else
screen.slamArea(0, 0, SHERLOCK_SCREEN_WIDTH, SHERLOCK_SCREEN_HEIGHT);
}
// Wait for a frame
events.wait(1);
}
keymapper->getKeymap("scalpel-map")->setEnabled(false);
keymapper->getKeymap("sherlock-default")->setEnabled(true);
keymapper->getKeymap("scalpel")->setEnabled(true);
keymapper->getKeymap("scalpel-quit")->setEnabled(true);
freeSprites();
_overPos = people[HOLMES]._position;
// Reset font
screen.setFont(oldFont);
// Free map graphic
delete bigMap;
_active = false;
return _charPoint;
}
void ScalpelMap::setupSprites() {
Events &events = *_vm->_events;
People &people = *_vm->_people;
Scene &scene = *_vm->_scene;
_savedPos.x = -1;
if (!IS_3DO) {
// PC
_mapCursors = new ImageFile("omouse.vgs");
_shapes = new ImageFile("mapicon.vgs");
_iconShapes = new ImageFile("overicon.vgs");
} else {
// 3DO
_mapCursors = new ImageFile3DO("omouse.vgs", kImageFile3DOType_RoomFormat);
_shapes = new ImageFile3DO("mapicon.vgs", kImageFile3DOType_RoomFormat);
_iconShapes = new ImageFile3DO("overicon.vgs", kImageFile3DOType_RoomFormat);
}
_cursorIndex = 0;
events.setCursor((*_mapCursors)[_cursorIndex]._frame);
_iconSave.create((*_shapes)[4]._width, (*_shapes)[4]._height, g_system->getScreenFormat());
Person &p = people[HOLMES];
p._description = " ";
p._type = CHARACTER;
p._position = Common::Point(12400, 5000);
p._sequenceNumber = 0;
p._images = _shapes;
p._imageFrame = nullptr;
p._frameNumber = 0;
p._delta = Common::Point(0, 0);
p._oldSize = Common::Point(0, 0);
p._oldSize = Common::Point(0, 0);
p._misc = 0;
p._walkCount = 0;
p._allow = 0;
p._noShapeSize = Common::Point(0, 0);
p._goto = Common::Point(28000, 15000);
p._status = 0;
p._walkSequences = _walkSequences;
p.setImageFrame();
scene._bgShapes.clear();
}
void ScalpelMap::freeSprites() {
delete _mapCursors;
delete _shapes;
delete _iconShapes;
}
void ScalpelMap::showPlaces() {
Debugger &debugger = *_vm->_debugger;
Screen &screen = *_vm->_screen;
for (uint idx = 0; idx < _points.size(); ++idx) {
const MapEntry &pt = _points[idx];
if (pt.x != 0 && pt.y != 0) {
if (debugger._showAllLocations != LOC_DISABLED)
_vm->setFlagsDirect(idx);
if (pt.x >= _bigPos.x && (pt.x - _bigPos.x) < SHERLOCK_SCREEN_WIDTH
&& pt.y >= _bigPos.y && (pt.y - _bigPos.y) < SHERLOCK_SCREEN_HEIGHT) {
if (_vm->readFlags(idx)) {
screen._backBuffer1.SHtransBlitFrom((*_iconShapes)[pt._translate],
Common::Point(pt.x - _bigPos.x - 6, pt.y - _bigPos.y - 12));
}
}
}
}
if (debugger._showAllLocations == LOC_REFRESH)
debugger._showAllLocations = LOC_ALL;
}
void ScalpelMap::saveTopLine() {
_topLine.SHblitFrom(_vm->_screen->_backBuffer1, Common::Point(0, 0), Common::Rect(0, 0, SHERLOCK_SCREEN_WIDTH, 12));
}
void ScalpelMap::eraseTopLine() {
Screen &screen = *_vm->_screen;
screen._backBuffer1.SHblitFrom(_topLine, Common::Point(0, 0));
screen.slamArea(0, 0, SHERLOCK_SCREEN_WIDTH, _topLine.height());
}
void ScalpelMap::showPlaceName(int idx, bool highlighted) {
People &people = *_vm->_people;
Screen &screen = *_vm->_screen;
Common::String name = _locationNames[idx];
int width = screen.stringWidth(name);
if (!_cursorIndex) {
restoreIcon();
saveIcon(people[HOLMES]._imageFrame, _lDrawnPos);
bool flipped = people[HOLMES]._sequenceNumber == MAP_DOWNLEFT || people[HOLMES]._sequenceNumber == MAP_LEFT
|| people[HOLMES]._sequenceNumber == MAP_UPLEFT;
screen._backBuffer1.SHtransBlitFrom(*people[HOLMES]._imageFrame, _lDrawnPos, flipped);
}
if (highlighted) {
int xp = (SHERLOCK_SCREEN_WIDTH - screen.stringWidth(name)) / 2;
screen.gPrint(Common::Point(xp + 2, 2), BLACK, "%s", name.c_str());
screen.gPrint(Common::Point(xp + 1, 1), BLACK, "%s", name.c_str());
screen.gPrint(Common::Point(xp, 0), 12, "%s", name.c_str());
screen.slamArea(xp, 0, width + 2, 15);
}
}
void ScalpelMap::updateMap(bool flushScreen) {
Events &events = *_vm->_events;
People &people = *_vm->_people;
Screen &screen = *_vm->_screen;
Common::Point osPos = _savedPos;
Common::Point osSize = _savedSize;
Common::Point hPos;
if (_cursorIndex >= 1) {
if (++_cursorIndex > (1 + 8))
_cursorIndex = 1;
events.setCursor((*_mapCursors)[(_cursorIndex + 1) / 2]._frame);
}
if (!_drawMap && !flushScreen)
restoreIcon();
else
_savedPos.x = -1;
people[HOLMES].adjustSprite();
_lDrawnPos.x = hPos.x = people[HOLMES]._position.x / FIXED_INT_MULTIPLIER - _bigPos.x;
_lDrawnPos.y = hPos.y = people[HOLMES]._position.y / FIXED_INT_MULTIPLIER - people[HOLMES].frameHeight() - _bigPos.y;
// Draw the person icon
saveIcon(people[HOLMES]._imageFrame, hPos);
if (people[HOLMES]._sequenceNumber == MAP_DOWNLEFT || people[HOLMES]._sequenceNumber == MAP_LEFT
|| people[HOLMES]._sequenceNumber == MAP_UPLEFT)
screen._backBuffer1.SHtransBlitFrom(*people[HOLMES]._imageFrame, hPos, true);
else
screen._backBuffer1.SHtransBlitFrom(*people[HOLMES]._imageFrame, hPos, false);
if (flushScreen) {
screen.slamArea(0, 0, SHERLOCK_SCREEN_WIDTH, SHERLOCK_SCREEN_HEIGHT);
} else if (!_drawMap) {
if (hPos.x > 0 && hPos.y >= 0 && hPos.x < SHERLOCK_SCREEN_WIDTH && hPos.y < SHERLOCK_SCREEN_HEIGHT)
screen.flushImage(people[HOLMES]._imageFrame, Common::Point(people[HOLMES]._position.x / FIXED_INT_MULTIPLIER - _bigPos.x,
people[HOLMES]._position.y / FIXED_INT_MULTIPLIER - people[HOLMES].frameHeight() - _bigPos.y),
&people[HOLMES]._oldPosition.x, &people[HOLMES]._oldPosition.y, &people[HOLMES]._oldSize.x, &people[HOLMES]._oldSize.y);
if (osPos.x != -1)
screen.slamArea(osPos.x, osPos.y, osSize.x, osSize.y);
}
}
void ScalpelMap::walkTheStreets() {
People &people = *_vm->_people;
Common::Array<Common::Point> tempPath;
// Get indexes into the path lists for the start and destination scenes
int start = _points[_oldCharPoint]._translate;
int dest = _points[_charPoint]._translate;
// Get pointer to start of path
const byte *path = _paths.getPath(start, dest);
// Add in destination position
people[HOLMES]._walkTo.clear();
Common::Point destPos = people[HOLMES]._walkDest;
// Check for any intermediate points between the two locations
if (path[0] || _charPoint > 50 || _oldCharPoint > 50) {
people[HOLMES]._sequenceNumber = -1;
if (_charPoint == 51 || _oldCharPoint == 51) {
people[HOLMES].setWalking();
} else {
bool reversePath = false;
// Check for moving the path backwards or forwards
if (path[0] == 255) {
reversePath = true;
SWAP(start, dest);
path = _paths.getPath(start, dest);
}
do {
int idx = *path++;
tempPath.push_back(_pathPoints[idx - 1] + Common::Point(4, 4));
} while (*path != 254);
// Load up the path to use
people[HOLMES]._walkTo.clear();
if (reversePath) {
for (int idx = (int)tempPath.size() - 1; idx >= 0; --idx)
people[HOLMES]._walkTo.push(tempPath[idx]);
} else {
for (int idx = 0; idx < (int)tempPath.size(); ++idx)
people[HOLMES]._walkTo.push(tempPath[idx]);
}
people[HOLMES]._walkDest = people[HOLMES]._walkTo.pop() + Common::Point(12, 6);
people[HOLMES].setWalking();
}
} else {
people[HOLMES]._walkCount = 0;
}
// Store the final destination icon position
people[HOLMES]._walkTo.push(destPos);
}
void ScalpelMap::saveIcon(ImageFrame *src, const Common::Point &pt) {
Screen &screen = *_vm->_screen;
Common::Point size(src->_width, src->_height);
Common::Point pos = pt;
if (pos.x < 0) {
size.x += pos.x;
pos.x = 0;
}
if (pos.y < 0) {
size.y += pos.y;
pos.y = 0;
}
if ((pos.x + size.x) > SHERLOCK_SCREEN_WIDTH)
size.x -= (pos.x + size.x) - SHERLOCK_SCREEN_WIDTH;
if ((pos.y + size.y) > SHERLOCK_SCREEN_HEIGHT)
size.y -= (pos.y + size.y) - SHERLOCK_SCREEN_HEIGHT;
if (size.x < 1 || size.y < 1 || pos.x >= SHERLOCK_SCREEN_WIDTH || pos.y >= SHERLOCK_SCREEN_HEIGHT || _drawMap) {
// Flag as the area not needing to be saved
_savedPos.x = -1;
return;
}
assert(size.x <= _iconSave.width() && size.y <= _iconSave.height());
_iconSave.SHblitFrom(screen._backBuffer1, Common::Point(0, 0),
Common::Rect(pos.x, pos.y, pos.x + size.x, pos.y + size.y));
_savedPos = pos;
_savedSize = size;
}
void ScalpelMap::restoreIcon() {
Screen &screen = *_vm->_screen;
if (_savedPos.x >= 0 && _savedPos.y >= 0 && _savedPos.x <= SHERLOCK_SCREEN_WIDTH
&& _savedPos.y < SHERLOCK_SCREEN_HEIGHT)
screen._backBuffer1.SHblitFrom(_iconSave, _savedPos, Common::Rect(0, 0, _savedSize.x, _savedSize.y));
}
void ScalpelMap::highlightIcon(const Common::Point &pt) {
int oldPoint = _point;
// Iterate through the icon list
bool done = false;
for (int idx = 0; idx < (int)_points.size(); ++idx) {
const MapEntry &entry = _points[idx];
// Check whether the mouse is over a given icon
if (entry.x != 0 && entry.y != 0) {
if (Common::Rect(entry.x - 8, entry.y - 8, entry.x + 9, entry.y + 9).contains(pt)) {
done = true;
if (_point != idx && _vm->readFlags(idx)) {
// Changed to a new valid (visible) location
eraseTopLine();
showPlaceName(idx, true);
_point = idx;
}
}
}
}
if (!done) {
// No icon was highlighted
if (_point != -1) {
// No longer highlighting previously highlighted icon, so erase it
showPlaceName(_point, false);
eraseTopLine();
}
_point = -1;
} else if (oldPoint != -1 && oldPoint != _point) {
showPlaceName(oldPoint, false);
eraseTopLine();
}
}
} // End of namespace Scalpel
} // End of namespace Sherlock

View File

@@ -0,0 +1,172 @@
/* 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/>.
*
*/
#ifndef SHERLOCK_SCALPEL_MAP_H
#define SHERLOCK_SCALPEL_MAP_H
#include "common/scummsys.h"
#include "common/array.h"
#include "common/rect.h"
#include "common/str-array.h"
#include "sherlock/surface.h"
#include "sherlock/map.h"
#include "sherlock/resources.h"
namespace Sherlock {
class SherlockEngine;
namespace Scalpel {
struct MapEntry : Common::Point {
int _translate;
MapEntry() : Common::Point(), _translate(-1) {}
MapEntry(int posX, int posY, int translate) : Common::Point(posX, posY), _translate(translate) {}
};
class MapPaths {
private:
int _numLocations;
Common::Array< Common::Array<byte> > _paths;
public:
MapPaths();
/**
* Load the data for the paths between locations on the map
*/
void load(int numLocations, Common::SeekableReadStream &s);
/**
* Get the path between two locations on the map
*/
const byte *getPath(int srcLocation, int destLocation);
};
class ScalpelMap: public Map {
private:
Common::Array<MapEntry> _points; // Map locations for each scene
Common::StringArray _locationNames;
MapPaths _paths;
Common::Array<Common::Point> _pathPoints;
Common::Point _savedPos;
Common::Point _savedSize;
Surface _topLine;
ImageFile *_mapCursors;
ImageFile *_shapes;
ImageFile *_iconShapes;
WalkSequences _walkSequences;
Point32 _lDrawnPos;
int _point;
bool _placesShown;
int _cursorIndex;
bool _drawMap;
Surface _iconSave;
protected:
/**
* Load data needed for the map
*/
void loadData();
/**
* Load and initialize all the sprites that are needed for the map display
*/
void setupSprites();
/**
* Free the sprites and data used by the map
*/
void freeSprites();
/**
* Draws an icon for every place that's currently known
*/
void showPlaces();
/**
* Makes a copy of the top rows of the screen that are used to display location names
*/
void saveTopLine();
/**
* Erases anything shown in the top line by restoring the previously saved original map background
*/
void eraseTopLine();
/**
* Prints the name of the specified icon
*/
void showPlaceName(int idx, bool highlighted);
/**
* Update all on-screen sprites to account for any scrolling of the map
*/
void updateMap(bool flushScreen);
/**
* Handle moving icon for player from their previous location on the map to a destination location
*/
void walkTheStreets();
/**
* Save the area under the player's icon
*/
void saveIcon(ImageFrame *src, const Common::Point &pt);
/**
* Restore the area under the player's icon
*/
void restoreIcon();
/**
* Handles highlighting map icons, showing their names
*/
void highlightIcon(const Common::Point &pt);
public:
ScalpelMap(SherlockEngine *vm);
~ScalpelMap() override {}
const MapEntry &operator[](int idx) { return _points[idx]; }
/**
* Loads the list of points for locations on the map for each scene
*/
void loadPoints(int count, const int *xList, const int *yList, const int *transList);
/**
* Load the sequence data for player icon animations
*/
void loadSequences(int count, const byte *seq);
/**
* Show the map
*/
int show() override;
};
} // End of namespace Scalpel
} // End of namespace Sherlock
#endif

View File

@@ -0,0 +1,570 @@
/* 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 "sherlock/scalpel/scalpel_people.h"
#include "sherlock/scalpel/scalpel_map.h"
#include "sherlock/sherlock.h"
namespace Sherlock {
namespace Scalpel {
// Walk speeds
#define MWALK_SPEED 2
#define XWALK_SPEED 4
#define YWALK_SPEED 1
/*----------------------------------------------------------------*/
void ScalpelPerson::adjustSprite() {
Map &map = *_vm->_map;
People &people = *_vm->_people;
Scene &scene = *_vm->_scene;
Talk &talk = *_vm->_talk;
if (_type == INVALID || (_type == CHARACTER && scene._animating))
return;
if (!talk._talkCounter && _type == CHARACTER && _walkCount) {
// Handle active movement for the sprite
_position += _delta;
--_walkCount;
if (!_walkCount) {
// If there any points left for the character to walk to along the
// route to a destination, then move to the next point
if (!people[HOLMES]._walkTo.empty()) {
_walkDest = people[HOLMES]._walkTo.pop();
setWalking();
} else {
gotoStand();
}
}
}
if (_type == CHARACTER && !map._active) {
if ((_position.y / FIXED_INT_MULTIPLIER) > LOWER_LIMIT) {
_position.y = LOWER_LIMIT * FIXED_INT_MULTIPLIER;
gotoStand();
}
if ((_position.y / FIXED_INT_MULTIPLIER) < UPPER_LIMIT) {
_position.y = UPPER_LIMIT * FIXED_INT_MULTIPLIER;
gotoStand();
}
if ((_position.x / FIXED_INT_MULTIPLIER) < LEFT_LIMIT) {
_position.x = LEFT_LIMIT * FIXED_INT_MULTIPLIER;
gotoStand();
}
if ((_position.x / FIXED_INT_MULTIPLIER) > RIGHT_LIMIT) {
_position.x = RIGHT_LIMIT * FIXED_INT_MULTIPLIER;
gotoStand();
}
} else if (!map._active) {
_position.y = CLIP((int)_position.y, (int)UPPER_LIMIT, (int)LOWER_LIMIT);
_position.x = CLIP((int)_position.x, (int)LEFT_LIMIT, (int)RIGHT_LIMIT);
}
if (!map._active || (map._frameChangeFlag = !map._frameChangeFlag))
++_frameNumber;
if (_frameNumber >= (int)_walkSequences[_sequenceNumber]._sequences.size() ||
_walkSequences[_sequenceNumber][_frameNumber] == 0) {
switch (_sequenceNumber) {
case STOP_UP:
case STOP_DOWN:
case STOP_LEFT:
case STOP_RIGHT:
case STOP_UPRIGHT:
case STOP_UPLEFT:
case STOP_DOWNRIGHT:
case STOP_DOWNLEFT:
// We're in a stop sequence, so reset back to the last frame, so
// the character is shown as standing still
--_frameNumber;
break;
default:
// Move 1 past the first frame - we need to compensate, since we
// already passed the frame increment
_frameNumber = 1;
break;
}
}
// Update the _imageFrame to point to the new frame's image
setImageFrame();
// Check to see if character has entered an exit zone
if (!_walkCount && scene._walkedInScene && scene._goToScene == -1) {
Common::Rect charRect(_position.x / FIXED_INT_MULTIPLIER - 5, _position.y / FIXED_INT_MULTIPLIER - 2,
_position.x / FIXED_INT_MULTIPLIER + 5, _position.y / FIXED_INT_MULTIPLIER + 2);
Exit *exit = scene.checkForExit(charRect);
if (exit) {
scene._goToScene = exit->_scene;
if (exit->_newPosition.x != 0) {
people._savedPos = exit->_newPosition;
if (people._savedPos._facing > 100 && people._savedPos.x < 1)
people._savedPos.x = 100;
}
}
}
}
void ScalpelPerson::gotoStand() {
ScalpelMap &map = *(ScalpelMap *)_vm->_map;
People &people = *_vm->_people;
_walkTo.clear();
_walkCount = 0;
switch (_sequenceNumber) {
case Scalpel::WALK_UP:
_sequenceNumber = STOP_UP;
break;
case WALK_DOWN:
_sequenceNumber = STOP_DOWN;
break;
case TALK_LEFT:
case WALK_LEFT:
_sequenceNumber = STOP_LEFT;
break;
case TALK_RIGHT:
case WALK_RIGHT:
_sequenceNumber = STOP_RIGHT;
break;
case WALK_UPRIGHT:
_sequenceNumber = STOP_UPRIGHT;
break;
case WALK_UPLEFT:
_sequenceNumber = STOP_UPLEFT;
break;
case WALK_DOWNRIGHT:
_sequenceNumber = STOP_DOWNRIGHT;
break;
case WALK_DOWNLEFT:
_sequenceNumber = STOP_DOWNLEFT;
break;
default:
break;
}
// Only restart frame at 0 if the sequence number has changed
if (_oldWalkSequence != -1 || _sequenceNumber == Scalpel::STOP_UP)
_frameNumber = 0;
if (map._active) {
_sequenceNumber = 0;
people[HOLMES]._position.x = (map[map._charPoint].x - 6) * FIXED_INT_MULTIPLIER;
people[HOLMES]._position.y = (map[map._charPoint].y + 10) * FIXED_INT_MULTIPLIER;
}
_oldWalkSequence = -1;
people._allowWalkAbort = true;
}
void ScalpelPerson::setWalking() {
Map &map = *_vm->_map;
Scene &scene = *_vm->_scene;
int oldDirection, oldFrame;
Common::Point speed, delta;
// Flag that player has now walked in the scene
scene._walkedInScene = true;
// Stop any previous walking, since a new dest is being set
_walkCount = 0;
oldDirection = _sequenceNumber;
oldFrame = _frameNumber;
// Set speed to use horizontal and vertical movement
if (map._active) {
speed = Common::Point(MWALK_SPEED, MWALK_SPEED);
} else {
speed = Common::Point(XWALK_SPEED, YWALK_SPEED);
}
// If the player is already close to the given destination that no
// walking is needed, move to the next straight line segment in the
// overall walking route, if there is one
for (;;) {
// Since we want the player to be centered on the destination they
// clicked, but characters draw positions start at their left, move
// the destination half the character width to draw him centered
int temp;
if (_walkDest.x >= (temp = _imageFrame->_frame.w / 2))
_walkDest.x -= temp;
delta = Common::Point(
ABS(_position.x / FIXED_INT_MULTIPLIER - _walkDest.x),
ABS(_position.y / FIXED_INT_MULTIPLIER - _walkDest.y)
);
// If we're ready to move a sufficient distance, that's it. Otherwise,
// move onto the next portion of the walk path, if there is one
if ((delta.x > 3 || delta.y > 0) || _walkTo.empty())
break;
// Pop next walk segment off the walk route stack
_walkDest = _walkTo.pop();
}
// If a sufficient move is being done, then start the move
if (delta.x > 3 || delta.y) {
// See whether the major movement is horizontal or vertical
if (delta.x >= delta.y) {
// Set the initial frame sequence for the left and right, as well
// as setting the delta x depending on direction
if (_walkDest.x < (_position.x / FIXED_INT_MULTIPLIER)) {
_sequenceNumber = (map._active ? (int)MAP_LEFT : (int)WALK_LEFT);
_delta.x = speed.x * -FIXED_INT_MULTIPLIER;
} else {
_sequenceNumber = (map._active ? (int)MAP_RIGHT : (int)WALK_RIGHT);
_delta.x = speed.x * FIXED_INT_MULTIPLIER;
}
// See if the x delta is too small to be divided by the speed, since
// this would cause a divide by zero error
if (delta.x >= speed.x) {
// Det the delta y
_delta.y = (delta.y * FIXED_INT_MULTIPLIER) / (delta.x / speed.x);
if (_walkDest.y < (_position.y / FIXED_INT_MULTIPLIER))
_delta.y = -_delta.y;
// Set how many times we should add the delta to the player's position
_walkCount = delta.x / speed.x;
} else {
// The delta x was less than the speed (ie. we're really close to
// the destination). So set delta to 0 so the player won't move
_delta = Point32(0, 0);
_position = Point32(_walkDest.x * FIXED_INT_MULTIPLIER, _walkDest.y * FIXED_INT_MULTIPLIER);
_walkCount = 1;
}
// See if the sequence needs to be changed for diagonal walking
if (_delta.y > 150) {
if (!map._active) {
switch (_sequenceNumber) {
case WALK_LEFT:
_sequenceNumber = WALK_DOWNLEFT;
break;
case WALK_RIGHT:
_sequenceNumber = WALK_DOWNRIGHT;
break;
default:
break;
}
}
} else if (_delta.y < -150) {
if (!map._active) {
switch (_sequenceNumber) {
case WALK_LEFT:
_sequenceNumber = WALK_UPLEFT;
break;
case WALK_RIGHT:
_sequenceNumber = WALK_UPRIGHT;
break;
default:
break;
}
}
}
} else {
// Major movement is vertical, so set the sequence for up and down,
// and set the delta Y depending on the direction
if (_walkDest.y < (_position.y / FIXED_INT_MULTIPLIER)) {
_sequenceNumber = WALK_UP;
_delta.y = speed.y * -FIXED_INT_MULTIPLIER;
} else {
_sequenceNumber = WALK_DOWN;
_delta.y = speed.y * FIXED_INT_MULTIPLIER;
}
// If we're on the overhead map, set the sequence so we keep moving
// in the same direction
if (map._active)
_sequenceNumber = (oldDirection == -1) ? MAP_RIGHT : oldDirection;
// Set the delta x
_delta.x = (delta.x * FIXED_INT_MULTIPLIER) / (delta.y / speed.y);
if (_walkDest.x < (_position.x / FIXED_INT_MULTIPLIER))
_delta.x = -_delta.x;
_walkCount = delta.y / speed.y;
}
}
// See if the new walk sequence is the same as the old. If it's a new one,
// we need to reset the frame number to zero so its animation starts at
// its beginning. Otherwise, if it's the same sequence, we can leave it
// as is, so it keeps the animation going at wherever it was up to
if (_sequenceNumber != _oldWalkSequence)
_frameNumber = 0;
_oldWalkSequence = _sequenceNumber;
if (!_walkCount)
gotoStand();
// If the sequence is the same as when we started, then Holmes was
// standing still and we're trying to re-stand him, so reset Holmes'
// rame to the old frame number from before it was reset to 0
if (_sequenceNumber == oldDirection)
_frameNumber = oldFrame;
}
void ScalpelPerson::walkToCoords(const Point32 &destPos, int destDir) {
Events &events = *_vm->_events;
People &people = *_vm->_people;
Scene &scene = *_vm->_scene;
Talk &talk = *_vm->_talk;
CursorId oldCursor = events.getCursor();
events.setCursor(WAIT);
_walkDest = Common::Point(destPos.x / FIXED_INT_MULTIPLIER + 10, destPos.y / FIXED_INT_MULTIPLIER);
people._allowWalkAbort = true;
goAllTheWay();
// Keep calling doBgAnim until the walk is done
do {
events.pollEventsAndWait();
scene.doBgAnim();
} while (!_vm->shouldQuit() && _walkCount);
if (!talk._talkToAbort) {
// Put character exactly on destination position, and set direction
_position = destPos;
_sequenceNumber = destDir;
gotoStand();
// Draw Holmes facing the new direction
scene.doBgAnim();
if (!talk._talkToAbort)
events.setCursor(oldCursor);
}
}
Common::Point ScalpelPerson::getSourcePoint() const {
return Common::Point(_position.x / FIXED_INT_MULTIPLIER + frameWidth() / 2,
_position.y / FIXED_INT_MULTIPLIER);
}
void ScalpelPerson::synchronize(Serializer &s) {
if (_walkCount)
gotoStand();
s.syncAsSint32LE(_position.x);
s.syncAsSint32LE(_position.y);
}
/*----------------------------------------------------------------*/
ScalpelPeople::ScalpelPeople(SherlockEngine *vm) : People(vm) {
_data.push_back(new ScalpelPerson());
}
void ScalpelPeople::setTalking(int speaker) {
Resources &res = *_vm->_res;
// If no speaker is specified, then we can exit immediately
if (speaker == -1)
return;
if (_portraitsOn) {
delete _talkPics;
Common::Path filename(Common::String::format("%s.vgs", _characters[speaker]._portrait));
_talkPics = new ImageFile(filename);
// Load portrait sequences
Common::SeekableReadStream *stream = res.load("sequence.txt");
stream->seek(speaker * MAX_FRAME);
int idx = 0;
do {
_portrait._sequences[idx] = stream->readByte();
++idx;
} while (idx < 2 || _portrait._sequences[idx - 2] || _portrait._sequences[idx - 1]);
delete stream;
_portrait._maxFrames = idx;
_portrait._frameNumber = 0;
_portrait._sequenceNumber = 0;
_portrait._images = _talkPics;
_portrait._imageFrame = &(*_talkPics)[0];
_portrait._position = Common::Point(_portraitSide, 10);
_portrait._delta = Common::Point(0, 0);
_portrait._oldPosition = Common::Point(0, 0);
_portrait._goto = Common::Point(0, 0);
_portrait._flags = 5;
_portrait._status = 0;
_portrait._misc = 0;
_portrait._allow = 0;
_portrait._type = ACTIVE_BG_SHAPE;
_portrait._name = " ";
_portrait._description = " ";
_portrait._examine = " ";
_portrait._walkCount = 0;
if (_holmesFlip || _speakerFlip) {
_portrait._flags |= 2;
_holmesFlip = false;
_speakerFlip = false;
}
if (_portraitSide == 20)
_portraitSide = 220;
else
_portraitSide = 20;
_portraitLoaded = true;
}
}
void ScalpelPeople::synchronize(Serializer &s) {
(*this)[HOLMES].synchronize(s);
s.syncAsSint16LE(_holmesQuotient);
s.syncAsByte(_holmesOn);
if (s.isLoading()) {
_savedPos = _data[HOLMES]->_position;
_savedPos._facing = _data[HOLMES]->_sequenceNumber;
}
}
void ScalpelPeople::setTalkSequence(int speaker, int sequenceNum) {
People &people = *_vm->_people;
Scene &scene = *_vm->_scene;
// If no speaker is specified, then nothing needs to be done
if (speaker == -1)
return;
if (speaker) {
int objNum = people.findSpeaker(speaker);
if (objNum != -1) {
Object &obj = scene._bgShapes[objNum];
if (obj._seqSize < MAX_TALK_SEQUENCES) {
warning("Tried to copy too many talk frames");
} else {
for (int idx = 0; idx < MAX_TALK_SEQUENCES; ++idx) {
obj._sequences[idx] = people._characters[speaker]._talkSequences[idx];
if (idx > 0 && !obj._sequences[idx] && !obj._sequences[idx - 1])
return;
obj._frameNumber = 0;
obj._sequenceNumber = 0;
}
}
}
}
}
bool ScalpelPeople::loadWalk() {
bool result = false;
if (_data[HOLMES]->_walkLoaded) {
return false;
} else {
if (!IS_3DO) {
_data[HOLMES]->_images = new ImageFile("walk.vgs");
} else {
// Load walk.anim on 3DO, which is a cel animation file
_data[HOLMES]->_images = new ImageFile3DO("walk.anim", kImageFile3DOType_CelAnimation);
}
_data[HOLMES]->setImageFrame();
_data[HOLMES]->_walkLoaded = true;
result = true;
}
_forceWalkReload = false;
return result;
}
const Common::Point ScalpelPeople::restrictToZone(int zoneId, const Common::Point &destPos) {
Scene &scene = *_vm->_scene;
Common::Point walkDest = destPos;
// The destination isn't in a zone
if (walkDest.x >= (SHERLOCK_SCREEN_WIDTH - 1))
walkDest.x = SHERLOCK_SCREEN_WIDTH - 2;
// Trace a line between the centroid of the found closest zone to
// the destination, to find the point at which the zone will be left
const Common::Rect &destRect = scene._zones[zoneId];
const Common::Point destCenter((destRect.left + destRect.right) / 2,
(destRect.top + destRect.bottom) / 2);
const Common::Point delta = walkDest - destCenter;
Point32 pt(destCenter.x * FIXED_INT_MULTIPLIER, destCenter.y * FIXED_INT_MULTIPLIER);
// Move along the line until the zone is left
do {
pt += delta;
} while (destRect.contains(pt.x / FIXED_INT_MULTIPLIER, pt.y / FIXED_INT_MULTIPLIER));
// Set the new walk destination to the last point that was in the
// zone just before it was left
return Common::Point((pt.x - delta.x * 2) / FIXED_INT_MULTIPLIER,
(pt.y - delta.y * 2) / FIXED_INT_MULTIPLIER);
}
void ScalpelPeople::setListenSequence(int speaker, int sequenceNum) {
People &people = *_vm->_people;
Scene &scene = *_vm->_scene;
// Don't bother doing anything if no specific speaker is specified
if (speaker == -1)
return;
if (speaker) {
int objNum = people.findSpeaker(speaker);
if (objNum != -1) {
Object &obj = scene._bgShapes[objNum];
if (obj._seqSize < MAX_TALK_SEQUENCES) {
warning("Tried to copy too few still frames");
} else {
for (uint idx = 0; idx < MAX_TALK_SEQUENCES; ++idx) {
obj._sequences[idx] = people._characters[speaker]._stillSequences[idx];
if (idx > 0 && !people._characters[speaker]._talkSequences[idx] &&
!people._characters[speaker]._talkSequences[idx - 1])
break;
}
obj._frameNumber = 0;
obj._seqTo = 0;
}
}
}
}
} // End of namespace Scalpel
} // End of namespace Sherlock

View File

@@ -0,0 +1,129 @@
/* 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/>.
*
*/
#ifndef SHERLOCK_SCALPEL_PEOPLE_H
#define SHERLOCK_SCALPEL_PEOPLE_H
#include "common/scummsys.h"
#include "sherlock/people.h"
namespace Sherlock {
class SherlockEngine;
namespace Scalpel {
// Animation sequence identifiers for characters
enum ScalpelSequences {
WALK_RIGHT = 0, WALK_DOWN = 1, WALK_LEFT = 2, WALK_UP = 3, STOP_LEFT = 4,
STOP_DOWN = 5, STOP_RIGHT = 6, STOP_UP = 7, WALK_UPRIGHT = 8,
WALK_DOWNRIGHT = 9, WALK_UPLEFT = 10, WALK_DOWNLEFT = 11,
STOP_UPRIGHT = 12, STOP_UPLEFT = 13, STOP_DOWNRIGHT = 14,
STOP_DOWNLEFT = 15, TALK_RIGHT = 6, TALK_LEFT = 4
};
class ScalpelPerson : public Person {
public:
ScalpelPerson() : Person() {}
~ScalpelPerson() override {}
/**
* Synchronize the data for a savegame
*/
virtual void synchronize(Serializer &s);
/**
* This adjusts the sprites position, as well as its animation sequence:
*/
void adjustSprite() override;
/**
* Bring a moving character to a standing position
*/
void gotoStand() override;
/**
* Set the variables for moving a character from one poisition to another
* in a straight line
*/
void setWalking() override;
/**
* Walk to the co-ordinates passed, and then face the given direction
*/
void walkToCoords(const Point32 &destPos, int destDir) override;
/**
* Get the source position for a character potentially affected by scaling
*/
Common::Point getSourcePoint() const override;
};
class ScalpelPeople : public People {
public:
ScalpelPeople(SherlockEngine *vm);
~ScalpelPeople() override {}
ScalpelPerson &operator[](PeopleId id) { return *(ScalpelPerson *)_data[id]; }
ScalpelPerson &operator[](int idx) { return *(ScalpelPerson *)_data[idx]; }
/**
* Setup the data for an animating speaker portrait at the top of the screen
*/
void setTalking(int speaker);
/**
* Synchronize the data for a savegame
*/
void synchronize(Serializer &s) override;
/**
* Change the sequence of the scene background object associated with the specified speaker.
*/
void setTalkSequence(int speaker, int sequenceNum = 1) override;
/**
* Restrict passed point to zone using Sherlock's positioning rules
*/
const Common::Point restrictToZone(int zoneId, const Common::Point &destPos) override;
/**
* Load the walking images for Sherlock
*/
bool loadWalk() override;
/**
* If the specified speaker is a background object, it will set it so that it uses
* the Listen Sequence (specified by the sequence number). If the current sequence
* has an Allow Talk Code in it, the _gotoSeq field will be set so that the object
* begins listening as soon as it hits the Allow Talk Code. If there is no Abort Code,
* the Listen Sequence will begin immediately.
* @param speaker Who is speaking
* @param sequenceNum Which listen sequence to use
*/
void setListenSequence(int speaker, int sequenceNum = 1) override;
};
} // End of namespace Scalpel
} // End of namespace Sherlock
#endif

View File

@@ -0,0 +1,308 @@
/* 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 "sherlock/scalpel/scalpel_fixed_text.h"
#include "sherlock/scalpel/scalpel_saveload.h"
#include "sherlock/scalpel/scalpel_screen.h"
#include "sherlock/scalpel/scalpel.h"
#include "backends/keymapper/keymapper.h"
namespace Sherlock {
namespace Scalpel {
const int ENV_POINTS[6][3] = {
{ 41, 80, 61 }, // Exit
{ 81, 120, 101 }, // Load
{ 121, 160, 141 }, // Save
{ 161, 200, 181 }, // Up
{ 201, 240, 221 }, // Down
{ 241, 280, 261 } // Quit
};
/*----------------------------------------------------------------*/
ScalpelSaveManager::ScalpelSaveManager(SherlockEngine *vm, const Common::String &target) :
SaveManager(vm, target), _envMode(SAVEMODE_NONE) {
_fixedTextExit = FIXED(LoadSave_Exit);
_fixedTextLoad = FIXED(LoadSave_Load);
_fixedTextSave = FIXED(LoadSave_Save);
_fixedTextUp = FIXED(LoadSave_Up);
_fixedTextDown = FIXED(LoadSave_Down);
_fixedTextQuit = FIXED(LoadSave_Quit);
_actionsIndexed[0] = kActionScalpelFilesExit;
_actionsIndexed[1] = kActionScalpelFilesLoad;
_actionsIndexed[2] = kActionScalpelFilesSave;
_actionsIndexed[3] = kActionScalpelScrollUp;
_actionsIndexed[4] = kActionScalpelScrollDown;
_actionsIndexed[5] = kActionScalpelFilesQuit;
_actionsIndexed[6] = kActionScalpelFiles1;
_actionsIndexed[7] = kActionScalpelFiles2;
_actionsIndexed[8] = kActionScalpelFiles3;
_actionsIndexed[9] = kActionScalpelFiles4;
_actionsIndexed[10] = kActionScalpelFiles5;
_actionsIndexed[11] = kActionScalpelFiles6;
_actionsIndexed[12] = kActionScalpelFiles7;
_actionsIndexed[13] = kActionScalpelFiles8;
_actionsIndexed[14] = kActionScalpelFiles9;
_fixedTextQuitGameQuestion = FIXED(QuitGame_Question);
_fixedTextQuitGameYes = FIXED(QuitGame_Yes);
_fixedTextQuitGameNo = FIXED(QuitGame_No);
}
int ScalpelSaveManager::identifyUserButton(Common::CustomEventType action) {
for (uint16 actionNr = 0; actionNr < ARRAYSIZE(_actionsIndexed); actionNr++) {
if (action == _actionsIndexed[actionNr])
return actionNr;
}
return -1;
}
void ScalpelSaveManager::drawInterface() {
ScalpelScreen &screen = *(ScalpelScreen *)_vm->_screen;
UserInterface &ui = *_vm->_ui;
// Create a list of savegame slots
createSavegameList();
screen._backBuffer1.fillRect(Common::Rect(0, CONTROLS_Y, SHERLOCK_SCREEN_WIDTH, CONTROLS_Y + 10), BORDER_COLOR);
screen._backBuffer1.fillRect(Common::Rect(0, CONTROLS_Y + 10, 2, SHERLOCK_SCREEN_HEIGHT), BORDER_COLOR);
screen._backBuffer1.fillRect(Common::Rect(318, CONTROLS_Y + 10, SHERLOCK_SCREEN_WIDTH, SHERLOCK_SCREEN_HEIGHT), BORDER_COLOR);
screen._backBuffer1.fillRect(Common::Rect(0, 199, SHERLOCK_SCREEN_WIDTH, SHERLOCK_SCREEN_HEIGHT), BORDER_COLOR);
screen._backBuffer1.fillRect(Common::Rect(2, CONTROLS_Y + 10, SHERLOCK_SCREEN_WIDTH - 2, SHERLOCK_SCREEN_HEIGHT - 1), INV_BACKGROUND);
screen.makeButton(Common::Rect(ENV_POINTS[0][0], CONTROLS_Y, ENV_POINTS[0][1], CONTROLS_Y + 10),
ENV_POINTS[0][2], _fixedTextExit);
screen.makeButton(Common::Rect(ENV_POINTS[1][0], CONTROLS_Y, ENV_POINTS[1][1], CONTROLS_Y + 10),
ENV_POINTS[1][2], _fixedTextLoad);
screen.makeButton(Common::Rect(ENV_POINTS[2][0], CONTROLS_Y, ENV_POINTS[2][1], CONTROLS_Y + 10),
ENV_POINTS[2][2], _fixedTextSave);
screen.makeButton(Common::Rect(ENV_POINTS[3][0], CONTROLS_Y, ENV_POINTS[3][1], CONTROLS_Y + 10),
ENV_POINTS[3][2], _fixedTextUp);
screen.makeButton(Common::Rect(ENV_POINTS[4][0], CONTROLS_Y, ENV_POINTS[4][1], CONTROLS_Y + 10),
ENV_POINTS[4][2], _fixedTextDown);
screen.makeButton(Common::Rect(ENV_POINTS[5][0], CONTROLS_Y, ENV_POINTS[5][1], CONTROLS_Y + 10),
ENV_POINTS[5][2], _fixedTextQuit);
if (!_savegameIndex)
screen.buttonPrint(Common::Point(ENV_POINTS[3][2], CONTROLS_Y), COMMAND_NULL, 0, _fixedTextUp);
if (_savegameIndex == MAX_SAVEGAME_SLOTS - ONSCREEN_FILES_COUNT)
screen.buttonPrint(Common::Point(ENV_POINTS[4][2], CONTROLS_Y), COMMAND_NULL, 0, _fixedTextDown);
for (int idx = _savegameIndex; idx < _savegameIndex + ONSCREEN_FILES_COUNT; ++idx) {
screen.gPrint(Common::Point(6, CONTROLS_Y + 11 + (idx - _savegameIndex) * 10),
INV_FOREGROUND, "%d.", idx + 1);
screen.gPrint(Common::Point(24, CONTROLS_Y + 11 + (idx - _savegameIndex) * 10),
INV_FOREGROUND, "%s", _savegames[idx].c_str());
}
if (!ui._slideWindows) {
screen.slamRect(Common::Rect(0, CONTROLS_Y, SHERLOCK_SCREEN_WIDTH, SHERLOCK_SCREEN_HEIGHT));
} else {
ui.summonWindow();
}
_envMode = SAVEMODE_NONE;
}
int ScalpelSaveManager::getHighlightedButton() const {
Common::Point pt = _vm->_events->mousePos();
for (int idx = 0; idx < 6; ++idx) {
if (pt.x > ENV_POINTS[idx][0] && pt.x < ENV_POINTS[idx][1] && pt.y > CONTROLS_Y
&& pt.y < (CONTROLS_Y + 10))
return idx;
}
return -1;
}
void ScalpelSaveManager::highlightButtons(int btnIndex) {
ScalpelScreen &screen = *(ScalpelScreen *)_vm->_screen;
byte color = (btnIndex == 0) ? COMMAND_HIGHLIGHTED : COMMAND_FOREGROUND;
screen.buttonPrint(Common::Point(ENV_POINTS[0][2], CONTROLS_Y), color, 1, _fixedTextExit);
if ((btnIndex == 1) || ((_envMode == SAVEMODE_LOAD) && (btnIndex != 2)))
screen.buttonPrint(Common::Point(ENV_POINTS[1][2], CONTROLS_Y), COMMAND_HIGHLIGHTED, true, _fixedTextLoad);
else
screen.buttonPrint(Common::Point(ENV_POINTS[1][2], CONTROLS_Y), COMMAND_FOREGROUND, true, _fixedTextLoad);
if ((btnIndex == 2) || ((_envMode == SAVEMODE_SAVE) && (btnIndex != 1)))
screen.buttonPrint(Common::Point(ENV_POINTS[2][2], CONTROLS_Y), COMMAND_HIGHLIGHTED, true, _fixedTextSave);
else
screen.buttonPrint(Common::Point(ENV_POINTS[2][2], CONTROLS_Y), COMMAND_FOREGROUND, true, _fixedTextSave);
if (btnIndex == 3 && _savegameIndex)
screen.buttonPrint(Common::Point(ENV_POINTS[3][2], CONTROLS_Y), COMMAND_HIGHLIGHTED, true, _fixedTextUp);
else
if (_savegameIndex)
screen.buttonPrint(Common::Point(ENV_POINTS[3][2], CONTROLS_Y), COMMAND_FOREGROUND, true, _fixedTextUp);
if ((btnIndex == 4) && (_savegameIndex < MAX_SAVEGAME_SLOTS - 5))
screen.buttonPrint(Common::Point(ENV_POINTS[4][2], CONTROLS_Y), COMMAND_HIGHLIGHTED, true, _fixedTextDown);
else if (_savegameIndex < (MAX_SAVEGAME_SLOTS - 5))
screen.buttonPrint(Common::Point(ENV_POINTS[4][2], CONTROLS_Y), COMMAND_FOREGROUND, true, _fixedTextDown);
color = (btnIndex == 5) ? COMMAND_HIGHLIGHTED : COMMAND_FOREGROUND;
screen.buttonPrint(Common::Point(ENV_POINTS[5][2], CONTROLS_Y), color, 1, _fixedTextQuit);
}
bool ScalpelSaveManager::checkGameOnScreen(int slot) {
ScalpelScreen &screen = *(ScalpelScreen *)_vm->_screen;
// Check if it's already on-screen
if (slot != -1 && (slot < _savegameIndex || slot >= (_savegameIndex + ONSCREEN_FILES_COUNT))) {
_savegameIndex = slot;
screen._backBuffer1.fillRect(Common::Rect(3, CONTROLS_Y + 11, SHERLOCK_SCREEN_WIDTH - 2,
SHERLOCK_SCREEN_HEIGHT - 1), INV_BACKGROUND);
for (int idx = _savegameIndex; idx < (_savegameIndex + 5); ++idx) {
screen.gPrint(Common::Point(6, CONTROLS_Y + 11 + (idx - _savegameIndex) * 10),
INV_FOREGROUND, "%d.", idx + 1);
screen.gPrint(Common::Point(24, CONTROLS_Y + 11 + (idx - _savegameIndex) * 10),
INV_FOREGROUND, "%s", _savegames[idx].c_str());
}
screen.slamRect(Common::Rect(3, CONTROLS_Y + 11, 318, SHERLOCK_SCREEN_HEIGHT));
byte color = !_savegameIndex ? COMMAND_NULL : COMMAND_FOREGROUND;
screen.buttonPrint(Common::Point(ENV_POINTS[3][2], CONTROLS_Y), color, 1, _fixedTextUp);
color = (_savegameIndex == (MAX_SAVEGAME_SLOTS - 5)) ? COMMAND_NULL : COMMAND_FOREGROUND;
screen.buttonPrint(Common::Point(ENV_POINTS[4][2], CONTROLS_Y), color, 1, _fixedTextDown);
return true;
}
return false;
}
bool ScalpelSaveManager::promptForDescription(int slot) {
Events &events = *_vm->_events;
Scene &scene = *_vm->_scene;
ScalpelScreen &screen = *(ScalpelScreen *)_vm->_screen;
Talk &talk = *_vm->_talk;
int xp, yp;
bool flag = false;
screen.buttonPrint(Common::Point(ENV_POINTS[0][2], CONTROLS_Y), COMMAND_NULL, true, _fixedTextExit);
screen.buttonPrint(Common::Point(ENV_POINTS[1][2], CONTROLS_Y), COMMAND_NULL, true, _fixedTextLoad);
screen.buttonPrint(Common::Point(ENV_POINTS[2][2], CONTROLS_Y), COMMAND_NULL, true, _fixedTextSave);
screen.buttonPrint(Common::Point(ENV_POINTS[3][2], CONTROLS_Y), COMMAND_NULL, true, _fixedTextUp);
screen.buttonPrint(Common::Point(ENV_POINTS[4][2], CONTROLS_Y), COMMAND_NULL, true, _fixedTextDown);
screen.buttonPrint(Common::Point(ENV_POINTS[5][2], CONTROLS_Y), COMMAND_NULL, true, _fixedTextQuit);
Common::String saveName = _savegames[slot];
if (isSlotEmpty(slot)) {
// It's an empty slot, so start off with an empty save name
saveName = "";
yp = CONTROLS_Y + 12 + (slot - _savegameIndex) * 10;
screen.vgaBar(Common::Rect(24, yp, 85, yp + 9), INV_BACKGROUND);
}
screen.print(Common::Point(6, CONTROLS_Y + 12 + (slot - _savegameIndex) * 10), TALK_FOREGROUND, "%d.", slot + 1);
screen.print(Common::Point(24, CONTROLS_Y + 12 + (slot - _savegameIndex) * 10), TALK_FOREGROUND, "%s", saveName.c_str());
xp = 24 + screen.stringWidth(saveName);
yp = CONTROLS_Y + 12 + (slot - _savegameIndex) * 10;
int done = 0;
Common::Keymapper *keymapper = _vm->getEventManager()->getKeymapper();
keymapper->disableAllGameKeymaps();
do {
while (!_vm->shouldQuit() && !events.kbHit()) {
scene.doBgAnim();
if (talk._talkToAbort)
return false;
// Allow event processing
events.pollEventsAndWait();
events.setButtonState();
flag = !flag;
if (flag)
screen.vgaBar(Common::Rect(xp, yp - 1, xp + 8, yp + 9), INV_FOREGROUND);
else
screen.vgaBar(Common::Rect(xp, yp - 1, xp + 8, yp + 9), INV_BACKGROUND);
}
if (_vm->shouldQuit())
return false;
// Get the next keypress
Common::KeyState keyState = events.getKey();
if (keyState.keycode == Common::KEYCODE_BACKSPACE && saveName.size() > 0) {
// Delete character of save name
screen.vgaBar(Common::Rect(xp - screen.charWidth(saveName.lastChar()), yp - 1,
xp + 8, yp + 9), INV_BACKGROUND);
xp -= screen.charWidth(saveName.lastChar());
screen.vgaBar(Common::Rect(xp, yp - 1, xp + 8, yp + 9), INV_FOREGROUND);
saveName.deleteLastChar();
} else if (keyState.keycode == Common::KEYCODE_RETURN && saveName.compareToIgnoreCase(EMPTY_SAVEGAME_SLOT)) {
done = 1;
} else if (keyState.keycode == Common::KEYCODE_ESCAPE) {
screen.vgaBar(Common::Rect(xp, yp - 1, xp + 8, yp + 9), INV_BACKGROUND);
done = -1;
} else if (keyState.ascii >= ' ' && keyState.ascii <= 'z' && saveName.size() < 50
&& (xp + screen.charWidth(keyState.ascii)) < 308) {
char c = (char)keyState.ascii;
screen.vgaBar(Common::Rect(xp, yp - 1, xp + 8, yp + 9), INV_BACKGROUND);
screen.print(Common::Point(xp, yp), TALK_FOREGROUND, "%c", c);
xp += screen.charWidth(c);
screen.vgaBar(Common::Rect(xp, yp - 1, xp + 8, yp + 9), INV_FOREGROUND);
saveName += c;
}
} while (!done);
keymapper->getKeymap("sherlock-default")->setEnabled(true);
keymapper->getKeymap("scalpel-quit")->setEnabled(true);
if (done == 1) {
// Enter key perssed
_savegames[slot] = saveName;
keymapper->getKeymap("scalpel")->setEnabled(true);
} else {
done = 0;
_envMode = SAVEMODE_NONE;
highlightButtons(-1);
keymapper->getKeymap("scalpel-files")->setEnabled(true);
keymapper->getKeymap("scalpel-scroll")->setEnabled(true);
}
return done == 1;
}
} // End of namespace Scalpel
} // End of namespace Sherlock

View File

@@ -0,0 +1,91 @@
/* 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/>.
*
*/
#ifndef SHERLOCK_SCALPEL_SAVELOAD_H
#define SHERLOCK_SCALPEL_SAVELOAD_H
#include "sherlock/saveload.h"
#include "common/events.h"
namespace Sherlock {
namespace Scalpel {
extern const int ENV_POINTS[6][3];
class ScalpelSaveManager: public SaveManager {
public:
SaveMode _envMode;
Common::String _fixedTextExit;
Common::String _fixedTextLoad;
Common::String _fixedTextSave;
Common::String _fixedTextUp;
Common::String _fixedTextDown;
Common::String _fixedTextQuit;
Common::CustomEventType _actionsIndexed[15];
Common::String _fixedTextQuitGameQuestion;
Common::String _fixedTextQuitGameYes;
Common::String _fixedTextQuitGameNo;
public:
ScalpelSaveManager(SherlockEngine *vm, const Common::String &target);
~ScalpelSaveManager() override {}
/**
* Shows the in-game dialog interface for loading and saving games
*/
void drawInterface();
/**
* Return the index of the button the mouse is over, if any
*/
int getHighlightedButton() const;
/**
* Handle highlighting buttons
*/
void highlightButtons(int btnIndex);
/**
* Make sure that the selected savegame is on-screen
*/
bool checkGameOnScreen(int slot);
/**
* Prompts the user to enter a description in a given slot
*/
bool promptForDescription(int slot);
/**
* Identifies a button number according to the action, that the user pressed
*/
int identifyUserButton(Common::CustomEventType action);
};
} // End of namespace Scalpel
} // End of namespace Sherlock
#endif

View File

@@ -0,0 +1,757 @@
/* 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 "sherlock/scalpel/scalpel_scene.h"
#include "sherlock/scalpel/scalpel_map.h"
#include "sherlock/scalpel/scalpel_people.h"
#include "sherlock/scalpel/scalpel_user_interface.h"
#include "sherlock/scalpel/scalpel.h"
#include "sherlock/events.h"
#include "sherlock/people.h"
#include "sherlock/screen.h"
#include "sherlock/sherlock.h"
namespace Sherlock {
namespace Scalpel {
const int FS_TRANS[8] = {
STOP_UP, STOP_UPRIGHT, STOP_RIGHT, STOP_DOWNRIGHT, STOP_DOWN, STOP_DOWNLEFT, STOP_LEFT, STOP_UPLEFT
};
/*----------------------------------------------------------------*/
ScalpelScene::~ScalpelScene() {
for (uint idx = 0; idx < _canimShapes.size(); ++idx)
delete _canimShapes[idx];
}
bool ScalpelScene::loadScene(const Common::Path &filename) {
ScalpelMap &map = *(ScalpelMap *)_vm->_map;
bool result = Scene::loadScene(filename);
if (!_vm->isDemo()) {
// Reset the previous map location and position on overhead map
map._oldCharPoint = _currentScene;
map._overPos.x = (map[_currentScene].x - 6) * FIXED_INT_MULTIPLIER;
map._overPos.y = (map[_currentScene].y + 9) * FIXED_INT_MULTIPLIER;
}
return result;
}
void ScalpelScene::drawAllShapes() {
People &people = *_vm->_people;
Screen &screen = *_vm->_screen;
// Restrict drawing window
screen.setDisplayBounds(Common::Rect(0, 0, SHERLOCK_SCREEN_WIDTH, SHERLOCK_SCENE_HEIGHT));
// Draw all active shapes which are behind the person
for (uint idx = 0; idx < _bgShapes.size(); ++idx) {
if (_bgShapes[idx]._type == ACTIVE_BG_SHAPE && _bgShapes[idx]._misc == BEHIND)
screen.getBackBuffer()->SHtransBlitFrom(*_bgShapes[idx]._imageFrame, _bgShapes[idx]._position, _bgShapes[idx]._flags & OBJ_FLIPPED);
}
// Draw all canimations which are behind the person
for (uint idx = 0; idx < _canimShapes.size(); ++idx) {
if (_canimShapes[idx]->_type == ACTIVE_BG_SHAPE && _canimShapes[idx]->_misc == BEHIND)
screen.getBackBuffer()->SHtransBlitFrom(*_canimShapes[idx]->_imageFrame,
_canimShapes[idx]->_position, _canimShapes[idx]->_flags & OBJ_FLIPPED);
}
// Draw all active shapes which are normal and behind the person
for (uint idx = 0; idx < _bgShapes.size(); ++idx) {
if (_bgShapes[idx]._type == ACTIVE_BG_SHAPE && _bgShapes[idx]._misc == NORMAL_BEHIND)
screen.getBackBuffer()->SHtransBlitFrom(*_bgShapes[idx]._imageFrame, _bgShapes[idx]._position, _bgShapes[idx]._flags & OBJ_FLIPPED);
}
// Draw all canimations which are normal and behind the person
for (uint idx = 0; idx < _canimShapes.size(); ++idx) {
if (_canimShapes[idx]->_type == ACTIVE_BG_SHAPE && _canimShapes[idx]->_misc == NORMAL_BEHIND)
screen.getBackBuffer()->SHtransBlitFrom(*_canimShapes[idx]->_imageFrame, _canimShapes[idx]->_position,
_canimShapes[idx]->_flags & OBJ_FLIPPED);
}
// Draw any active characters
for (int idx = 0; idx < MAX_CHARACTERS; ++idx) {
Person &p = people[idx];
if (p._type == CHARACTER && p._walkLoaded) {
bool flipped = IS_SERRATED_SCALPEL && (
p._sequenceNumber == WALK_LEFT || p._sequenceNumber == STOP_LEFT ||
p._sequenceNumber == WALK_UPLEFT || p._sequenceNumber == STOP_UPLEFT ||
p._sequenceNumber == WALK_DOWNRIGHT || p._sequenceNumber == STOP_DOWNRIGHT);
screen.getBackBuffer()->SHtransBlitFrom(*p._imageFrame, Common::Point(p._position.x / FIXED_INT_MULTIPLIER,
p._position.y / FIXED_INT_MULTIPLIER - p.frameHeight()), flipped);
}
}
// Draw all static and active shapes that are NORMAL and are in front of the player
for (uint idx = 0; idx < _bgShapes.size(); ++idx) {
if ((_bgShapes[idx]._type == ACTIVE_BG_SHAPE || _bgShapes[idx]._type == STATIC_BG_SHAPE) &&
_bgShapes[idx]._misc == NORMAL_FORWARD)
screen.getBackBuffer()->SHtransBlitFrom(*_bgShapes[idx]._imageFrame, _bgShapes[idx]._position,
_bgShapes[idx]._flags & OBJ_FLIPPED);
}
// Draw all static and active canimations that are NORMAL and are in front of the player
for (uint idx = 0; idx < _canimShapes.size(); ++idx) {
if ((_canimShapes[idx]->_type == ACTIVE_BG_SHAPE || _canimShapes[idx]->_type == STATIC_BG_SHAPE) &&
_canimShapes[idx]->_misc == NORMAL_FORWARD)
screen.getBackBuffer()->SHtransBlitFrom(*_canimShapes[idx]->_imageFrame, _canimShapes[idx]->_position,
_canimShapes[idx]->_flags & OBJ_FLIPPED);
}
// Draw all static and active shapes that are FORWARD
for (uint idx = 0; idx < _bgShapes.size(); ++idx) {
_bgShapes[idx]._oldPosition = _bgShapes[idx]._position;
_bgShapes[idx]._oldSize = Common::Point(_bgShapes[idx].frameWidth(),
_bgShapes[idx].frameHeight());
if ((_bgShapes[idx]._type == ACTIVE_BG_SHAPE || _bgShapes[idx]._type == STATIC_BG_SHAPE) &&
_bgShapes[idx]._misc == FORWARD)
screen.getBackBuffer()->SHtransBlitFrom(*_bgShapes[idx]._imageFrame, _bgShapes[idx]._position,
_bgShapes[idx]._flags & OBJ_FLIPPED);
}
// Draw all static and active canimations that are forward
for (uint idx = 0; idx < _canimShapes.size(); ++idx) {
if ((_canimShapes[idx]->_type == ACTIVE_BG_SHAPE || _canimShapes[idx]->_type == STATIC_BG_SHAPE) &&
_canimShapes[idx]->_misc == FORWARD)
screen.getBackBuffer()->SHtransBlitFrom(*_canimShapes[idx]->_imageFrame, _canimShapes[idx]->_position,
_canimShapes[idx]->_flags & OBJ_FLIPPED);
}
screen.resetDisplayBounds();
}
void ScalpelScene::checkBgShapes() {
People &people = *_vm->_people;
Person &holmes = people[HOLMES];
Common::Point pt(holmes._position.x / FIXED_INT_MULTIPLIER, holmes._position.y / FIXED_INT_MULTIPLIER);
// Call the base scene method to handle bg shapes
Scene::checkBgShapes();
// Iterate through the canim list
for (uint idx = 0; idx < _canimShapes.size(); ++idx) {
Object &obj = *_canimShapes[idx];
if (obj._type == STATIC_BG_SHAPE || obj._type == ACTIVE_BG_SHAPE) {
if ((obj._flags & 5) == 1) {
obj._misc = (pt.y < (obj._position.y + obj._imageFrame->_frame.h - 1)) ?
NORMAL_FORWARD : NORMAL_BEHIND;
} else if (!(obj._flags & 1)) {
obj._misc = BEHIND;
} else if (obj._flags & 4) {
obj._misc = FORWARD;
}
}
}
}
void ScalpelScene::doBgAnimCheckCursor() {
Inventory &inv = *_vm->_inventory;
Events &events = *_vm->_events;
UserInterface &ui = *_vm->_ui;
Common::Point mousePos = events.mousePos();
events.animateCursorIfNeeded();
if (ui._menuMode == LOOK_MODE) {
if (mousePos.y > CONTROLS_Y1)
events.setCursor(ARROW);
else if (mousePos.y < CONTROLS_Y)
events.setCursor(MAGNIFY);
}
// Check for setting magnifying glass cursor
if (ui._menuMode == INV_MODE || ui._menuMode == USE_MODE || ui._menuMode == GIVE_MODE) {
if (inv._invMode == INVMODE_LOOK) {
// Only show Magnifying glass cursor if it's not on the inventory command line
if (mousePos.y < CONTROLS_Y || mousePos.y >(CONTROLS_Y1 + 13))
events.setCursor(MAGNIFY);
else
events.setCursor(ARROW);
} else {
events.setCursor(ARROW);
}
}
}
void ScalpelScene::doBgAnim() {
ScalpelEngine &vm = *((ScalpelEngine *)_vm);
Events &events = *_vm->_events;
People &people = *_vm->_people;
Screen &screen = *_vm->_screen;
Talk &talk = *_vm->_talk;
doBgAnimCheckCursor();
screen.setDisplayBounds(Common::Rect(0, 0, SHERLOCK_SCREEN_WIDTH, SHERLOCK_SCENE_HEIGHT));
talk._talkToAbort = false;
if (_restoreFlag) {
for (int idx = 0; idx < MAX_CHARACTERS; ++idx) {
if (people[idx]._type == CHARACTER)
people[idx].checkSprite();
}
for (uint idx = 0; idx < _bgShapes.size(); ++idx) {
if (_bgShapes[idx]._type == ACTIVE_BG_SHAPE)
_bgShapes[idx].checkObject();
}
if (people._portraitLoaded && people._portrait._type == ACTIVE_BG_SHAPE)
people._portrait.checkObject();
for (uint idx = 0; idx < _canimShapes.size(); ++idx) {
if (_canimShapes[idx]->_type != INVALID && _canimShapes[idx]->_type != REMOVE)
_canimShapes[idx]->checkObject();
}
if (_currentScene == DRAWING_ROOM)
vm.eraseBrumwellMirror();
// Restore the back buffer from the back buffer 2 in the changed area
Common::Rect bounds(people[HOLMES]._oldPosition.x, people[HOLMES]._oldPosition.y,
people[HOLMES]._oldPosition.x + people[HOLMES]._oldSize.x,
people[HOLMES]._oldPosition.y + people[HOLMES]._oldSize.y);
Common::Point pt(bounds.left, bounds.top);
if (people[HOLMES]._type == CHARACTER)
screen.restoreBackground(bounds);
else if (people[HOLMES]._type == REMOVE)
screen.getBackBuffer()->SHblitFrom(screen._backBuffer2, pt, bounds);
for (uint idx = 0; idx < _bgShapes.size(); ++idx) {
Object &o = _bgShapes[idx];
if (o._type == ACTIVE_BG_SHAPE || o._type == HIDE_SHAPE || o._type == REMOVE)
screen.restoreBackground(o.getOldBounds());
}
if (people._portraitLoaded)
screen.restoreBackground(Common::Rect(
people._portrait._oldPosition.x, people._portrait._oldPosition.y,
people._portrait._oldPosition.x + people._portrait._oldSize.x,
people._portrait._oldPosition.y + people._portrait._oldSize.y
));
for (uint idx = 0; idx < _bgShapes.size(); ++idx) {
Object &o = _bgShapes[idx];
if (o._type == NO_SHAPE && ((o._flags & OBJ_BEHIND) == 0)) {
// Restore screen area
screen.getBackBuffer()->SHblitFrom(screen._backBuffer2, o._position,
Common::Rect(o._position.x, o._position.y,
o._position.x + o._noShapeSize.x, o._position.y + o._noShapeSize.y));
o._oldPosition = o._position;
o._oldSize = o._noShapeSize;
}
}
for (uint idx = 0; idx < _canimShapes.size(); ++idx) {
Object &o = *_canimShapes[idx];
if (o._type == ACTIVE_BG_SHAPE || o._type == HIDE_SHAPE || o._type == REMOVE)
screen.restoreBackground(Common::Rect(o._oldPosition.x, o._oldPosition.y,
o._oldPosition.x + o._oldSize.x, o._oldPosition.y + o._oldSize.y));
}
}
//
// Update the background objects and canimations
//
for (uint idx = 0; idx < _bgShapes.size(); ++idx) {
Object &o = _bgShapes[idx];
if (o._type == ACTIVE_BG_SHAPE || o._type == NO_SHAPE)
o.adjustObject();
}
if (people._portraitLoaded && people._portrait._type == ACTIVE_BG_SHAPE)
people._portrait.adjustObject();
for (uint idx = 0; idx < _canimShapes.size(); ++idx) {
if (_canimShapes[idx]->_type != INVALID)
_canimShapes[idx]->adjustObject();
}
if (people[HOLMES]._type == CHARACTER && people._holmesOn)
people[HOLMES].adjustSprite();
// Flag the bg shapes which need to be redrawn
checkBgShapes();
if (_currentScene == DRAWING_ROOM)
vm.doBrumwellMirror();
// Draw all active shapes which are behind the person
for (uint idx = 0; idx < _bgShapes.size(); ++idx) {
Object &o = _bgShapes[idx];
if (o._type == ACTIVE_BG_SHAPE && o._misc == BEHIND)
screen.getBackBuffer()->SHtransBlitFrom(*o._imageFrame, o._position, o._flags & OBJ_FLIPPED);
}
// Draw all canimations which are behind the person
for (uint idx = 0; idx < _canimShapes.size(); ++idx) {
Object &o = *_canimShapes[idx];
if (o._type == ACTIVE_BG_SHAPE && o._misc == BEHIND) {
screen.getBackBuffer()->SHtransBlitFrom(*o._imageFrame, o._position, o._flags & OBJ_FLIPPED);
}
}
// Draw all active shapes which are HAPPEN and behind the person
for (uint idx = 0; idx < _bgShapes.size(); ++idx) {
Object &o = _bgShapes[idx];
if (o._type == ACTIVE_BG_SHAPE && o._misc == NORMAL_BEHIND)
screen.getBackBuffer()->SHtransBlitFrom(*o._imageFrame, o._position, o._flags & OBJ_FLIPPED);
}
// Draw all canimations which are NORMAL and behind the person
for (uint idx = 0; idx < _canimShapes.size(); ++idx) {
Object &o = *_canimShapes[idx];
if (o._type == ACTIVE_BG_SHAPE && o._misc == NORMAL_BEHIND) {
screen.getBackBuffer()->SHtransBlitFrom(*o._imageFrame, o._position, o._flags & OBJ_FLIPPED);
}
}
// Draw the player if he's active and his walk has been loaded into memory
if (people[HOLMES]._type == CHARACTER && people[HOLMES]._walkLoaded && people._holmesOn) {
// If Holmes is too far to the right, move him back so he's on-screen
int xRight = SHERLOCK_SCREEN_WIDTH - 2 - people[HOLMES]._imageFrame->_frame.w;
int tempX = MIN(people[HOLMES]._position.x / FIXED_INT_MULTIPLIER, xRight);
bool flipped = people[HOLMES]._sequenceNumber == WALK_LEFT || people[HOLMES]._sequenceNumber == STOP_LEFT ||
people[HOLMES]._sequenceNumber == WALK_UPLEFT || people[HOLMES]._sequenceNumber == STOP_UPLEFT ||
people[HOLMES]._sequenceNumber == WALK_DOWNRIGHT || people[HOLMES]._sequenceNumber == STOP_DOWNRIGHT;
screen.getBackBuffer()->SHtransBlitFrom(*people[HOLMES]._imageFrame,
Common::Point(tempX, people[HOLMES]._position.y / FIXED_INT_MULTIPLIER - people[HOLMES]._imageFrame->_frame.h), flipped);
}
// Draw all static and active shapes are NORMAL and are in front of the person
for (uint idx = 0; idx < _bgShapes.size(); ++idx) {
Object &o = _bgShapes[idx];
if ((o._type == ACTIVE_BG_SHAPE || o._type == STATIC_BG_SHAPE) && o._misc == NORMAL_FORWARD)
screen.getBackBuffer()->SHtransBlitFrom(*o._imageFrame, o._position, o._flags & OBJ_FLIPPED);
}
// Draw all static and active canimations that are NORMAL and are in front of the person
for (uint idx = 0; idx < _canimShapes.size(); ++idx) {
Object &o = *_canimShapes[idx];
if ((o._type == ACTIVE_BG_SHAPE || o._type == STATIC_BG_SHAPE) && o._misc == NORMAL_FORWARD) {
screen.getBackBuffer()->SHtransBlitFrom(*o._imageFrame, o._position, o._flags & OBJ_FLIPPED);
}
}
// Draw all static and active shapes that are in front of the person
for (uint idx = 0; idx < _bgShapes.size(); ++idx) {
Object &o = _bgShapes[idx];
if ((o._type == ACTIVE_BG_SHAPE || o._type == STATIC_BG_SHAPE) && o._misc == FORWARD)
screen.getBackBuffer()->SHtransBlitFrom(*o._imageFrame, o._position, o._flags & OBJ_FLIPPED);
}
// Draw any active portrait
if (people._portraitLoaded && people._portrait._type == ACTIVE_BG_SHAPE)
screen.getBackBuffer()->SHtransBlitFrom(*people._portrait._imageFrame,
people._portrait._position, people._portrait._flags & OBJ_FLIPPED);
// Draw all static and active canimations that are in front of the person
for (uint idx = 0; idx < _canimShapes.size(); ++idx) {
Object &o = *_canimShapes[idx];
if ((o._type == ACTIVE_BG_SHAPE || o._type == STATIC_BG_SHAPE) && o._misc == FORWARD) {
screen.getBackBuffer()->SHtransBlitFrom(*o._imageFrame, o._position, o._flags & OBJ_FLIPPED);
}
}
// Draw all NO_SHAPE shapes which have flag bit 0 clear
for (uint idx = 0; idx < _bgShapes.size(); ++idx) {
Object &o = _bgShapes[idx];
if (o._type == NO_SHAPE && (o._flags & OBJ_BEHIND) == 0)
screen.getBackBuffer()->SHtransBlitFrom(*o._imageFrame, o._position, o._flags & OBJ_FLIPPED);
}
// Bring the newly built picture to the screen
if (_animating == 2) {
_animating = 0;
screen.slamRect(Common::Rect(0, 0, SHERLOCK_SCREEN_WIDTH, SHERLOCK_SCENE_HEIGHT));
} else {
if (people[HOLMES]._type != INVALID && ((_goToScene == -1 || _canimShapes.empty()))) {
if (people[HOLMES]._type == REMOVE) {
screen.slamRect(Common::Rect(
people[HOLMES]._oldPosition.x, people[HOLMES]._oldPosition.y,
people[HOLMES]._oldPosition.x + people[HOLMES]._oldSize.x,
people[HOLMES]._oldPosition.y + people[HOLMES]._oldSize.y
));
people[HOLMES]._type = INVALID;
} else {
screen.flushImage(people[HOLMES]._imageFrame,
Common::Point(people[HOLMES]._position.x / FIXED_INT_MULTIPLIER,
people[HOLMES]._position.y / FIXED_INT_MULTIPLIER - people[HOLMES].frameHeight()),
&people[HOLMES]._oldPosition.x, &people[HOLMES]._oldPosition.y,
&people[HOLMES]._oldSize.x, &people[HOLMES]._oldSize.y);
}
}
if (_currentScene == DRAWING_ROOM)
vm.flushBrumwellMirror();
for (uint idx = 0; idx < _bgShapes.size(); ++idx) {
Object &o = _bgShapes[idx];
if ((o._type == ACTIVE_BG_SHAPE || o._type == REMOVE) && _goToScene == -1) {
screen.flushImage(o._imageFrame, o._position,
&o._oldPosition.x, &o._oldPosition.y, &o._oldSize.x, &o._oldSize.y);
}
}
if (people._portraitLoaded) {
if (people._portrait._type == REMOVE)
screen.slamRect(Common::Rect(
people._portrait._position.x, people._portrait._position.y,
people._portrait._position.x + people._portrait._delta.x,
people._portrait._position.y + people._portrait._delta.y
));
else
screen.flushImage(people._portrait._imageFrame, people._portrait._position,
&people._portrait._oldPosition.x, &people._portrait._oldPosition.y,
&people._portrait._oldSize.x, &people._portrait._oldSize.y);
if (people._portrait._type == REMOVE)
people._portrait._type = INVALID;
}
if (_goToScene == -1) {
for (uint idx = 0; idx < _bgShapes.size(); ++idx) {
Object &o = _bgShapes[idx];
if (o._type == NO_SHAPE && (o._flags & OBJ_BEHIND) == 0) {
screen.slamArea(o._position.x, o._position.y, o._oldSize.x, o._oldSize.y);
screen.slamArea(o._oldPosition.x, o._oldPosition.y, o._oldSize.x, o._oldSize.y);
} else if (o._type == HIDE_SHAPE) {
// Hiding shape, so flush it out and mark it as hidden
screen.flushImage(o._imageFrame, o._position,
&o._oldPosition.x, &o._oldPosition.y, &o._oldSize.x, &o._oldSize.y);
o._type = HIDDEN;
}
}
}
for (int idx = _canimShapes.size() - 1; idx >= 0; --idx) {
Object &o = *_canimShapes[idx];
if (o._type == INVALID) {
// Anim shape was invalidated by checkEndOfSequence, so at this point we can remove it
delete _canimShapes[idx];
_canimShapes.remove_at(idx);
} else if (o._type == REMOVE) {
if (_goToScene == -1)
screen.slamArea(o._position.x, o._position.y, o._delta.x, o._delta.y);
// Shape for an animation is no longer needed, so remove it completely
delete _canimShapes[idx];
_canimShapes.remove_at(idx);
} else if (o._type == ACTIVE_BG_SHAPE) {
screen.flushImage(o._imageFrame, o._position,
&o._oldPosition.x, &o._oldPosition.y, &o._oldSize.x, &o._oldSize.y);
}
}
}
_restoreFlag = true;
_doBgAnimDone = true;
events.wait(3);
screen.resetDisplayBounds();
// Check if the method was called for calling a portrait, and a talk was
// interrupting it. This talk file would not have been executed at the time,
// since we needed to finish the 'doBgAnim' to finish clearing the portrait
if (people._clearingThePortrait && talk._scriptMoreFlag == 3) {
// Reset the flags and call to talk
people._clearingThePortrait = false;
talk._scriptMoreFlag = 0;
talk.talkTo(talk._scriptName);
}
}
int ScalpelScene::startCAnim(int cAnimNum, int playRate) {
Events &events = *_vm->_events;
ScalpelMap &map = *(ScalpelMap *)_vm->_map;
People &people = *_vm->_people;
Resources &res = *_vm->_res;
Talk &talk = *_vm->_talk;
ScalpelUserInterface &ui = *(ScalpelUserInterface *)_vm->_ui;
Point32 tpPos, walkPos;
int tpDir, walkDir;
int tFrames = 0;
int gotoCode = -1;
Object *cObj;
// Validation
if (cAnimNum >= (int)_cAnim.size())
// number out of bounds
return -1;
if (_canimShapes.size() >= 3 || playRate == 0)
// Too many active animations, or invalid play rate
return 0;
CAnim &cAnim = _cAnim[cAnimNum];
if (playRate < 0) {
// Reverse direction
walkPos = cAnim._teleport[0];
walkDir = cAnim._teleport[0]._facing;
tpPos = cAnim._goto[0];
tpDir = cAnim._goto[0]._facing;
} else {
// Forward direction
walkPos = cAnim._goto[0];
walkDir = cAnim._goto[0]._facing;
tpPos = cAnim._teleport[0];
tpDir = cAnim._teleport[0]._facing;
}
CursorId oldCursor = events.getCursor();
events.setCursor(WAIT);
if (walkPos.x != -1) {
// Holmes must walk to the walk point before the cAnimation is started
if (people[HOLMES]._position != walkPos)
people[HOLMES].walkToCoords(walkPos, walkDir);
}
if (talk._talkToAbort)
return 1;
// Add new anim shape entry for displaying the animation
cObj = new Object();
_canimShapes.push_back(cObj);
// Copy the canimation into the bgShapes type canimation structure so it can be played
cObj->_allow = cAnimNum + 1; // Keep track of the parent structure
cObj->_name = _cAnim[cAnimNum]._name; // Copy name
// Remove any attempt to draw object frame
if (cAnim._type == NO_SHAPE && cAnim._sequences[0] < 100)
cAnim._sequences[0] = 0;
cObj->_sequences = cAnim._sequences;
cObj->_images = nullptr;
cObj->_position = cAnim._position;
cObj->_delta = Common::Point(0, 0);
cObj->_type = cAnim._type;
cObj->_flags = cAnim._flags;
cObj->_maxFrames = 0;
cObj->_frameNumber = -1;
cObj->_sequenceNumber = cAnimNum;
cObj->_oldPosition = Common::Point(0, 0);
cObj->_oldSize = Common::Point(0, 0);
cObj->_goto = Common::Point(0, 0);
cObj->_status = 0;
cObj->_misc = 0;
cObj->_imageFrame = nullptr;
if (cAnim._name.size() > 0 && cAnim._type != NO_SHAPE) {
if (tpPos.x != -1)
people[HOLMES]._type = REMOVE;
Common::Path fname(cAnim._name + ".vgs");
if (!res.isInCache(fname)) {
// Set up RRM scene data
Common::SeekableReadStream *roomStream = res.load(_roomFilename);
roomStream->seek(cAnim._dataOffset);
//rrmStream->seek(44 + cAnimNum * 4);
//rrmStream->seek(rrmStream->readUint32LE());
// Load the canimation into the cache
Common::SeekableReadStream *imgStream = !_compressed ? roomStream->readStream(cAnim._dataSize) :
Resources::decompressLZ(*roomStream, cAnim._dataSize);
res.addToCache(fname, *imgStream);
delete imgStream;
delete roomStream;
}
// Now load the resource as an image
if (!IS_3DO) {
cObj->_images = new ImageFile(fname);
} else {
cObj->_images = new ImageFile3DO(fname, kImageFile3DOType_RoomFormat);
}
cObj->_imageFrame = &(*cObj->_images)[0];
cObj->_maxFrames = cObj->_images->size();
int frames = 0;
if (playRate < 0) {
// Reverse direction
// Count number of frames
while (frames < MAX_FRAME && cObj->_sequences[frames])
++frames;
} else {
// Forward direction
BaseObject::_countCAnimFrames = true;
while (cObj->_type == ACTIVE_BG_SHAPE) {
cObj->checkObject();
++frames;
if (frames >= 1000)
error("CAnim has infinite loop sequence");
}
if (frames > 1)
--frames;
BaseObject::_countCAnimFrames = false;
cObj->_type = cAnim._type;
cObj->_frameNumber = -1;
cObj->_position = cAnim._position;
cObj->_delta = Common::Point(0, 0);
}
// Return if animation has no frames in it
if (frames == 0)
return -2;
++frames;
int repeat = ABS(playRate);
int dir;
if (playRate < 0) {
// Play in reverse
dir = -2;
cObj->_frameNumber = frames - 3;
} else {
dir = 0;
}
tFrames = frames - 1;
int pauseFrame = (_cAnimFramePause) ? frames - _cAnimFramePause : -1;
while (--frames) {
if (frames == pauseFrame)
ui.printObjectDesc();
doBgAnim();
// Repeat same frame
int temp = repeat;
while (--temp > 0) {
cObj->_frameNumber--;
doBgAnim();
if (_vm->shouldQuit())
return 0;
}
cObj->_frameNumber += dir;
}
people[HOLMES]._type = CHARACTER;
}
// Teleport to ending coordinates if necessary
if (tpPos.x != -1) {
people[HOLMES]._position = tpPos; // Place the player
people[HOLMES]._sequenceNumber = tpDir;
people[HOLMES].gotoStand();
}
if (playRate < 0)
// Reverse direction - set to end sequence
cObj->_frameNumber = tFrames - 1;
if (cObj->_frameNumber <= 26)
gotoCode = cObj->_sequences[cObj->_frameNumber + 3];
// Unless anim shape has already been removed, do a final check to allow it to become REMOVEd
for (uint idx = 0; idx < _canimShapes.size(); ++idx) {
if (_canimShapes[idx] == cObj) {
cObj->checkObject();
break;
}
}
if (gotoCode > 0 && !talk._talkToAbort) {
_goToScene = gotoCode;
if (_goToScene < 97 && map[_goToScene].x) {
map._overPos = map[_goToScene];
}
}
people.loadWalk();
if (tpPos.x != -1 && !talk._talkToAbort) {
// Teleport to ending coordinates
people[HOLMES]._position = tpPos;
people[HOLMES]._sequenceNumber = tpDir;
people[HOLMES].gotoStand();
}
events.setCursor(oldCursor);
return 1;
}
int ScalpelScene::closestZone(const Common::Point &pt) {
int dist = 1000;
int zone = -1;
for (uint idx = 0; idx < _zones.size(); ++idx) {
Common::Point zc((_zones[idx].left + _zones[idx].right) / 2,
(_zones[idx].top + _zones[idx].bottom) / 2);
int d = ABS(zc.x - pt.x) + ABS(zc.y - pt.y);
if (d < dist) {
// Found a closer zone
dist = d;
zone = idx;
}
}
return zone;
}
int ScalpelScene::findBgShape(const Common::Point &pt) {
if (!_doBgAnimDone)
// New frame hasn't been drawn yet
return -1;
for (int idx = (int)_bgShapes.size() - 1; idx >= 0; --idx) {
Object &o = _bgShapes[idx];
if (o._type != INVALID && o._type != NO_SHAPE && o._type != HIDDEN
&& o._type != REMOVE && o._aType <= PERSON) {
if (o.getNewBounds().contains(pt))
return idx;
} else if (o._type == NO_SHAPE) {
if (o.getNoShapeBounds().contains(pt))
return idx;
}
}
return -1;
}
} // End of namespace Scalpel
} // End of namespace Sherlock

View File

@@ -0,0 +1,105 @@
/* 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/>.
*
*/
#ifndef SHERLOCK_SCALPEL_SCENE_H
#define SHERLOCK_SCALPEL_SCENE_H
#include "common/scummsys.h"
#include "common/array.h"
#include "common/rect.h"
#include "common/serializer.h"
#include "sherlock/objects.h"
#include "sherlock/scene.h"
#include "sherlock/screen.h"
namespace Sherlock {
namespace Scalpel {
extern const int FS_TRANS[8];
enum { BLACKWOOD_CAPTURE = 2, BAKER_STREET = 4, DRAWING_ROOM = 12, STATION = 17, PUB_INTERIOR = 19,
LAWYER_OFFICE = 27, BAKER_ST_EXTERIOR = 39, RESCUE_ANNA = 52, MOOREHEAD_DEATH = 53, EXIT_GAME = 55,
BRUMWELL_SUICIDE = 70, OVERHEAD_MAP2 = 98, DARTS_GAME = 99, OVERHEAD_MAP = 100 };
class ScalpelScene : public Scene {
private:
void doBgAnimCheckCursor();
protected:
/**
* Loads the data associated for a given scene. The room resource file's format is:
* BGHEADER: Holds an index for the rest of the file
* STRUCTS: The objects for the scene
* IMAGES: The graphic information for the structures
*
* The _misc field of the structures contains the number of the graphic image
* that it should point to after loading; _misc is then set to 0.
*/
bool loadScene(const Common::Path &filename) override;
/**
* Checks all the background shapes. If a background shape is animating,
* it will flag it as needing to be drawn. If a non-animating shape is
* colliding with another shape, it will also flag it as needing drawing
*/
void checkBgShapes() override;
/**
* Draw all the shapes, people and NPCs in the correct order
*/
void drawAllShapes() override;
/**
* Returns the index of the closest zone to a given point.
*/
int closestZone(const Common::Point &pt) override;
public:
ScalpelScene(SherlockEngine *vm) : Scene(vm) {}
~ScalpelScene() override;
/**
* Draw all objects and characters.
*/
void doBgAnim() override;
/**
* Attempt to start a canimation sequence. It will load the requisite graphics, and
* then copy the canim object into the _canimShapes array to start the animation.
*
* @param cAnimNum The canim object within the current scene
* @param playRate Play rate. 0 is invalid; 1=normal speed, 2=1/2 speed, etc.
* A negative playRate can also be specified to play the animation in reverse
*/
int startCAnim(int cAnimNum, int playRate = 1) override;
/**
* Attempts to find a background shape within the passed bounds. If found,
* it will return the shape number, or -1 on failure.
*/
int findBgShape(const Common::Point &pt) override;
};
} // End of namespace Scalpel
} // End of namespace Sherlock
#endif

View File

@@ -0,0 +1,137 @@
/* 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 "sherlock/scalpel/scalpel_screen.h"
#include "sherlock/scalpel/scalpel.h"
namespace Sherlock {
namespace Scalpel {
ScalpelScreen::ScalpelScreen(SherlockEngine *vm) : Screen(vm) {
_backBuffer1.create(320, 200, g_system->getScreenFormat());
_backBuffer2.create(320, 200, g_system->getScreenFormat());
activateBackBuffer1();
}
void ScalpelScreen::makeButton(const Common::Rect &bounds, const Common::Point &textPoint,
const Common::String &buttonText, bool textContainsHotkey) {
Surface &bb = _backBuffer;
bb.fillRect(Common::Rect(bounds.left, bounds.top, bounds.right, bounds.top + 1), BUTTON_TOP);
bb.fillRect(Common::Rect(bounds.left, bounds.top, bounds.left + 1, bounds.bottom), BUTTON_TOP);
bb.fillRect(Common::Rect(bounds.right - 1, bounds.top, bounds.right, bounds.bottom), BUTTON_BOTTOM);
bb.fillRect(Common::Rect(bounds.left + 1, bounds.bottom - 1, bounds.right, bounds.bottom), BUTTON_BOTTOM);
bb.fillRect(Common::Rect(bounds.left + 1, bounds.top + 1, bounds.right - 1, bounds.bottom - 1), BUTTON_MIDDLE);
buttonPrint(textPoint, COMMAND_FOREGROUND, false, buttonText, textContainsHotkey);
}
void ScalpelScreen::makeButton(const Common::Rect &bounds, int textX,
const Common::String &buttonText, bool textContainsHotkey) {
makeButton(bounds, Common::Point(textX, bounds.top), buttonText, textContainsHotkey);
}
// ButtonText is supposed to have its hotkey as a prefix. The hotkey will get highlighted.
void ScalpelScreen::buttonPrint(const Common::Point &pt, uint color, bool slamIt,
const Common::String &buttonText, bool textContainsHotkey) {
int xStart = pt.x;
int skipTextOffset = textContainsHotkey ? +1 : 0; // skip first char in case text contains hotkey
// Center text around given x-coordinate
if (textContainsHotkey) {
xStart -= (stringWidth(Common::String(buttonText.c_str() + 1)) / 2);
} else {
xStart -= (stringWidth(buttonText) / 2);
}
if (color == COMMAND_FOREGROUND) {
uint16 prefixOffsetX = 0;
byte hotkey = buttonText[0];
// Hotkey needs to be highlighted
if (textContainsHotkey) {
Common::String prefixText = Common::String(buttonText.c_str() + 1);
uint16 prefixTextLen = prefixText.size();
uint16 prefixTextPos = 0;
// Hotkey was passed additionally, we search for the hotkey inside the button text and
// remove it from there. We then draw the whole text as highlighted and afterward
// the processed text again as regular text (without the hotkey)
while (prefixTextPos < prefixTextLen) {
if (prefixText[prefixTextPos] == hotkey) {
// Hotkey found, remove remaining text
while (prefixTextPos < prefixText.size()) {
prefixText.deleteLastChar();
}
break;
}
prefixTextPos++;
}
if (prefixTextPos < prefixTextLen) {
// only adjust in case hotkey character was actually found
prefixOffsetX = stringWidth(prefixText);
}
}
if (slamIt) {
print(Common::Point(xStart, pt.y + 1),
COMMAND_FOREGROUND, "%s", buttonText.c_str() + skipTextOffset);
if (textContainsHotkey)
print(Common::Point(xStart + prefixOffsetX, pt.y + 1), COMMAND_HIGHLIGHTED, "%c", hotkey);
} else {
gPrint(Common::Point(xStart, pt.y),
COMMAND_FOREGROUND, "%s", buttonText.c_str() + skipTextOffset);
if (textContainsHotkey)
gPrint(Common::Point(xStart + prefixOffsetX, pt.y), COMMAND_HIGHLIGHTED, "%c", hotkey);
}
} else if (slamIt) {
print(Common::Point(xStart, pt.y + 1), color, "%s", buttonText.c_str() + skipTextOffset);
} else {
gPrint(Common::Point(xStart, pt.y), color, "%s", buttonText.c_str() + skipTextOffset);
}
}
void ScalpelScreen::makePanel(const Common::Rect &r) {
_backBuffer.fillRect(r, BUTTON_MIDDLE);
_backBuffer.hLine(r.left, r.top, r.right - 2, BUTTON_TOP);
_backBuffer.hLine(r.left + 1, r.top + 1, r.right - 3, BUTTON_TOP);
_backBuffer.vLine(r.left, r.top, r.bottom - 1, BUTTON_TOP);
_backBuffer.vLine(r.left + 1, r.top + 1, r.bottom - 2, BUTTON_TOP);
_backBuffer.vLine(r.right - 1, r.top, r.bottom - 1, BUTTON_BOTTOM);
_backBuffer.vLine(r.right - 2, r.top + 1, r.bottom - 2, BUTTON_BOTTOM);
_backBuffer.hLine(r.left, r.bottom - 1, r.right - 1, BUTTON_BOTTOM);
_backBuffer.hLine(r.left + 1, r.bottom - 2, r.right - 1, BUTTON_BOTTOM);
}
void ScalpelScreen::makeField(const Common::Rect &r) {
_backBuffer.fillRect(r, BUTTON_MIDDLE);
_backBuffer.hLine(r.left, r.top, r.right - 1, BUTTON_BOTTOM);
_backBuffer.hLine(r.left + 1, r.bottom - 1, r.right - 1, BUTTON_TOP);
_backBuffer.vLine(r.left, r.top + 1, r.bottom - 1, BUTTON_BOTTOM);
_backBuffer.vLine(r.right - 1, r.top + 1, r.bottom - 2, BUTTON_TOP);
}
} // End of namespace Scalpel
} // End of namespace Sherlock

View File

@@ -0,0 +1,68 @@
/* 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/>.
*
*/
#ifndef SHERLOCK_SCALPEL_SCREEN_H
#define SHERLOCK_SCALPEL_SCREEN_H
#include "sherlock/screen.h"
namespace Sherlock {
class SherlockEngine;
namespace Scalpel {
class ScalpelScreen : public Screen {
public:
ScalpelScreen(SherlockEngine *vm);
~ScalpelScreen() override {}
/**
* Draws a button for use in the inventory, talk, and examine dialogs.
* ButtonText is supposed to have its hotkey as a prefix. The hotkey will get highlighted.
*/
void makeButton(const Common::Rect &bounds, const Common::Point &textPoint, const Common::String &buttonText, bool textContainsHotkey = true);
void makeButton(const Common::Rect &bounds, int textX, const Common::String &buttonText, bool textContainsHotkey = true);
/**
* Prints an interface command with the first letter highlighted to indicate
* what keyboard shortcut is associated with it
* ButtonText is supposed to have its hotkey as a prefix. The hotkey will get highlighted.
*/
void buttonPrint(const Common::Point &pt, uint color, bool slamIt, const Common::String &buttonText, bool textContainsHotkey = true);
/**
* Draw a panel in the back buffer with a raised area effect around the edges
*/
void makePanel(const Common::Rect &r);
/**
* Draw a field in the back buffer with a raised area effect around the edges,
* suitable for text input.
*/
void makeField(const Common::Rect &r);
};
} // End of namespace Scalpel
} // End of namespace Sherlock
#endif

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,168 @@
/* 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/>.
*
*/
#ifndef SHERLOCK_SCALPEL_TALK_H
#define SHERLOCK_SCALPEL_TALK_H
#include "common/scummsys.h"
#include "common/array.h"
#include "common/rect.h"
#include "common/serializer.h"
#include "common/stream.h"
#include "common/stack.h"
#include "sherlock/talk.h"
namespace Sherlock {
namespace Scalpel {
class ScalpelTalk : public Talk {
private:
Common::Stack<SequenceEntry> _sequenceStack;
/**
* Get the center position for the current speaker, if any
*/
Common::Point get3doPortraitPosition() const;
OpcodeReturn cmdSwitchSpeaker(const byte *&str);
OpcodeReturn cmdAssignPortraitLocation(const byte *&str);
OpcodeReturn cmdGotoScene(const byte *&str);
OpcodeReturn cmdCallTalkFile(const byte *&str);
OpcodeReturn cmdClearInfoLine(const byte *&str);
OpcodeReturn cmdClearWindow(const byte *&str);
OpcodeReturn cmdDisplayInfoLine(const byte *&str);
OpcodeReturn cmdElse(const byte *&str);
OpcodeReturn cmdIf(const byte *&str);
OpcodeReturn cmdMoveMouse(const byte *&str);
OpcodeReturn cmdPlayPrologue(const byte *&str);
OpcodeReturn cmdRemovePortrait(const byte *&str);
OpcodeReturn cmdSfxCommand(const byte *&str);
OpcodeReturn cmdSummonWindow(const byte *&str);
OpcodeReturn cmdWalkToCoords(const byte *&str);
protected:
/**
* Display the talk interface window
*/
void talkInterface(const byte *&str) override;
/**
* Pause when displaying a talk dialog on-screen
*/
void talkWait(const byte *&str) override;
/**
* Called when the active speaker is switched
*/
void switchSpeaker() override;
/**
* Called when a character being spoken to has no talk options to display
*/
void nothingToSay() override;
/**
* Show the talk display
*/
void showTalk() override;
public:
ScalpelTalk(SherlockEngine *vm);
~ScalpelTalk() override {}
Common::String _fixedTextWindowExit;
Common::String _fixedTextWindowUp;
Common::String _fixedTextWindowDown;
/**
* Opens the talk file 'talk.tlk' and searches the index for the specified
* conversation. If found, the data for that conversation is loaded
*/
void loadTalkFile(const Common::String &filename) override;
/**
* Called whenever a conversation or item script needs to be run. For standard conversations,
* it opens up a description window similar to how 'talk' does, but shows a 'reply' directly
* instead of waiting for a statement option.
* @remarks It seems that at some point, all item scripts were set up to use this as well.
* In their case, the conversation display is simply suppressed, and control is passed on to
* doScript to implement whatever action is required.
*/
void talkTo(const Common::String &filename) override;
/**
* When the talk window has been displayed, waits a period of time proportional to
* the amount of text that's been displayed
*/
int waitForMore(int delay) override;
/**
* Draws the interface for conversation display
*/
void drawInterface() override;
/**
* Display a list of statements in a window at the bottom of the screen that the
* player can select from.
*/
bool displayTalk(bool slamIt) override;
/**
* Prints a single conversation option in the interface window
*/
int talkLine(int lineNum, int stateNum, byte color, int lineY, bool slamIt) override;
/**
* Trigger to play a 3DO talk dialog movie
*/
bool talk3DOMovieTrigger(int subIndex);
/**
* Handles skipping over bad text in conversations
*/
static void skipBadText(const byte *&msgP);
/**
* Push the details of a passed object onto the saved sequences stack
*/
void pushSequenceEntry(Object *obj) override;
/**
* Pulls a background object sequence from the sequence stack and restore's the
* object's sequence
*/
void pullSequence(int slot = -1) override;
/**
* Returns true if the script stack is empty
*/
bool isSequencesEmpty() const override { return _sequenceStack.empty(); }
/**
* Clears the stack of pending object sequences associated with speakers in the scene
*/
void clearSequences() override;
};
} // End of namespace Scalpel
} // End of namespace Sherlock
#endif

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,247 @@
/* 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/>.
*
*/
#ifndef SHERLOCK_SCALPEL_UI_H
#define SHERLOCK_SCALPEL_UI_H
#include "common/scummsys.h"
#include "sherlock/user_interface.h"
#include "common/events.h"
namespace Sherlock {
class Inventory;
class Talk;
namespace Scalpel {
extern const int MENU_POINTS[12][4];
extern const int INVENTORY_POINTS[8][3];
enum {
MAINBUTTON_LOOK = 0,
MAINBUTTON_MOVE,
MAINBUTTON_TALK,
MAINBUTTON_PICKUP,
MAINBUTTON_OPEN,
MAINBUTTON_CLOSE,
MAINBUTTON_INVENTORY,
MAINBUTTON_USE,
MAINBUTTON_GIVE,
MAINBUTTON_JOURNAL,
MAINBUTTON_FILES,
MAINBUTTON_SETUP,
MAINBUTTON_LOADGAME,
MAINBUTTON_SAVEGAME
};
class Settings;
class ScalpelUserInterface: public UserInterface {
friend class Settings;
friend class Sherlock::Talk;
private:
char _keyPress;
Common::CustomEventType _actionPress;
int _lookHelp;
int _help, _oldHelp;
int _key;
Common::CustomEventType _action, _oldAction;
int _temp, _oldTemp; // button number (0-11)
int _oldLook;
bool _keyboardInput;
bool _actionInput;
bool _pause;
int _cNum;
Common::String _cAnimStr;
Common::String _descStr;
int _find;
private:
/**
* Draws the image for a user interface button in the down/pressed state.
*/
void depressButton(int num);
/**
* If he mouse button is pressed, then calls depressButton to draw the button
* as pressed; if not, it will show it as released with a call to "restoreButton".
*/
void pushButton(int num);
/**
* By the time this method has been called, the graphics for the button change
* have already been drawn. This simply takes care of switching the mode around
* accordingly
*/
void toggleButton(uint16 num);
/**
* Print the name of an object in the scene
*/
void lookScreen(const Common::Point &pt);
/**
* Gets the item in the inventory the mouse is on and display's its description
*/
void lookInv();
/**
* Handles input when the file list window is being displayed
*/
void doEnvControl();
/**
* Handle input whilst the inventory is active
*/
void doInvControl();
/**
* Handles waiting whilst an object's description window is open.
*/
void doLookControl();
/**
* Handles input until one of the user interface buttons/commands is selected
*/
void doMainControl();
/**
* Handles the input for the MOVE, OPEN, and CLOSE commands
*/
void doMiscControl(int allowed);
/**
* Handles input for picking up items
*/
void doPickControl();
/**
* Handles input when in talk mode. It highlights the buttons and available statements,
* and handles allowing the user to click on them
*/
void doTalkControl();
/**
* Handles events when the Journal is active.
* @remarks Whilst this would in theory be better in the Journal class, since it displays in
* the user interface, it uses so many internal UI fields, that it sort of made some sense
* to put it in the UserInterface class.
*/
void journalControl();
/**
* Checks to see whether a USE action is valid on the given object
*/
void checkUseAction(const UseType *use, const Common::String &invName, FixedTextActionId fixedTextActionId,
int objNum, bool giveMode);
/**
* Print the previously selected object's decription
*/
void printObjectDesc(const Common::String &str, bool firstTime);
public:
ImageFile *_controlPanel;
ImageFile *_controls;
int _oldUse;
Common::CustomEventType _actionsIndexed[14];
public:
ScalpelUserInterface(SherlockEngine *vm);
~ScalpelUserInterface() override;
/**
* Handles counting down whilst checking for input, then clears the info line.
*/
void whileMenuCounter();
/**
* Draws the image for the given user interface button in the up
* (not selected) position
*/
void restoreButton(int num);
/**
* Creates a text window and uses it to display the in-depth description
* of the highlighted object
*/
void examine();
Common::Point getTopLeftButtonPoint(int num) const;
Common::Rect getButtonRect(int buttonNr) const;
int infoLineHeight() const;
int infoLineYOffset() const;
public:
/**
* Resets the user interface
*/
void reset() override;
/**
* Main input handler for the user interface
*/
void handleInput() override;
/**
* Draw the user interface onto the screen's back buffers
*/
void drawInterface(int bufferNum = 3) override;
/**
* Displays a passed window by gradually scrolling it vertically on-screen
*/
void summonWindow(const Surface &bgSurface, bool slideUp = true) override;
/**
* Slide the window stored in the back buffer onto the screen
*/
void summonWindow(bool slideUp = true, int height = CONTROLS_Y) override;
/**
* Close a currently open window
* @param flag 0 = slide old window down, 1 = slide prior UI back up
*/
void banishWindow(bool slideUp = true) override;
/**
* Clears the info line of the screen
*/
void clearInfo() override;
/**
* Clear any active text window
*/
void clearWindow() override;
/**
* Print the previously selected object's decription
*/
virtual void printObjectDesc();
};
} // End of namespace Scalpel
} // End of namespace Sherlock
#endif

View File

@@ -0,0 +1,468 @@
/* 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 "sherlock/sherlock.h"
#include "sherlock/scalpel/settings.h"
#include "sherlock/scalpel/scalpel_screen.h"
#include "sherlock/scalpel/scalpel_user_interface.h"
#include "sherlock/scalpel/scalpel_fixed_text.h"
#include "sherlock/scalpel/scalpel.h"
#include "backends/keymapper/keymapper.h"
namespace Sherlock {
namespace Scalpel {
static const int SETUP_POINTS_INTL[12][4] = {
{ 4, 154, 101, 53 }, // Exit
{ 4, 165, 101, 53 }, // Music Toggle
{ 219, 165, 316, 268 }, // Voice Toggle
{ 103, 165, 217, 160 }, // Sound Effects Toggle
{ 219, 154, 316, 268 }, // Help Button Left/Right
{ 103, 154, 217, 160 }, // New Font Style
{ 4, 187, 101, 53 }, // Joystick Toggle
{ 103, 187, 217, 160 }, // Calibrate Joystick
{ 219, 176, 316, 268 }, // Fade Style
{ 103, 176, 217, 160 }, // Window Open Style
{ 4, 176, 101, 53 }, // Portraits Toggle
{ 219, 187, 316, 268 } // _key Pad Accel. Toggle
};
// Different from original to accommodate hotkeys
static const int SETUP_POINTS_ZH[12][4] = {
{ 3, 159, 73, 38 }, // Exit // OK
{ 3, 178, 73, 38 }, // Music Toggle // OK
{ 0, 0, 0, 0 }, // Voice Toggle
{ 74, 178, 145, 109 }, // Sound Effects Toggle // OK
{ 146, 178, 233, 191 }, // Help Button Left/Right // OK
{ 0, 0, 0, 0 }, // New Font Style // OK
{ 0, 0, 0, 0 }, // Joystick Toggle // OK
{ 0, 0, 0, 0 }, // Calibrate Joystick // OK
{ 234, 159, 317, 276 }, // Fade Style // OK
{ 146, 159, 233, 191 }, // Window Open Style // OK
{ 74, 159, 145, 109 }, // Portraits Toggle // OK
{ 234, 178, 317, 276 } // _key Pad Accel. Toggle
};
/*----------------------------------------------------------------*/
bool Settings::doesButtonExist(int num) const {
if (_vm->getLanguage() == Common::Language::ZH_TWN)
return num != 2 && num != 5 && num != 6 && num != 7;
return true;
}
void Settings::makeButtonNumDisabled(int num, const Common::String &s) {
if (!doesButtonExist(num))
return;
ScalpelScreen &screen = *(ScalpelScreen *)_vm->_screen;
makeButtonNum(num, s);
screen.buttonPrint(getButtonTextPoint(num), COMMAND_NULL, false, s);
}
Common::Rect Settings::getButtonRect(int num) const {
if (_vm->getLanguage() == Common::Language::ZH_TWN) {
return Common::Rect(SETUP_POINTS_ZH[num][0], SETUP_POINTS_ZH[num][1],
SETUP_POINTS_ZH[num][2], SETUP_POINTS_ZH[num][1] + 19);
} else {
return Common::Rect(SETUP_POINTS_INTL[num][0], SETUP_POINTS_INTL[num][1],
SETUP_POINTS_INTL[num][2], SETUP_POINTS_INTL[num][1] + 10);
}
}
void Settings::makeButtonNum(int num, const Common::String &s) {
if (!doesButtonExist(num))
return;
ScalpelScreen &screen = *(ScalpelScreen *)_vm->_screen;
screen.makeButton(getButtonRect(num), getButtonTextPoint(num), s);
}
Common::Point Settings::getButtonTextPoint(int num) const {
if (_vm->getLanguage() == Common::Language::ZH_TWN) {
return Common::Point(SETUP_POINTS_ZH[num][3], SETUP_POINTS_ZH[num][1] + 2);
} else {
return Common::Point(SETUP_POINTS_INTL[num][3], SETUP_POINTS_INTL[num][1]);
}
}
void Settings::drawInterface(bool flag) {
People &people = *_vm->_people;
ScalpelScreen &screen = *(ScalpelScreen *)_vm->_screen;
Sound &sound = *_vm->_sound;
Music &music = *_vm->_music;
UserInterface &ui = *_vm->_ui;
Common::String tempStr;
if (!flag) {
screen._backBuffer1.fillRect(Common::Rect(0, CONTROLS_Y1, SHERLOCK_SCREEN_WIDTH, CONTROLS_Y1 + 1), BORDER_COLOR);
screen._backBuffer1.fillRect(Common::Rect(0, CONTROLS_Y1 + 1, 2, SHERLOCK_SCREEN_HEIGHT), BORDER_COLOR);
screen._backBuffer1.fillRect(Common::Rect(SHERLOCK_SCREEN_WIDTH - 2, CONTROLS_Y1 + 1, SHERLOCK_SCREEN_WIDTH,
SHERLOCK_SCREEN_HEIGHT), BORDER_COLOR);
screen._backBuffer1.hLine(0, SHERLOCK_SCREEN_HEIGHT - 1, SHERLOCK_SCREEN_WIDTH - 1, BORDER_COLOR);
screen._backBuffer1.fillRect(Common::Rect(2, CONTROLS_Y1 + 1, SHERLOCK_SCREEN_WIDTH - 2,
SHERLOCK_SCREEN_HEIGHT - 1), INV_BACKGROUND);
}
tempStr = FIXED(Settings_Exit);
_actionExit = kActionScalpelSettingsExit;
makeButtonNum(0, tempStr);
if (music._musicOn) {
tempStr = FIXED(Settings_MusicOn);
} else {
tempStr = FIXED(Settings_MusicOff);
}
_actionMusic = kActionScalpelSettingsToggleMusic;
makeButtonNum(1, tempStr);
if (people._portraitsOn) {
tempStr = FIXED(Settings_PortraitsOn);
} else {
tempStr = FIXED(Settings_PortraitsOff);
}
_actionPortraits = kActionScalpelSettingsTogglePortraits;
makeButtonNum(10, tempStr);
// WORKAROUND: We don't support the joystick in ScummVM, so draw the next two buttons as disabled
tempStr = FIXED(Settings_JoystickOff);
makeButtonNumDisabled(6, tempStr);
tempStr = FIXED(Settings_NewFontStyle);
_actionNewFontStyle = kActionScalpelSettingsChangeFontStyle;
makeButtonNum(5, tempStr);
if (sound._digitized) {
tempStr = FIXED(Settings_SoundEffectsOn);
} else {
tempStr = FIXED(Settings_SoundEffectsOff);
}
_actionSoundEffects = kActionScalpelSettingsToggleSoundEffects;
makeButtonNum(3, tempStr);
if (ui._slideWindows) {
tempStr = FIXED(Settings_WindowsSlide);
} else {
tempStr = FIXED(Settings_WindowsAppear);
}
_actionWindows = kActionScalpelSettingsToggleWindowsMode;
makeButtonNum(9, tempStr);
tempStr = FIXED(Settings_CalibrateJoystick);
makeButtonNumDisabled(7, tempStr);
if (ui._helpStyle) {
tempStr = FIXED(Settings_AutoHelpRight);
} else {
tempStr = FIXED(Settings_AutoHelpLeft);
}
_actionAutoHelp = kActionScalpelSettingsChangeAutohelpLoc;
makeButtonNum(4, tempStr);
if (sound._voices) {
tempStr = FIXED(Settings_VoicesOn);
} else {
tempStr = FIXED(Settings_VoicesOff);
}
_actionVoices = kActionScalpelSettingsToggleVoices;
makeButtonNum(2, tempStr);
if (screen._fadeStyle) {
tempStr = FIXED(Settings_FadeByPixel);
} else {
tempStr = FIXED(Settings_FadeDirectly);
}
_actionFade = kActionScalpelSettingsChangeFadeMode;
makeButtonNum(8, tempStr);
if (screen._fadeStyle) {
// German version uses a different action for fade modes
if (_vm->getLanguage() == Common::Language::DE_DEU)
_actionFade = kActionScalpelSettingsFadeByPixels;
} else {
if (_vm->getLanguage() == Common::Language::DE_DEU)
_actionFade = kActionScalpelSettingsFadeDirectly;
}
tempStr = FIXED(Settings_KeyPadSlow);
makeButtonNumDisabled(11, tempStr);
_actionsIndexed[0] = _actionExit;
_actionsIndexed[1] = _actionMusic;
_actionsIndexed[2] = _actionVoices;
_actionsIndexed[3] = _actionSoundEffects;
_actionsIndexed[4] = _actionAutoHelp;
_actionsIndexed[5] = _actionNewFontStyle;
_actionsIndexed[8] = _actionFade;
_actionsIndexed[9] = _actionWindows;
_actionsIndexed[10] = _actionPortraits;
// Show the window immediately, or slide it on-screen
if (!flag) {
if (!ui._slideWindows) {
screen.slamRect(Common::Rect(0, CONTROLS_Y1, SHERLOCK_SCREEN_WIDTH, SHERLOCK_SCREEN_HEIGHT));
} else {
ui.summonWindow(true, CONTROLS_Y1);
}
ui._windowOpen = true;
} else {
screen.slamRect(Common::Rect(0, CONTROLS_Y1, SHERLOCK_SCREEN_WIDTH, SHERLOCK_SCREEN_HEIGHT));
}
}
int Settings::drawButtons(const Common::Point &pt, Common::CustomEventType action) {
Events &events = *_vm->_events;
People &people = *_vm->_people;
ScalpelScreen &screen = *(ScalpelScreen *)_vm->_screen;
Music &music = *_vm->_music;
Sound &sound = *_vm->_sound;
UserInterface &ui = *_vm->_ui;
int found = -1;
byte color;
Common::String tempStr;
for (int idx = 0; idx < 12; ++idx) {
if (!doesButtonExist(idx))
continue;
if ((getButtonRect(idx).contains(pt) && (events._pressed || events._released))
|| (action == _actionsIndexed[idx])) {
found = idx;
color = COMMAND_HIGHLIGHTED;
} else {
color = COMMAND_FOREGROUND;
}
// Print the button text
switch (idx) {
case 0:
tempStr = FIXED(Settings_Exit);
break;
case 1:
if (music._musicOn) {
tempStr = FIXED(Settings_MusicOn);
} else {
tempStr = FIXED(Settings_MusicOff);
}
break;
case 2:
if (sound._voices) {
tempStr = FIXED(Settings_VoicesOn);
} else {
tempStr = FIXED(Settings_VoicesOff);
}
break;
case 3:
if (sound._digitized) {
tempStr = FIXED(Settings_SoundEffectsOn);
} else {
tempStr = FIXED(Settings_SoundEffectsOff);
}
break;
case 4:
if (ui._helpStyle) {
tempStr = FIXED(Settings_AutoHelpRight);
} else {
tempStr = FIXED(Settings_AutoHelpLeft);
}
break;
case 5:
tempStr = FIXED(Settings_NewFontStyle);
break;
case 6:
// Joystick Off - disabled in ScummVM
continue;
case 7:
// Calibrate Joystick - disabled in ScummVM
continue;
case 8:
if (screen._fadeStyle) {
tempStr = FIXED(Settings_FadeByPixel);
} else {
tempStr = FIXED(Settings_FadeDirectly);
}
break;
case 9:
if (ui._slideWindows) {
tempStr = FIXED(Settings_WindowsSlide);
} else {
tempStr = FIXED(Settings_WindowsAppear);
}
break;
case 10:
if (people._portraitsOn) {
tempStr = FIXED(Settings_PortraitsOn);
} else {
tempStr = FIXED(Settings_PortraitsOff);
}
break;
case 11:
// Key Pad Slow - disabled in ScummVM
continue;
default:
continue;
}
screen.buttonPrint(getButtonTextPoint(idx), color, true, tempStr);
}
return found;
}
void Settings::show(SherlockEngine *vm) {
Events &events = *vm->_events;
People &people = *vm->_people;
Scene &scene = *vm->_scene;
Screen &screen = *vm->_screen;
Sound &sound = *vm->_sound;
Music &music = *vm->_music;
Talk &talk = *vm->_talk;
ScalpelUserInterface &ui = *(ScalpelUserInterface *)vm->_ui;
bool updateConfig = false;
assert(vm->getGameID() == GType_SerratedScalpel);
Settings settings(vm);
settings.drawInterface(false);
Common::Keymapper *keymapper = g_system->getEventManager()->getKeymapper();
keymapper->getKeymap("scalpel")->setEnabled(false);
keymapper->getKeymap("scalpel-settings")->setEnabled(true);
do {
if (ui._menuCounter)
ui.whileMenuCounter();
int found = -1;
ui._action = kActionNone;
scene.doBgAnim();
if (talk._talkToAbort)
return;
events.setButtonState();
Common::Point pt = events.mousePos();
if (events._pressed || events._released || events.actionHit()) {
ui.clearInfo();
ui._action = kActionNone;
if (events.actionHit()) {
ui._action = events.getAction();
if (ui._action == kActionScalpelSettingsSelect) {
events._pressed = false;
events._oldButtons = 0;
ui._actionPress = kActionNone;
events._released = true;
}
}
// Handle highlighting button under mouse
found = settings.drawButtons(pt, ui._action);
}
if ((found == 0 && events._released) || (ui._action == settings._actionExit))
// Exit
break;
if ((found == 1 && events._released) || ui._action == settings._actionMusic) {
// Toggle music
music._musicOn = !music._musicOn;
if (!music._musicOn)
music.stopMusic();
else
music.startSong();
updateConfig = true;
settings.drawInterface(true);
}
if ((found == 2 && events._released) || ui._action == settings._actionVoices) {
sound._voices = !sound._voices;
updateConfig = true;
settings.drawInterface(true);
}
if ((found == 3 && events._released) || ui._action == settings._actionSoundEffects) {
// Toggle sound effects
sound._digitized = !sound._digitized;
updateConfig = true;
settings.drawInterface(true);
}
if ((found == 4 && events._released) || ui._action == settings._actionAutoHelp) {
// Help button style
ui._helpStyle = !ui._helpStyle;
updateConfig = true;
settings.drawInterface(true);
}
if ((found == 5 && events._released) || ui._action == settings._actionNewFontStyle) {
// New font style
int fontNum = screen.fontNumber() + 1;
if (fontNum == 3)
fontNum = 0;
screen.setFont(fontNum);
updateConfig = true;
settings.drawInterface(true);
}
if ((found == 8 && events._released) || ui._action == settings._actionFade) {
// Toggle fade style
screen._fadeStyle = !screen._fadeStyle;
updateConfig = true;
settings.drawInterface(true);
}
if ((found == 9 && events._released) || ui._action == settings._actionWindows) {
// Window style
ui._slideWindows = !ui._slideWindows;
updateConfig = true;
settings.drawInterface(true);
}
if ((found == 10 && events._released) || ui._action == settings._actionPortraits) {
// Toggle portraits being shown
people._portraitsOn = !people._portraitsOn;
updateConfig = true;
settings.drawInterface(true);
}
} while (!vm->shouldQuit());
ui.banishWindow();
keymapper->getKeymap("scalpel-settings")->setEnabled(false);
keymapper->getKeymap("scalpel")->setEnabled(true);
if (updateConfig)
vm->saveConfig();
ui._actionPress = kActionNone;
ui._actionInput = false;
ui._windowBounds.top = CONTROLS_Y1;
ui._action = (Common::CustomEventType) -1;
}
} // End of namespace Scalpel
} // End of namespace Sherlock

View File

@@ -0,0 +1,96 @@
/* 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/>.
*
*/
#ifndef SHERLOCK_SETTINGS_H
#define SHERLOCK_SETTINGS_H
#include "common/events.h"
#include "common/scummsys.h"
#include "sherlock/sherlock.h"
namespace Sherlock {
class SherlockEngine;
namespace Scalpel {
class Settings {
private:
SherlockEngine *_vm;
Settings(SherlockEngine *vm) : _vm(vm) {
_actionExit = kActionNone;
_actionMusic = kActionNone;
_actionPortraits = kActionNone;
_actionNewFontStyle = kActionNone;
_actionSoundEffects = kActionNone;
_actionWindows = kActionNone;
_actionAutoHelp = kActionNone;
_actionVoices = kActionNone;
_actionFade = kActionNone;
memset(_actionsIndexed, kActionNone, sizeof(_actionsIndexed));
}
Common::CustomEventType _actionExit;
Common::CustomEventType _actionMusic;
Common::CustomEventType _actionPortraits;
Common::CustomEventType _actionNewFontStyle;
Common::CustomEventType _actionSoundEffects;
Common::CustomEventType _actionWindows;
Common::CustomEventType _actionAutoHelp;
Common::CustomEventType _actionVoices;
Common::CustomEventType _actionFade;
Common::CustomEventType _actionsIndexed[12];
/**
* Draws the interface for the settings window
*/
void drawInterface(bool flag);
/**
* Draws the buttons for the settings dialog
*/
int drawButtons(const Common::Point &pt, Common::CustomEventType action);
Common::Rect getButtonRect(int num) const;
Common::Point getButtonTextPoint(int num) const;
void makeButtonNum(int num, const Common::String &s);
void makeButtonNumDisabled(int num, const Common::String &s);
bool doesButtonExist(int num) const;
public:
/**
* Handles input when the settings window is being shown
* @remarks Whilst this would in theory be better in the Journal class, since it displays in
* the user interface, it uses so many internal UI fields, that it sort of made some sense
* to put it in the UserInterface class.
*/
static void show(SherlockEngine *vm);
};
} // End of namespace Scalpel
} // End of namespace Sherlock
#endif

View File

@@ -0,0 +1,686 @@
/* 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/scummsys.h"
#include "sherlock/scalpel/tsage/logo.h"
#include "sherlock/scalpel/scalpel.h"
namespace Sherlock {
namespace Scalpel {
namespace TsAGE {
TLib *Visage::_tLib;
Visage::Visage() {
_resNum = -1;
_rlbNum = -1;
_stream = nullptr;
}
void Visage::setVisage(int resNum, int rlbNum) {
if ((_resNum != resNum) || (_rlbNum != rlbNum)) {
_resNum = resNum;
_rlbNum = rlbNum;
delete _stream;
// Games after Ringworld have an extra indirection via the visage index file
Common::SeekableReadStream *stream = _tLib->getResource(RES_VISAGE, resNum, 9999);
if (rlbNum == 0)
rlbNum = 1;
// Check how many slots there are
uint16 count = stream->readUint16LE();
if (rlbNum > count)
rlbNum = count;
// Get the flags/rlbNum to use
stream->seek((rlbNum - 1) * 4 + 2);
uint32 v = stream->readUint32LE();
int flags = v >> 30;
if (flags & 3) {
rlbNum = (int)(v & 0xff);
}
assert((flags & 3) == 0);
delete stream;
_stream = _tLib->getResource(RES_VISAGE, resNum, rlbNum);
}
}
void Visage::clear() {
delete _stream;
_stream = nullptr;
}
Visage::~Visage() {
delete _stream;
}
void Visage::getFrame(ObjectSurface &s, int frameNum) {
_stream->seek(0);
int numFrames = _stream->readUint16LE();
if (frameNum > numFrames)
frameNum = numFrames;
if (frameNum > 0)
--frameNum;
_stream->seek(frameNum * 4 + 2);
int offset = _stream->readUint32LE();
_stream->seek(offset);
surfaceFromRes(s);
}
int Visage::getFrameCount() const {
_stream->seek(0);
return _stream->readUint16LE();
}
bool Visage::isLoaded() const {
return _stream != nullptr;
}
void Visage::surfaceFromRes(ObjectSurface &s) {
int frameWidth = _stream->readUint16LE();
int frameHeight = _stream->readUint16LE();
Common::Rect r(0, 0, frameWidth, frameHeight);
s.create(r.width(), r.height());
s._centroid.x = _stream->readSint16LE();
s._centroid.y = _stream->readSint16LE();
_stream->skip(1);
byte flags = _stream->readByte();
bool rleEncoded = (flags & 2) != 0;
byte *destP = (byte *)s.getPixels();
if (!rleEncoded) {
_stream->read(destP, r.width() * r.height());
} else {
Common::fill(destP, destP + (r.width() * r.height()), 0xff);
for (int yp = 0; yp < r.height(); ++yp) {
int width = r.width();
destP = (byte *)s.getBasePtr(0, yp);
while (width > 0) {
uint8 controlVal = _stream->readByte();
if ((controlVal & 0x80) == 0) {
// Copy specified number of bytes
_stream->read(destP, controlVal);
width -= controlVal;
destP += controlVal;
} else if ((controlVal & 0x40) == 0) {
// Skip a specified number of output pixels
destP += controlVal & 0x3f;
width -= controlVal & 0x3f;
} else {
// Copy a specified pixel a given number of times
controlVal &= 0x3f;
int pixel = _stream->readByte();
Common::fill(destP, destP + controlVal, pixel);
destP += controlVal;
width -= controlVal;
}
}
assert(width == 0);
}
}
}
/*--------------------------------------------------------------------------*/
ScalpelEngine *Object::_vm;
Object::Object() {
_vm = nullptr;
_isAnimating = _finished = false;
_frame = 0;
_numFrames = 0;
_frameChange = 0;
_angle = _changeCtr = 0;
_walkStartFrame = 0;
_majorDiff = _minorDiff = 0;
_updateStartFrame = 0;
}
void Object::setVisage(int visage, int strip) {
_visage.setVisage(visage, strip);
}
void Object::setAnimMode(bool isAnimating) {
_isAnimating = isAnimating;
_finished = false;
_updateStartFrame = _vm->_events->getFrameCounter();
if (_numFrames)
_updateStartFrame += 60 / _numFrames;
_frameChange = 1;
}
void Object::setDestination(const Common::Point &pt) {
_destination = pt;
int moveRate = 10;
_walkStartFrame = _vm->_events->getFrameCounter();
_walkStartFrame += 60 / moveRate;
calculateMoveAngle();
// Get the difference
int diffX = _destination.x - _position.x;
int diffY = _destination.y - _position.y;
int xSign = (diffX < 0) ? -1 : (diffX > 0 ? 1 : 0);
int ySign = (diffY < 0) ? -1 : (diffY > 0 ? 1 : 0);
diffX = ABS(diffX);
diffY = ABS(diffY);
if (diffX < diffY) {
_minorDiff = diffX / 2;
_majorDiff = diffY;
} else {
_minorDiff = diffY / 2;
_majorDiff = diffX;
}
// Set the destination position
_moveDelta = Common::Point(diffX, diffY);
_moveSign = Common::Point(xSign, ySign);
_changeCtr = 0;
assert(diffX || diffY);
}
void Object::erase() {
Screen &screen = *_vm->_screen;
if (_visage.isLoaded() && !_oldBounds.isEmpty())
screen.SHblitFrom(screen._backBuffer1, Common::Point(_oldBounds.left, _oldBounds.top), _oldBounds);
}
void Object::update() {
Screen &screen = *_vm->_screen;
if (_visage.isLoaded()) {
if (isMoving()) {
uint32 currTime = _vm->_events->getFrameCounter();
if (_walkStartFrame <= currTime) {
int moveRate = 10;
int frameInc = 60 / moveRate;
_walkStartFrame = currTime + frameInc;
move();
}
}
if (_isAnimating) {
if (_frame < _visage.getFrameCount())
_frame = changeFrame();
else
_finished = true;
}
// Get the new frame
ObjectSurface s;
_visage.getFrame(s, _frame);
// Display the frame
_oldBounds = Common::Rect(_position.x, _position.y, _position.x + s.width(), _position.y + s.height());
_oldBounds.translate(-s._centroid.x, -s._centroid.y);
screen.SHtransBlitFrom(s, Common::Point(_oldBounds.left, _oldBounds.top));
}
}
int Object::changeFrame() {
int frameNum = _frame;
uint32 currentFrame = _vm->_events->getFrameCounter();
if (_updateStartFrame <= currentFrame) {
if (_numFrames > 0) {
int v = 60 / _numFrames;
_updateStartFrame = currentFrame + v;
frameNum = getNewFrame();
}
}
return frameNum;
}
int Object::getNewFrame() {
int frameNum = _frame + _frameChange;
if (_frameChange > 0) {
if (frameNum > _visage.getFrameCount()) {
frameNum = 1;
}
} else if (frameNum < 1) {
frameNum = _visage.getFrameCount();
}
return frameNum;
}
void Object::calculateMoveAngle() {
int xDiff = _destination.x - _position.x, yDiff = _position.y - _destination.y;
if (!xDiff && !yDiff)
_angle = 0;
else if (!xDiff)
_angle = (_destination.y >= _position.y) ? 180 : 0;
else if (!yDiff)
_angle = (_destination.x >= _position.x) ? 90 : 270;
else {
int result = (((xDiff * 100) / ((abs(xDiff) + abs(yDiff))) * 90) / 100);
if (yDiff < 0)
result = 180 - result;
else if (xDiff < 0)
result += 360;
_angle = result;
}
}
bool Object::isAnimEnded() const {
return _finished;
}
bool Object::isMoving() const {
return (_destination.x != 0) && (_destination != _position);
}
void Object::move() {
Common::Point currPos = _position;
Common::Point moveDiff(5, 3);
int percent = 100;
if (dontMove())
return;
if (_moveDelta.x >= _moveDelta.y) {
int xAmount = _moveSign.x * moveDiff.x * percent / 100;
if (!xAmount)
xAmount = _moveSign.x;
currPos.x += xAmount;
int yAmount = ABS(_destination.y - currPos.y);
int yChange = _majorDiff / ABS(xAmount);
int ySign;
if (!yChange)
ySign = _moveSign.y;
else {
int v = yAmount / yChange;
_changeCtr += yAmount % yChange;
if (_changeCtr >= yChange) {
++v;
_changeCtr -= yChange;
}
ySign = _moveSign.y * v;
}
currPos.y += ySign;
_majorDiff -= ABS(xAmount);
} else {
int yAmount = _moveSign.y * moveDiff.y * percent / 100;
if (!yAmount)
yAmount = _moveSign.y;
currPos.y += yAmount;
int xAmount = ABS(_destination.x - currPos.x);
int xChange = _majorDiff / ABS(yAmount);
int xSign;
if (!xChange)
xSign = _moveSign.x;
else {
int v = xAmount / xChange;
_changeCtr += xAmount % xChange;
if (_changeCtr >= xChange) {
++v;
_changeCtr -= xChange;
}
xSign = _moveSign.x * v;
}
currPos.x += xSign;
_majorDiff -= ABS(yAmount);
}
_position = currPos;
if (dontMove())
_position = _destination;
}
bool Object::dontMove() const {
return (_majorDiff <= 0);
}
void Object::endMove() {
_position = _destination;
}
/*----------------------------------------------------------------*/
bool Logo::show(ScalpelEngine *vm) {
Events &events = *vm->_events;
Logo *logo = new Logo(vm);
bool interrupted = false;
while (!logo->finished()) {
logo->nextFrame();
// Erase areas from previous frame, and update and re-draw objects
for (int idx = 0; idx < 4; ++idx)
logo->_objects[idx].erase();
for (int idx = 0; idx < 4; ++idx)
logo->_objects[idx].update();
events.delay(10);
events.setButtonState();
++logo->_frameCounter;
interrupted = vm->shouldQuit() || events.kbHit() || events._pressed || events.actionHit();
if (interrupted) {
// Keyboard, mouse, or action button pressed, so break out of logo display
events.clearEvents();
break;
}
}
delete logo;
return !interrupted;
}
Logo::Logo(ScalpelEngine *vm) : _vm(vm), _lib("sf3.rlb") {
Object::_vm = vm;
Visage::_tLib = &_lib;
_finished = false;
// Initialize counter
_counter = 0;
_frameCounter = 0;
// Initialize wait frame counters
_waitFrames = 0;
_waitStartFrame = 0;
// Initialize animation counters
_animateObject = 0;
_animateStartFrame = 0;
_animateFrameDelay = 0;
_animateFrames = nullptr;
_animateFrame = 0;
// Save a copy of the original palette
_vm->_screen->getPalette(_originalPalette);
// Set up the palettes
Common::fill(&_palette1[0], &_palette1[Graphics::PALETTE_SIZE], 0);
Common::fill(&_palette1[0], &_palette2[Graphics::PALETTE_SIZE], 0);
Common::fill(&_palette1[0], &_palette3[Graphics::PALETTE_SIZE], 0);
_lib.getPalette(_palette1, 1111);
_lib.getPalette(_palette1, 10);
_lib.getPalette(_palette2, 1111);
_lib.getPalette(_palette2, 1);
_lib.getPalette(_palette3, 1111);
_lib.getPalette(_palette3, 14);
}
Logo::~Logo() {
// Restore the original palette
_vm->_screen->setPalette(_originalPalette);
}
bool Logo::finished() const {
return _finished;
}
const AnimationFrame handFrames[] = {
{ 1, 33, 91 }, { 2, 44, 124 }, { 3, 64, 153 }, { 4, 87, 174 },
{ 5, 114, 191 }, { 6, 125, 184 }, { 7, 154, 187 }, { 8, 181, 182 },
{ 9, 191, 167 }, { 10, 190, 150 }, { 11, 182, 139 }, { 11, 170, 130 },
{ 11, 158, 121 }, { 0, 0, 0 }
};
const AnimationFrame companyFrames[] = {
{ 1, 155, 94 }, { 2, 155, 94 }, { 3, 155, 94 }, { 4, 155, 94 },
{ 5, 155, 94 }, { 6, 155, 94 }, { 7, 155, 94 }, { 8, 155, 94 },
{ 0, 0, 0 }
};
void Logo::nextFrame() {
Screen &screen = *_vm->_screen;
if (_waitFrames) {
uint32 currFrame = _frameCounter;
if (currFrame - _waitStartFrame < _waitFrames) {
return;
}
_waitStartFrame = 0;
_waitFrames = 0;
}
if (_animateFrames) {
uint32 currFrame = _frameCounter;
if (currFrame > _animateStartFrame + _animateFrameDelay) {
AnimationFrame animationFrame = _animateFrames[_animateFrame];
if (animationFrame.frame) {
_objects[_animateObject]._frame = animationFrame.frame;
_objects[_animateObject]._position = Common::Point(animationFrame.x, animationFrame.y);
_animateStartFrame += _animateFrameDelay;
_animateFrame++;
} else {
_animateObject = 0;
_animateFrameDelay = 0;
_animateFrames = nullptr;
_animateStartFrame = 0;
_animateFrame = 0;
}
}
if (_animateFrames)
return;
}
switch (_counter++) {
case 0:
// Load the background and fade it in
loadBackground();
fade(_palette1);
break;
case 1:
// First half of square, circle, and triangle arranging themselves
_objects[0].setVisage(16, 1);
_objects[0]._frame = 1;
_objects[0]._position = Common::Point(169, 107);
_objects[0]._numFrames = 7;
_objects[0].setAnimMode(true);
break;
case 2:
// Keep waiting until first animation ends
if (!_objects[0].isAnimEnded()) {
--_counter;
} else {
// Start second half of the shapes animation
_objects[0].setVisage(16, 2);
_objects[0]._frame = 1;
_objects[0]._numFrames = 11;
_objects[0].setAnimMode(true);
}
break;
case 3:
// Keep waiting until second animation of shapes ordering themselves ends
if (!_objects[0].isAnimEnded()) {
--_counter;
} else {
// Fade out the background but keep the shapes visible
fade(_palette2);
screen._backBuffer1.clear();
}
waitFrames(10);
break;
case 4:
// Load the new palette
byte palette[Graphics::PALETTE_SIZE];
Common::copy(&_palette2[0], &_palette2[Graphics::PALETTE_SIZE], &palette[0]);
_lib.getPalette(palette, 12);
screen.clear();
screen.setPalette(palette);
// Morph into the EA logo
_objects[0].setVisage(12, 1);
_objects[0]._frame = 1;
_objects[0]._numFrames = 7;
_objects[0].setAnimMode(true);
_objects[0]._position = Common::Point(170, 142);
_objects[0].setDestination(Common::Point(158, 71));
break;
case 5:
// Wait until the logo has expanded upwards to form EA logo
if (_objects[0].isMoving())
--_counter;
break;
case 6:
fade(_palette3, 40);
break;
case 7:
// Show the 'Electronic Arts' company name
_objects[1].setVisage(14, 1);
_objects[1]._frame = 1;
_objects[1]._position = Common::Point(152, 98);
waitFrames(120);
break;
case 8:
// Start sequence of positioning and size hand cursor in an arc
_objects[2].setVisage(18, 1);
startAnimation(2, 5, &handFrames[0]);
break;
case 9:
// Show a highlighting of the company name
_objects[1].remove();
_objects[2].erase();
_objects[2].remove();
_objects[3].setVisage(19, 1);
startAnimation(3, 8, &companyFrames[0]);
break;
case 10:
waitFrames(180);
break;
case 11:
_finished = true;
break;
default:
break;
}
}
void Logo::waitFrames(uint frames) {
_waitFrames = frames;
_waitStartFrame = _frameCounter;
}
void Logo::startAnimation(uint object, uint frameDelay, const AnimationFrame *frames) {
_animateObject = object;
_animateFrameDelay = frameDelay;
_animateFrames = frames;
_animateStartFrame = _frameCounter;
_animateFrame = 1;
_objects[object]._frame = frames[0].frame;
_objects[object]._position = Common::Point(frames[0].x, frames[0].y);
}
void Logo::loadBackground() {
Screen &screen = *_vm->_screen;
for (int idx = 0; idx < 4; ++idx) {
// Get the portion of the screen
Common::SeekableReadStream *stream = _lib.getResource(RES_BITMAP, 10, idx);
// Load it onto the surface
Common::Point pt((idx / 2) * (SHERLOCK_SCREEN_WIDTH / 2), (idx % 2) * (SHERLOCK_SCREEN_HEIGHT / 2));
for (int y = 0; y < (SHERLOCK_SCREEN_HEIGHT / 2); ++y, ++pt.y) {
byte *pDest = (byte *)screen._backBuffer1.getBasePtr(pt.x, pt.y);
stream->read(pDest, SHERLOCK_SCREEN_WIDTH / 2);
}
// _backgroundBounds = Rect(0, 0, READ_LE_UINT16(data), READ_LE_UINT16(data + 2));
delete stream;
}
// Default to a blank palette
byte palette[Graphics::PALETTE_SIZE];
Common::fill(&palette[0], &palette[Graphics::PALETTE_SIZE], 0);
screen.setPalette(palette);
// Copy the surface to the screen
screen.SHblitFrom(screen._backBuffer1);
}
void Logo::fade(const byte palette[Graphics::PALETTE_SIZE], int step) {
Events &events = *_vm->_events;
Screen &screen = *_vm->_screen;
byte startPalette[Graphics::PALETTE_SIZE];
byte tempPalette[Graphics::PALETTE_SIZE];
screen.getPalette(startPalette);
for (int percent = 0; percent < 100; percent += step) {
for (int palIndex = 0; palIndex < 256; ++palIndex) {
const byte *pal1P = (const byte *)&startPalette[palIndex * 3];
const byte *pal2P = (const byte *)&palette[palIndex * 3];
byte *destP = &tempPalette[palIndex * 3];
for (int rgbIndex = 0; rgbIndex < 3; ++rgbIndex, ++pal1P, ++pal2P, ++destP) {
*destP = (int)*pal1P + ((int)*pal2P - (int)*pal1P) * percent / 100;
}
}
screen.setPalette(tempPalette);
events.wait(1);
}
// Set final palette
screen.setPalette(palette);
}
} // end of namespace TsAGE
} // end of namespace Scalpel
} // end of namespace Sherlock

View File

@@ -0,0 +1,249 @@
/* 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/>.
*
*/
#ifndef SHERLOCK_SCALPEL_TSAGE_LOGO_H
#define SHERLOCK_SCALPEL_TSAGE_LOGO_H
#include "common/scummsys.h"
#include "common/array.h"
#include "common/file.h"
#include "common/list.h"
#include "common/str.h"
#include "common/str-array.h"
#include "common/util.h"
#include "graphics/surface.h"
#include "sherlock/scalpel/tsage/resources.h"
#include "sherlock/screen.h"
namespace Sherlock {
namespace Scalpel {
class ScalpelEngine;
namespace TsAGE {
class ObjectSurface : public Surface {
public:
Common::Point _centroid;
public:
ObjectSurface() : Surface() {}
~ObjectSurface() override {}
};
class Visage {
private:
Common::SeekableReadStream *_stream;
/**
* Translates a raw image resource into a graphics surface
*/
void surfaceFromRes(ObjectSurface &s);
public:
static TLib *_tLib;
int _resNum;
int _rlbNum;
public:
Visage();
~Visage();
/**
* Set the visage number
*/
void setVisage(int resNum, int rlbNum = 9999);
/**
* Clear the visage
*/
void clear();
/**
* Get a frame from the visage
*/
void getFrame(ObjectSurface &s, int frameNum);
/**
* Return the number of frames
*/
int getFrameCount() const;
/**
* Returns whether the visage is loaded
*/
bool isLoaded() const;
};
class Object {
private:
Visage _visage;
uint32 _updateStartFrame;
bool _isAnimating;
bool _finished;
uint32 _walkStartFrame;
int _angle;
int _changeCtr;
int _majorDiff, _minorDiff;
Common::Point _moveDelta;
Common::Point _moveSign;
/**
* Return the next frame when the object is animating
*/
int changeFrame();
/**
* Gets the next frame in the sequence
*/
int getNewFrame();
/**
* Calculate the angle between the current position and a designated destination
*/
void calculateMoveAngle();
/**
* Handle any object movement
*/
void move();
/**
* Returns whether not to make any movement
*/
bool dontMove() const;
/**
* Ends any current movement
*/
void endMove();
public:
static ScalpelEngine *_vm;
Common::Point _position;
Common::Point _destination;
Common::Rect _oldBounds;
int _frame;
int _numFrames;
int _frameChange;
public:
Object();
/**
* Load the data for the object
*/
void setVisage(int visage, int strip);
/**
* Sets whether the object is animating
*/
void setAnimMode(bool isAnimating);
/**
* Starts an object moving to a given destination
*/
void setDestination(const Common::Point &pt);
/**
* Returns true if an animation is ended
*/
bool isAnimEnded() const;
/**
* Return true if object is moving
*/
bool isMoving() const;
/**
* Erase the area the object was previously drawn at, by restoring the background
*/
void erase();
/**
* Update the frame
*/
void update();
/**
* Remove an object from being displayed
*/
void remove() { _visage.clear(); }
};
struct AnimationFrame {
int frame;
int x;
int y;
};
class Logo {
private:
ScalpelEngine *_vm;
TLib _lib;
int _counter, _frameCounter;
bool _finished;
byte _originalPalette[Graphics::PALETTE_SIZE];
byte _palette1[Graphics::PALETTE_SIZE];
byte _palette2[Graphics::PALETTE_SIZE];
byte _palette3[Graphics::PALETTE_SIZE];
Object _objects[4];
uint _waitFrames;
uint32 _waitStartFrame;
int _animateObject;
uint32 _animateStartFrame;
uint _animateFrameDelay;
const AnimationFrame *_animateFrames;
uint _animateFrame;
Logo(ScalpelEngine *vm);
~Logo();
void nextFrame();
bool finished() const;
/**
* Wait for a number of frames. Note that the frame count in _events is
* not the same as the number of calls to nextFrame().
*/
void waitFrames(uint frames);
/**
* Start an animation sequence. Used for sequences that are described
* one frame at a time because they do unusual things, or run at
* unusual rates.
*/
void startAnimation(uint object, uint frameDelay, const AnimationFrame *frames);
/**
* Load the background for the scene
*/
void loadBackground();
/**
* Fade from the current palette to a new one
*/
void fade(const byte palette[Graphics::PALETTE_SIZE], int step = 6);
public:
static bool show(ScalpelEngine *vm);
};
} // end of namespace TsAGE
} // end of namespace Scalpel
} // end of namespace Sherlock
#endif

View File

@@ -0,0 +1,372 @@
/* 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/scummsys.h"
#include "common/memstream.h"
#include "common/stack.h"
#include "sherlock/scalpel/tsage/resources.h"
namespace Sherlock {
namespace Scalpel {
namespace TsAGE {
static uint16 bitMasks[4] = {0x1ff, 0x3ff, 0x7ff, 0xfff};
uint16 BitReader::readToken() {
assert((numBits >= 9) && (numBits <= 12));
uint16 result = _remainder;
int bitsLeft = numBits - _bitsLeft;
int bitOffset = _bitsLeft;
_bitsLeft = 0;
while (bitsLeft >= 0) {
_remainder = readByte();
result |= _remainder << bitOffset;
bitsLeft -= 8;
bitOffset += 8;
}
_bitsLeft = -bitsLeft;
_remainder >>= 8 - _bitsLeft;
return result & bitMasks[numBits - 9];
}
/*-------------------------------------------------------------------------*/
TLib::TLib(const Common::Path &filename) : _filename(filename) {
// If the resource strings list isn't yet loaded, load them
if (_resStrings.size() == 0) {
Common::File f;
if (f.open("tsage.cfg")) {
while (!f.eos()) {
_resStrings.push_back(f.readLine());
}
f.close();
}
}
if (!_file.open(filename))
error("Missing file %s", filename.toString().c_str());
loadIndex();
}
TLib::~TLib() {
_resStrings.clear();
}
/**
* Load a section index from the given position in the file
*/
void TLib::loadSection(uint32 fileOffset) {
_resources.clear();
_file.seek(fileOffset);
_sections.fileOffset = fileOffset;
loadSection(_file, _resources);
}
struct DecodeReference {
uint16 vWord;
uint8 vByte;
};
/**
* Gets a resource from the currently loaded section
*/
Common::SeekableReadStream *TLib::getResource(uint16 id, bool suppressErrors) {
// Scan for an entry for the given Id
ResourceEntry *re = nullptr;
ResourceList::iterator iter;
for (iter = _resources.begin(); iter != _resources.end(); ++iter) {
if ((*iter).id == id) {
re = &(*iter);
break;
}
}
if (!re) {
if (suppressErrors)
return nullptr;
error("Could not find resource Id #%d", id);
}
if (!re->isCompressed) {
// Read in the resource data and return it
byte *dataP = (byte *)malloc(re->size);
_file.seek(_sections.fileOffset + re->fileOffset);
_file.read(dataP, re->size);
return new Common::MemoryReadStream(dataP, re->size, DisposeAfterUse::YES);
}
/*
* Decompress the data block
*/
_file.seek(_sections.fileOffset + re->fileOffset);
Common::ReadStream *compStream = _file.readStream(re->size);
BitReader bitReader(*compStream);
byte *dataOut = (byte *)malloc(re->uncompressedSize);
byte *destP = dataOut;
uint bytesWritten = 0;
uint16 ctrCurrent = 0x102, ctrMax = 0x200;
uint16 word_48050 = 0, currentToken = 0, word_48054 =0;
byte byte_49068 = 0, byte_49069 = 0;
const uint tableSize = 0x1000;
DecodeReference *table = (DecodeReference *)malloc(tableSize * sizeof(DecodeReference));
if (!table)
error("[TLib::getResource] Cannot allocate table buffer");
for (uint i = 0; i < tableSize; ++i) {
table[i].vByte = table[i].vWord = 0;
}
Common::Stack<uint16> tokenList;
for (;;) {
// Get the next decode token
uint16 token = bitReader.readToken();
// Handle the token
if (token == 0x101) {
// End of compressed stream
break;
} else if (token == 0x100) {
// Reset bit-rate
bitReader.numBits = 9;
ctrMax = 0x200;
ctrCurrent = 0x102;
// Set variables with next token
currentToken = word_48050 = bitReader.readToken();
byte_49069 = byte_49068 = (byte)currentToken;
++bytesWritten;
assert(bytesWritten <= re->uncompressedSize);
*destP++ = byte_49069;
} else {
word_48054 = word_48050 = token;
if (token >= ctrCurrent) {
word_48050 = currentToken;
tokenList.push(byte_49068);
}
while (word_48050 >= 0x100) {
assert(word_48050 < 0x1000);
tokenList.push(table[word_48050].vByte);
word_48050 = table[word_48050].vWord;
}
byte_49069 = byte_49068 = (byte)word_48050;
tokenList.push(word_48050);
// Write out any cached tokens
while (!tokenList.empty()) {
++bytesWritten;
assert(bytesWritten <= re->uncompressedSize);
*destP++ = tokenList.pop();
}
assert(ctrCurrent < 0x1000);
table[ctrCurrent].vByte = byte_49069;
table[ctrCurrent].vWord = currentToken;
++ctrCurrent;
currentToken = word_48054;
if ((ctrCurrent >= ctrMax) && (bitReader.numBits != 12)) {
// Move to the next higher bit-rate
++bitReader.numBits;
ctrMax <<= 1;
}
}
}
free(table);
assert(bytesWritten == re->uncompressedSize);
delete compStream;
return new Common::MemoryReadStream(dataOut, re->uncompressedSize, DisposeAfterUse::YES);
}
/**
* Finds the correct section and loads the specified resource within it
*/
Common::SeekableReadStream *TLib::getResource(ResourceType resType, uint16 resNum, uint16 rlbNum, bool suppressErrors) {
SectionList::iterator i = _sections.begin();
while ((i != _sections.end()) && ((*i).resType != resType || (*i).resNum != resNum))
++i;
if (i == _sections.end()) {
if (suppressErrors)
return nullptr;
error("Unknown resource type %d num %d", resType, resNum);
}
loadSection((*i).fileOffset);
return getResource(rlbNum, suppressErrors);
}
/**
* Gets the offset of the start of a resource in the resource file
*/
uint32 TLib::getResourceStart(ResourceType resType, uint16 resNum, uint16 rlbNum, ResourceEntry &entry) {
// Find the correct section
SectionList::iterator i = _sections.begin();
while ((i != _sections.end()) && ((*i).resType != resType || (*i).resNum != resNum))
++i;
if (i == _sections.end()) {
error("Unknown resource type %d num %d", resType, resNum);
}
// Load in the section index
loadSection((*i).fileOffset);
// Scan for an entry for the given Id
ResourceEntry *re = nullptr;
ResourceList::iterator iter;
for (iter = _resources.begin(); iter != _resources.end(); ++iter) {
if ((*iter).id == rlbNum) {
re = &(*iter);
break;
}
}
// Throw an error if no resource was found, or the resource is compressed
if (!re || re->isCompressed)
error("Invalid resource Id #%d", rlbNum);
// Return the resource entry as well as the file offset
entry = *re;
return _sections.fileOffset + entry.fileOffset;
}
void TLib::loadIndex() {
uint16 resNum, configId, fileOffset;
// Load the root resources section
loadSection(0);
// Get the single resource from it
Common::SeekableReadStream *stream = getResource(0);
_sections.clear();
// Loop through reading the entries
while ((resNum = stream->readUint16LE()) != 0xffff) {
configId = stream->readUint16LE();
fileOffset = stream->readUint16LE();
SectionEntry se;
se.resNum = resNum;
se.resType = (ResourceType)(configId & 0x1f);
se.fileOffset = (((configId >> 5) & 0x7ff) << 16) | fileOffset;
_sections.push_back(se);
}
delete stream;
}
/**
* Retrieves the specified palette resource and returns it's data
*
* @paletteNum Specefies the palette number
*/
void TLib::getPalette(byte palette[Graphics::PALETTE_SIZE], int paletteNum) {
// Get the specified palette
Common::SeekableReadStream *stream = getResource(RES_PALETTE, paletteNum, 0, true);
if (!stream)
return;
int startNum = stream->readUint16LE();
int numEntries = stream->readUint16LE();
assert((startNum < 256) && ((startNum + numEntries) <= 256));
stream->skip(2);
// Copy over the data
stream->read(&palette[startNum * 3], numEntries * 3);
delete stream;
}
/**
* Open up the given resource file using a passed file object. If the desired entry is found
* in the index, return the index entry for it, and move the file to the start of the resource
*/
bool TLib::scanIndex(Common::File &f, ResourceType resType, int rlbNum, int resNum,
ResourceEntry &resEntry) {
// Load the root section index
ResourceList resList;
loadSection(f, resList);
// Loop through the index for the desired entry
ResourceList::iterator iter;
for (iter = resList.begin(); iter != resList.end(); ++iter) {
ResourceEntry &re = *iter;
if (re.id == resNum) {
// Found it, so exit
resEntry = re;
f.seek(re.fileOffset);
return true;
}
}
// No matching entry found
return false;
}
/**
* Inner logic for decoding a section index into a passed resource list object
*/
void TLib::loadSection(Common::File &f, ResourceList &resources) {
if (f.readUint32BE() != 0x544D492D)
error("Data block is not valid Rlb data");
/*uint8 unknown1 = */f.readByte();
uint16 numEntries = f.readByte();
for (uint i = 0; i < numEntries; ++i) {
uint16 id = f.readUint16LE();
uint16 size = f.readUint16LE();
uint16 uncSize = f.readUint16LE();
uint8 sizeHi = f.readByte();
uint8 type = f.readByte() >> 5;
assert(type <= 1);
uint32 offset = f.readUint32LE();
ResourceEntry re;
re.id = id;
re.fileOffset = offset;
re.isCompressed = type != 0;
re.size = ((sizeHi & 0xF) << 16) | size;
re.uncompressedSize = ((sizeHi & 0xF0) << 12) | uncSize;
resources.push_back(re);
}
}
} // end of namespace TsAGE
} // end of namespace Scalpel
} // end of namespace Sherlock

View File

@@ -0,0 +1,138 @@
/* 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/>.
*
*/
#ifndef SHERLOCK_SCALPEL_TSAGE_RESOURCES_H
#define SHERLOCK_SCALPEL_TSAGE_RESOURCES_H
#include "common/scummsys.h"
#include "common/array.h"
#include "common/file.h"
#include "common/list.h"
#include "common/str.h"
#include "common/str-array.h"
#include "common/util.h"
#include "graphics/surface.h"
#include "sherlock/screen.h"
namespace Sherlock {
namespace Scalpel {
namespace TsAGE {
// Magic number used by original game to identify valid memory blocks
const uint32 MEMORY_ENTRY_ID = 0xE11DA722;
const int MEMORY_POOL_SIZE = 1000;
enum ResourceType { RES_LIBRARY, RES_STRIP, RES_IMAGE, RES_PALETTE, RES_VISAGE, RES_SOUND, RES_MESSAGE,
RES_FONT, RES_POINTER, RES_BANK, RES_SND_DRIVER, RES_PRIORITY, RES_CONTROL, RES_WALKRGNS,
RES_BITMAP, RES_SAVE, RES_SEQUENCE,
// Return to Ringworld specific resource types
RT17, RT18, RT19, RT20, RT21, RT22, RT23, RT24, RT25, RT26, RT27, RT28, RT29, RT30, RT31
};
class SectionEntry {
public:
ResourceType resType;
uint16 resNum;
uint32 fileOffset;
SectionEntry() {
resType = RES_LIBRARY;
resNum = 0;
fileOffset = 0;
}
};
class ResourceEntry {
public:
uint16 id;
bool isCompressed;
uint32 fileOffset;
uint32 size;
uint32 uncompressedSize;
ResourceEntry() {
id = 0;
isCompressed = false;
fileOffset = 0;
size = 0;
uncompressedSize = 0;
}
};
typedef Common::List<ResourceEntry> ResourceList;
class SectionList : public Common::List<SectionEntry> {
public:
uint32 fileOffset;
SectionList() {
fileOffset = 0;
}
};
class BitReader {
private:
Common::ReadStream &_stream;
uint8 _remainder, _bitsLeft;
byte readByte() { return _stream.eos() ? 0 : _stream.readByte(); }
public:
BitReader(Common::ReadStream &s) : _stream(s) {
numBits = 9;
_remainder = 0;
_bitsLeft = 0;
}
uint16 readToken();
int numBits;
};
class TLib {
private:
Common::StringArray _resStrings;
private:
Common::File _file;
Common::Path _filename;
ResourceList _resources;
SectionList _sections;
void loadSection(uint32 fileOffset);
void loadIndex();
static bool scanIndex(Common::File &f, ResourceType resType, int rlbNum, int resNum, ResourceEntry &resEntry);
static void loadSection(Common::File &f, ResourceList &resources);
public:
TLib(const Common::Path &filename);
~TLib();
const Common::Path &getFilename() { return _filename; }
const SectionList &getSections() { return _sections; }
Common::SeekableReadStream *getResource(uint16 id, bool suppressErrors = false);
Common::SeekableReadStream *getResource(ResourceType resType, uint16 resNum, uint16 rlbNum, bool suppressErrors = false);
uint32 getResourceStart(ResourceType resType, uint16 resNum, uint16 rlbNum, ResourceEntry &entry);
void getPalette(byte palette[Graphics::PALETTE_SIZE], int paletteNum);
};
} // end of namespace TsAGE
} // end of namespace Scalpel
} // end of namespace Sherlock
#endif