scummvm/engines/sherlock/tattoo/tattoo_map.cpp
Torbjörn Andersson 454779e30a SHERLOCK: Fix Rose Tattoo glitch when opening map
When the map was displayed, there would often be a noticeable glitch
where the map jumped from the upper left corner to the saved position.
This removes that initial map display and relies on scrolling to always
move it to the correct position.

This fixes #10850
2021-12-02 20:18:28 -08:00

439 lines
13 KiB
C++

/* ScummVM - Graphic Adventure Engine
*
* ScummVM is the legal property of its developers, whose names
* are too numerous to list here. Please refer to the COPYRIGHT
* file distributed with this source distribution.
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation; either version 2
* 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, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*
*/
#include "sherlock/tattoo/tattoo_map.h"
#include "sherlock/tattoo/tattoo_scene.h"
#include "sherlock/tattoo/tattoo.h"
namespace Sherlock {
namespace Tattoo {
#define MAP_NAME_COLOR 131
#define CLOSEUP_STEPS 30
#define SCROLL_SPEED 16
/*-------------------------------------------------------------------------*/
void MapEntry::clear() {
_iconNum = -1;
_description = "";
}
/*-------------------------------------------------------------------------*/
TattooMap::TattooMap(SherlockEngine *vm) : Map(vm), _mapTooltip(vm) {
_iconImages = nullptr;
_bgFound = _oldBgFound = 0;
loadData();
}
int TattooMap::show() {
Debugger &debugger = *_vm->_debugger;
Events &events = *_vm->_events;
Music &music = *_vm->_music;
Resources &res = *_vm->_res;
TattooScene &scene = *(TattooScene *)_vm->_scene;
Screen &screen = *_vm->_screen;
int result = 0;
// Check if we need to keep track of how many times player has been to the map
for (uint idx = 0; idx < scene._sceneTripCounters.size(); ++idx) {
SceneTripEntry &entry = scene._sceneTripCounters[idx];
if (entry._sceneNumber == OVERHEAD_MAP || entry._sceneNumber == OVERHEAD_MAP2) {
if (--entry._numTimes == 0) {
_vm->setFlagsDirect(entry._flag);
scene._sceneTripCounters.remove_at(idx);
}
}
}
if (music._musicOn) {
// See if Holmes or Watson is the active character
Common::String song;
if (_vm->readFlags(FLAG_PLAYER_IS_HOLMES))
// Player is Holmes
song = "Cue9";
else if (_vm->readFlags(FLAG_ALT_MAP_MUSIC))
song = "Cue8";
else
song = "Cue7";
if (music.loadSong(song)) {
music.startSong();
}
}
screen.initPaletteFade(1364485);
// Load the custom mouse cursors for the map
ImageFile cursors("omouse.vgs");
events.setCursor(cursors[0]._frame);
events.warpMouse();
// Load the data for the map
_iconImages = new ImageFile("mapicons.vgs");
loadData();
// Load the palette
Common::SeekableReadStream *stream = res.load("map.pal");
stream->read(screen._cMap, PALETTE_SIZE);
screen.translatePalette(screen._cMap);
delete stream;
// Load the map image and draw it to the back buffer
ImageFile *map = new ImageFile("map.vgs");
screen._backBuffer1.create(SHERLOCK_SCREEN_WIDTH * 2, SHERLOCK_SCREEN_HEIGHT * 2);
screen._backBuffer1.SHblitFrom((*map)[0], Common::Point(0, 0));
screen.activateBackBuffer1();
delete map;
screen.clear();
screen.setPalette(screen._cMap);
drawMapIcons();
// Copy the map drawn in the back buffer to the secondary back buffer
screen._backBuffer2.create(SHERLOCK_SCREEN_WIDTH * 2, SHERLOCK_SCREEN_HEIGHT * 2);
screen._backBuffer2.SHblitFrom(screen._backBuffer1);
// Set initial scroll position, forcing the map to be displayed
_targetScroll = _bigPos;
screen._currentScroll = Common::Point(-1, -1);
do {
// Allow for event processing and get the current mouse position
events.pollEventsAndWait();
events.setButtonState();
Common::Point mousePos = events.screenMousePos();
if (debugger._showAllLocations == LOC_REFRESH) {
drawMapIcons();
screen.slamArea(screen._currentScroll.x, screen._currentScroll.y, SHERLOCK_SCREEN_WIDTH, SHERLOCK_SCREEN_WIDTH);
}
music.checkSongProgress();
checkMapNames(true);
if (mousePos.x < (SHERLOCK_SCREEN_WIDTH / 6))
_targetScroll.x -= 2 * SCROLL_SPEED * (SHERLOCK_SCREEN_WIDTH / 6 - mousePos.x) / (SHERLOCK_SCREEN_WIDTH / 6);
if (mousePos.x > (SHERLOCK_SCREEN_WIDTH * 5 / 6))
_targetScroll.x += 2 * SCROLL_SPEED * (mousePos.x - (SHERLOCK_SCREEN_WIDTH * 5 / 6)) / (SHERLOCK_SCREEN_WIDTH / 6);
if (mousePos.y < (SHERLOCK_SCREEN_HEIGHT / 6))
_targetScroll.y -= 2 * SCROLL_SPEED * (SHERLOCK_SCREEN_HEIGHT / 6 - mousePos.y) / (SHERLOCK_SCREEN_HEIGHT / 6);
if (mousePos.y > (SHERLOCK_SCREEN_HEIGHT * 5 / 6))
_targetScroll.y += 2 * SCROLL_SPEED * (mousePos.y - SHERLOCK_SCREEN_HEIGHT * 5 / 6) / (SHERLOCK_SCREEN_HEIGHT / 6);
if (_targetScroll.x < 0)
_targetScroll.x = 0;
if ((_targetScroll.x + SHERLOCK_SCREEN_WIDTH) > screen._backBuffer1.width())
_targetScroll.x = screen._backBuffer1.width() - SHERLOCK_SCREEN_WIDTH;
if (_targetScroll.y < 0)
_targetScroll.y = 0;
if ((_targetScroll.y + SHERLOCK_SCREEN_HEIGHT) > screen._backBuffer1.height())
_targetScroll.y = screen._backBuffer1.height() - SHERLOCK_SCREEN_HEIGHT;
// Check the keyboard
if (events.kbHit()) {
Common::KeyState keyState = events.getKey();
switch (keyState.keycode) {
case Common::KEYCODE_HOME:
_targetScroll.x = 0;
_targetScroll.y = 0;
break;
case Common::KEYCODE_END:
_targetScroll.x = screen._backBuffer1.width() - SHERLOCK_SCREEN_WIDTH;
_targetScroll.y = screen._backBuffer1.height() - SHERLOCK_SCREEN_HEIGHT;
break;
case Common::KEYCODE_PAGEUP:
_targetScroll.y -= SHERLOCK_SCREEN_HEIGHT;
if (_targetScroll.y < 0)
_targetScroll.y = 0;
break;
case Common::KEYCODE_PAGEDOWN:
_targetScroll.y += SHERLOCK_SCREEN_HEIGHT;
if (_targetScroll.y > (screen._backBuffer1.height() - SHERLOCK_SCREEN_HEIGHT))
_targetScroll.y = screen._backBuffer1.height() - SHERLOCK_SCREEN_HEIGHT;
break;
case Common::KEYCODE_SPACE:
events._pressed = false;
events._oldButtons = 0;
events._released = true;
break;
default:
break;
}
}
// Handle any scrolling of the map
if (screen._currentScroll != _targetScroll) {
// If there is a Text description being displayed, restore the area under it
_mapTooltip.erase();
screen._currentScroll = _targetScroll;
checkMapNames(false);
screen.slamArea(_targetScroll.x, _targetScroll.y, SHERLOCK_SCREEN_WIDTH, SHERLOCK_SCREEN_HEIGHT);
}
// Handling if a location has been clicked on
if (events._released && _bgFound != -1) {
// If there is a Text description being displayed, restore the area under it
_mapTooltip.erase();
// Save the current scroll position on the map
_bigPos = screen._currentScroll;
showCloseUp(_bgFound);
result = _bgFound + 1;
}
} while (!result && !_vm->shouldQuit());
music.stopMusic();
events.clearEvents();
_mapTooltip.banishWindow();
// Reset the back buffers back to standard size
screen._backBuffer1.create(SHERLOCK_SCREEN_WIDTH, SHERLOCK_SCREEN_HEIGHT);
screen._backBuffer2.create(SHERLOCK_SCREEN_WIDTH, SHERLOCK_SCREEN_HEIGHT);
screen.activateBackBuffer1();
return result;
}
void TattooMap::loadData() {
Resources &res = *_vm->_res;
char c;
Common::SeekableReadStream *stream = res.load("map.txt");
_data.resize(100);
for (uint idx = 0; idx < _data.size(); ++idx)
_data[idx].clear();
do
{
// Find the start of the number
do {
c = stream->readByte();
if (stream->pos() >= stream->size())
break;
} while (c < '0' || c > '9');
if (stream->pos() >= stream->size())
break;
// Get the scene number
Common::String locStr;
locStr += c;
while ((c = stream->readByte()) != '.')
locStr += c;
MapEntry &mapEntry = _data[atoi(locStr.c_str()) - 1];
// Get the location name
while (stream->readByte() != '"')
;
while ((c = stream->readByte()) != '"')
mapEntry._description += c;
// Find the ( specifying the (X,Y) position of the Icon
while (stream->readByte() != '(')
;
// Get the X Position of the icon
Common::String numStr;
while ((c = stream->readByte()) != ',')
numStr += c;
mapEntry.x = atoi(numStr.c_str());
// Get the Y position of the icon
numStr = "";
while ((c = stream->readByte()) != ')')
numStr += c;
mapEntry.y = atoi(numStr.c_str());
// Find and get the location's icon number
while (stream->readByte() != '#')
;
Common::String iconStr;
while (stream->pos() < stream->size() && (c = stream->readByte()) != '\r')
iconStr += c;
mapEntry._iconNum = atoi(iconStr.c_str()) - 1;
} while (stream->pos() < stream->size());
delete stream;
}
void TattooMap::drawMapIcons() {
Debugger &debugger = *_vm->_debugger;
Screen &screen = *_vm->_screen;
for (uint idx = 0; idx < _data.size(); ++idx) {
if (debugger._showAllLocations != LOC_DISABLED)
_vm->setFlagsDirect(idx + 1);
if (_data[idx]._iconNum != -1 && _vm->readFlags(idx + 1)) {
MapEntry &mapEntry = _data[idx];
ImageFrame &img = (*_iconImages)[mapEntry._iconNum];
screen._backBuffer1.SHtransBlitFrom(img._frame, Common::Point(mapEntry.x - img._width / 2,
mapEntry.y - img._height / 2));
}
}
if (debugger._showAllLocations == LOC_REFRESH)
debugger._showAllLocations = LOC_ALL;
}
void TattooMap::checkMapNames(bool slamIt) {
Events &events = *_vm->_events;
Common::Point mapPos = events.mousePos();
// See if the mouse is pointing at any of the map locations
_bgFound = -1;
for (uint idx = 0; idx < _data.size(); ++idx) {
if (_data[idx]._iconNum != -1 && _vm->readFlags(idx + 1)) {
MapEntry &mapEntry = _data[idx];
ImageFrame &img = (*_iconImages)[mapEntry._iconNum];
Common::Rect r(mapEntry.x - img._width / 2, mapEntry.y - img._height / 2,
mapEntry.x + img._width / 2, mapEntry.y + img._height / 2);
if (r.contains(mapPos)) {
_bgFound = idx;
break;
}
}
}
// Handle updating the tooltip
if (_bgFound != _oldBgFound) {
if (_bgFound == -1) {
_mapTooltip.setText("");
} else {
const Common::String &desc = _data[_bgFound]._description;
_mapTooltip.setText(desc);
}
_oldBgFound = _bgFound;
}
_mapTooltip.handleEvents();
if (slamIt)
_mapTooltip.draw();
}
void TattooMap::restoreArea(const Common::Rect &bounds) {
Screen &screen = *_vm->_screen;
Common::Rect r = bounds;
r.clip(Common::Rect(0, 0, screen._backBuffer1.width(), screen._backBuffer1.height()));
if (!r.isEmpty())
screen._backBuffer1.SHblitFrom(screen._backBuffer2, Common::Point(r.left, r.top), r);
}
void TattooMap::showCloseUp(int closeUpNum) {
Events &events = *_vm->_events;
Screen &screen = *_vm->_screen;
// Hide the cursor
events.hideCursor();
// Get the closeup images
Common::String fname = Common::String::format("res%02d.vgs", closeUpNum + 1);
ImageFile pic(fname);
Point32 closeUp(_data[closeUpNum].x * 100, _data[closeUpNum].y * 100);
Point32 delta((SHERLOCK_SCREEN_WIDTH / 2 - closeUp.x / 100) * 100 / CLOSEUP_STEPS,
(SHERLOCK_SCREEN_HEIGHT / 2 - closeUp.y / 100) * 100 / CLOSEUP_STEPS);
Common::Rect oldBounds(closeUp.x / 100, closeUp.y / 100, closeUp.x / 100 + 1, closeUp.y / 100 + 1);
int size = 64;
int n = 256;
int deltaVal = 512;
bool minimize = false;
int scaleVal, newSize;
do {
scaleVal = n;
newSize = pic[0].sDrawXSize(n);
if (newSize > size) {
if (minimize)
deltaVal /= 2;
n += deltaVal;
} else {
minimize = true;
deltaVal /= 2;
n -= deltaVal;
if (n < 1)
n = 1;
}
} while (deltaVal && size != newSize);
int deltaScale = (SCALE_THRESHOLD - scaleVal) / CLOSEUP_STEPS;
for (int step = 0; step < CLOSEUP_STEPS; ++step) {
Common::Point picSize(pic[0].sDrawXSize(scaleVal), pic[0].sDrawYSize(scaleVal));
Common::Point pt(screen._currentScroll.x + closeUp.x / 100 - picSize.x / 2,
screen._currentScroll.y + closeUp.y / 100 - picSize.y / 2);
restoreArea(oldBounds);
screen._backBuffer1.SHtransBlitFrom(pic[0], pt, false, 0, scaleVal);
screen.slamRect(oldBounds);
screen.slamArea(pt.x, pt.y, picSize.x, picSize.y);
oldBounds = Common::Rect(pt.x, pt.y, pt.x + picSize.x + 1, pt.y + picSize.y + 1);
closeUp += delta;
scaleVal += deltaScale;
events.wait(1);
}
// Handle final drawing of closeup
Common::Rect r(screen._currentScroll.x + SHERLOCK_SCREEN_WIDTH / 2 - pic[0]._width / 2,
screen._currentScroll.y + SHERLOCK_SCREEN_HEIGHT / 2 - pic[0]._height / 2,
screen._currentScroll.x + SHERLOCK_SCREEN_WIDTH / 2 - pic[0]._width / 2 + pic[0]._width,
screen._currentScroll.y + SHERLOCK_SCREEN_HEIGHT / 2 - pic[0]._height / 2 + pic[0]._height);
restoreArea(oldBounds);
screen._backBuffer1.SHtransBlitFrom(pic[0], Common::Point(r.left, r.top));
screen.slamRect(oldBounds);
screen.slamRect(r);
events.wait(60);
events.showCursor();
}
} // End of namespace Tattoo
} // End of namespace Sherlock