1 /* ScummVM - Graphic Adventure Engine
2  *
3  * ScummVM is the legal property of its developers, whose names
4  * are too numerous to list here. Please refer to the COPYRIGHT
5  * file distributed with this source distribution.
6  *
7  * This program is free software; you can redistribute it and/or
8  * modify it under the terms of the GNU General Public License
9  * as published by the Free Software Foundation; either version 2
10  * of the License, or (at your option) any later version.
11  *
12  * This program is distributed in the hope that it will be useful,
13  * but WITHOUT ANY WARRANTY; without even the implied warranty of
14  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
15  * GNU General Public License for more details.
16  *
17  * You should have received a copy of the GNU General Public License
18  * along with this program; if not, write to the Free Software
19  * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
20  *
21  */
22 
23 #include "sherlock/tattoo/tattoo_map.h"
24 #include "sherlock/tattoo/tattoo_scene.h"
25 #include "sherlock/tattoo/tattoo.h"
26 
27 namespace Sherlock {
28 
29 namespace Tattoo {
30 
31 #define MAP_NAME_COLOR 131
32 #define CLOSEUP_STEPS 30
33 #define SCROLL_SPEED 16
34 
35 /*-------------------------------------------------------------------------*/
36 
clear()37 void MapEntry::clear() {
38 	_iconNum = -1;
39 	_description = "";
40 }
41 
42 /*-------------------------------------------------------------------------*/
43 
TattooMap(SherlockEngine * vm)44 TattooMap::TattooMap(SherlockEngine *vm) : Map(vm), _mapTooltip(vm) {
45 	_iconImages = nullptr;
46 	_bgFound = _oldBgFound = 0;
47 
48 	loadData();
49 }
50 
show()51 int TattooMap::show() {
52 	Debugger &debugger = *_vm->_debugger;
53 	Events &events = *_vm->_events;
54 	Music &music = *_vm->_music;
55 	Resources &res = *_vm->_res;
56 	TattooScene &scene = *(TattooScene *)_vm->_scene;
57 	Screen &screen = *_vm->_screen;
58 	int result = 0;
59 
60 	// Check if we need to keep track of how many times player has been to the map
61 	for (uint idx = 0; idx < scene._sceneTripCounters.size(); ++idx) {
62 		SceneTripEntry &entry = scene._sceneTripCounters[idx];
63 
64 		if (entry._sceneNumber == OVERHEAD_MAP || entry._sceneNumber == OVERHEAD_MAP2) {
65 			if (--entry._numTimes == 0) {
66 				_vm->setFlagsDirect(entry._flag);
67 				scene._sceneTripCounters.remove_at(idx);
68 			}
69 		}
70 	}
71 
72 	if (music._musicOn) {
73 		// See if Holmes or Watson is the active character
74 		Common::String song;
75 		if (_vm->readFlags(FLAG_PLAYER_IS_HOLMES))
76 			// Player is Holmes
77 			song = "Cue9";
78 		else if (_vm->readFlags(FLAG_ALT_MAP_MUSIC))
79 			song = "Cue8";
80 		else
81 			song = "Cue7";
82 
83 		if (music.loadSong(song)) {
84 			music.startSong();
85 		}
86 	}
87 
88 	screen.initPaletteFade(1364485);
89 
90 	// Load the custom mouse cursors for the map
91 	ImageFile cursors("omouse.vgs");
92 	events.setCursor(cursors[0]._frame);
93 	events.warpMouse();
94 
95 	// Load the data for the map
96 	_iconImages = new ImageFile("mapicons.vgs");
97 	loadData();
98 
99 	// Load the palette
100 	Common::SeekableReadStream *stream = res.load("map.pal");
101 	stream->read(screen._cMap, PALETTE_SIZE);
102 	screen.translatePalette(screen._cMap);
103 	delete stream;
104 
105 	// Load the map image and draw it to the back buffer
106 	ImageFile *map = new ImageFile("map.vgs");
107 	screen._backBuffer1.create(SHERLOCK_SCREEN_WIDTH * 2, SHERLOCK_SCREEN_HEIGHT * 2);
108 	screen._backBuffer1.SHblitFrom((*map)[0], Common::Point(0, 0));
109 	screen.activateBackBuffer1();
110 	delete map;
111 
112 	screen.clear();
113 	screen.setPalette(screen._cMap);
114 	drawMapIcons();
115 
116 	// Copy the map drawn in the back buffer to the secondary back buffer
117 	screen._backBuffer2.create(SHERLOCK_SCREEN_WIDTH * 2, SHERLOCK_SCREEN_HEIGHT * 2);
118 	screen._backBuffer2.SHblitFrom(screen._backBuffer1);
119 
120 	// Display the built map to the screen
121 	screen.slamArea(0, 0, SHERLOCK_SCREEN_WIDTH, SHERLOCK_SCREEN_HEIGHT);
122 
123 	// Set initial scroll position
124 	_targetScroll = _bigPos;
125 	screen._currentScroll = Common::Point(-1, -1);
126 
127 	do {
128 		// Allow for event processing and get the current mouse position
129 		events.pollEventsAndWait();
130 		events.setButtonState();
131 		Common::Point mousePos = events.screenMousePos();
132 
133 		if (debugger._showAllLocations == LOC_REFRESH) {
134 			drawMapIcons();
135 			screen.slamArea(screen._currentScroll.x, screen._currentScroll.y, SHERLOCK_SCREEN_WIDTH, SHERLOCK_SCREEN_WIDTH);
136 		}
137 
138 		music.checkSongProgress();
139 		checkMapNames(true);
140 
141 		if (mousePos.x < (SHERLOCK_SCREEN_WIDTH / 6))
142 			_targetScroll.x -= 2 * SCROLL_SPEED * (SHERLOCK_SCREEN_WIDTH / 6 - mousePos.x) / (SHERLOCK_SCREEN_WIDTH / 6);
143 		if (mousePos.x > (SHERLOCK_SCREEN_WIDTH * 5 / 6))
144 			_targetScroll.x += 2 * SCROLL_SPEED * (mousePos.x - (SHERLOCK_SCREEN_WIDTH * 5 / 6)) / (SHERLOCK_SCREEN_WIDTH / 6);
145 		if (mousePos.y < (SHERLOCK_SCREEN_HEIGHT / 6))
146 			_targetScroll.y -= 2 * SCROLL_SPEED * (SHERLOCK_SCREEN_HEIGHT / 6 - mousePos.y) / (SHERLOCK_SCREEN_HEIGHT / 6);
147 		if (mousePos.y > (SHERLOCK_SCREEN_HEIGHT * 5 / 6))
148 			_targetScroll.y += 2 * SCROLL_SPEED * (mousePos.y - SHERLOCK_SCREEN_HEIGHT * 5 / 6) / (SHERLOCK_SCREEN_HEIGHT / 6);
149 
150 		if (_targetScroll.x < 0)
151 			_targetScroll.x = 0;
152 		if ((_targetScroll.x + SHERLOCK_SCREEN_WIDTH) > screen._backBuffer1.width())
153 			_targetScroll.x = screen._backBuffer1.width() - SHERLOCK_SCREEN_WIDTH;
154 		if (_targetScroll.y < 0)
155 			_targetScroll.y = 0;
156 		if ((_targetScroll.y + SHERLOCK_SCREEN_HEIGHT) > screen._backBuffer1.height())
157 			_targetScroll.y = screen._backBuffer1.height() - SHERLOCK_SCREEN_HEIGHT;
158 
159 		// Check the keyboard
160 		if (events.kbHit()) {
161 			Common::KeyState keyState = events.getKey();
162 
163 			switch (keyState.keycode) {
164 			case Common::KEYCODE_HOME:
165 				_targetScroll.x = 0;
166 				_targetScroll.y = 0;
167 				break;
168 
169 			case Common::KEYCODE_END:
170 				_targetScroll.x = screen._backBuffer1.width() - SHERLOCK_SCREEN_WIDTH;
171 				_targetScroll.y = screen._backBuffer1.height() - SHERLOCK_SCREEN_HEIGHT;
172 				break;
173 
174 			case Common::KEYCODE_PAGEUP:
175 				_targetScroll.y -= SHERLOCK_SCREEN_HEIGHT;
176 				if (_targetScroll.y < 0)
177 					_targetScroll.y = 0;
178 				break;
179 
180 			case Common::KEYCODE_PAGEDOWN:
181 				_targetScroll.y += SHERLOCK_SCREEN_HEIGHT;
182 				if (_targetScroll.y > (screen._backBuffer1.height() - SHERLOCK_SCREEN_HEIGHT))
183 					_targetScroll.y = screen._backBuffer1.height() - SHERLOCK_SCREEN_HEIGHT;
184 				break;
185 
186 			case Common::KEYCODE_SPACE:
187 				events._pressed = false;
188 				events._oldButtons = 0;
189 				events._released = true;
190 				break;
191 
192 			default:
193 				break;
194 			}
195 		}
196 
197 		// Handle any scrolling of the map
198 		if (screen._currentScroll != _targetScroll) {
199 			// If there is a Text description being displayed, restore the area under it
200 			_mapTooltip.erase();
201 
202 			screen._currentScroll = _targetScroll;
203 
204 			checkMapNames(false);
205 			screen.slamArea(_targetScroll.x, _targetScroll.y, SHERLOCK_SCREEN_WIDTH, SHERLOCK_SCREEN_HEIGHT);
206 		}
207 
208 		// Handling if a location has been clicked on
209 		if (events._released && _bgFound != -1) {
210 			// If there is a Text description being displayed, restore the area under it
211 			_mapTooltip.erase();
212 
213 			// Save the current scroll position on the map
214 			_bigPos = screen._currentScroll;
215 
216 			showCloseUp(_bgFound);
217 			result = _bgFound + 1;
218 		}
219 	} while (!result && !_vm->shouldQuit());
220 
221 	music.stopMusic();
222 	events.clearEvents();
223 	_mapTooltip.banishWindow();
224 
225 	// Reset the back buffers back to standard size
226 	screen._backBuffer1.create(SHERLOCK_SCREEN_WIDTH, SHERLOCK_SCREEN_HEIGHT);
227 	screen._backBuffer2.create(SHERLOCK_SCREEN_WIDTH, SHERLOCK_SCREEN_HEIGHT);
228 	screen.activateBackBuffer1();
229 
230 	return result;
231 }
232 
loadData()233 void TattooMap::loadData() {
234 	Resources &res = *_vm->_res;
235 	char c;
236 
237 	Common::SeekableReadStream *stream = res.load("map.txt");
238 
239 	_data.resize(100);
240 	for (uint idx = 0; idx < _data.size(); ++idx)
241 		_data[idx].clear();
242 
243 	do
244 	{
245 		// Find the start of the number
246 		do {
247 			c = stream->readByte();
248 			if (stream->pos() >= stream->size())
249 				break;
250 		} while (c < '0' || c > '9');
251 		if (stream->pos() >= stream->size())
252 			break;
253 
254 		// Get the scene number
255 		Common::String locStr;
256 		locStr += c;
257 		while ((c = stream->readByte()) != '.')
258 			locStr += c;
259 		MapEntry &mapEntry = _data[atoi(locStr.c_str()) - 1];
260 
261 		// Get the location name
262 		while (stream->readByte() != '"')
263 			;
264 
265 		while ((c = stream->readByte()) != '"')
266 			mapEntry._description += c;
267 
268 		// Find the ( specifying the (X,Y) position of the Icon
269 		while (stream->readByte() != '(')
270 			;
271 
272 		// Get the X Position of the icon
273 		Common::String numStr;
274 		while ((c = stream->readByte()) != ',')
275 			numStr += c;
276 		mapEntry.x = atoi(numStr.c_str());
277 
278 		// Get the Y position of the icon
279 		numStr = "";
280 		while ((c = stream->readByte()) != ')')
281 			numStr += c;
282 		mapEntry.y = atoi(numStr.c_str());
283 
284 		// Find and get the location's icon number
285 		while (stream->readByte() != '#')
286 			;
287 
288 		Common::String iconStr;
289 		while (stream->pos() < stream->size() && (c = stream->readByte()) != '\r')
290 			iconStr += c;
291 
292 		mapEntry._iconNum = atoi(iconStr.c_str()) - 1;
293 	} while (stream->pos() < stream->size());
294 
295 	delete stream;
296 }
297 
drawMapIcons()298 void TattooMap::drawMapIcons() {
299 	Debugger &debugger = *_vm->_debugger;
300 	Screen &screen = *_vm->_screen;
301 
302 	for (uint idx = 0; idx < _data.size(); ++idx) {
303 		if (debugger._showAllLocations != LOC_DISABLED)
304 			_vm->setFlagsDirect(idx + 1);
305 
306 		if (_data[idx]._iconNum != -1 && _vm->readFlags(idx + 1)) {
307 			MapEntry &mapEntry = _data[idx];
308 			ImageFrame &img = (*_iconImages)[mapEntry._iconNum];
309 			screen._backBuffer1.SHtransBlitFrom(img._frame, Common::Point(mapEntry.x - img._width / 2,
310 				mapEntry.y - img._height / 2));
311 		}
312 	}
313 
314 	if (debugger._showAllLocations == LOC_REFRESH)
315 		debugger._showAllLocations = LOC_ALL;
316 }
317 
checkMapNames(bool slamIt)318 void TattooMap::checkMapNames(bool slamIt) {
319 	Events &events = *_vm->_events;
320 	Common::Point mapPos = events.mousePos();
321 
322 	// See if the mouse is pointing at any of the map locations
323 	_bgFound = -1;
324 
325 	for (uint idx = 0; idx < _data.size(); ++idx) {
326 		if (_data[idx]._iconNum != -1 && _vm->readFlags(idx + 1)) {
327 			MapEntry &mapEntry = _data[idx];
328 			ImageFrame &img = (*_iconImages)[mapEntry._iconNum];
329 			Common::Rect r(mapEntry.x - img._width / 2, mapEntry.y - img._height / 2,
330 				mapEntry.x + img._width / 2, mapEntry.y + img._height / 2);
331 
332 			if (r.contains(mapPos)) {
333 				_bgFound = idx;
334 				break;
335 			}
336 		}
337 	}
338 
339 	// Handle updating the tooltip
340 	if (_bgFound != _oldBgFound) {
341 		if (_bgFound == -1) {
342 			_mapTooltip.setText("");
343 		} else {
344 			const Common::String &desc = _data[_bgFound]._description;
345 			_mapTooltip.setText(desc);
346 		}
347 
348 		_oldBgFound = _bgFound;
349 	}
350 
351 	_mapTooltip.handleEvents();
352 	if (slamIt)
353 		_mapTooltip.draw();
354 }
355 
restoreArea(const Common::Rect & bounds)356 void TattooMap::restoreArea(const Common::Rect &bounds) {
357 	Screen &screen = *_vm->_screen;
358 
359 	Common::Rect r = bounds;
360 	r.clip(Common::Rect(0, 0, screen._backBuffer1.width(), screen._backBuffer1.height()));
361 
362 	if (!r.isEmpty())
363 		screen._backBuffer1.SHblitFrom(screen._backBuffer2, Common::Point(r.left, r.top), r);
364 }
365 
showCloseUp(int closeUpNum)366 void TattooMap::showCloseUp(int closeUpNum) {
367 	Events &events = *_vm->_events;
368 	Screen &screen = *_vm->_screen;
369 
370 	// Hide the cursor
371 	events.hideCursor();
372 
373 	// Get the closeup images
374 	Common::String fname = Common::String::format("res%02d.vgs", closeUpNum + 1);
375 	ImageFile pic(fname);
376 
377 	Point32 closeUp(_data[closeUpNum].x * 100, _data[closeUpNum].y * 100);
378 	Point32 delta((SHERLOCK_SCREEN_WIDTH / 2 - closeUp.x / 100) * 100 / CLOSEUP_STEPS,
379 		(SHERLOCK_SCREEN_HEIGHT / 2 - closeUp.y / 100) * 100 / CLOSEUP_STEPS);
380 	Common::Rect oldBounds(closeUp.x / 100, closeUp.y / 100, closeUp.x / 100 + 1, closeUp.y / 100 + 1);
381 	int size = 64;
382 	int n = 256;
383 	int deltaVal = 512;
384 	bool minimize = false;
385 	int scaleVal, newSize;
386 
387 	do {
388 		scaleVal = n;
389 		newSize = pic[0].sDrawXSize(n);
390 
391 		if (newSize > size) {
392 			if (minimize)
393 				deltaVal /= 2;
394 			n += deltaVal;
395 		} else {
396 			minimize = true;
397 			deltaVal /= 2;
398 			n -= deltaVal;
399 			if (n < 1)
400 				n = 1;
401 		}
402 	} while (deltaVal && size != newSize);
403 
404 	int deltaScale = (SCALE_THRESHOLD - scaleVal) / CLOSEUP_STEPS;
405 
406 	for (int step = 0; step < CLOSEUP_STEPS; ++step) {
407 		Common::Point picSize(pic[0].sDrawXSize(scaleVal), pic[0].sDrawYSize(scaleVal));
408 		Common::Point pt(screen._currentScroll.x + closeUp.x / 100 - picSize.x / 2,
409 			screen._currentScroll.y + closeUp.y / 100 - picSize.y / 2);
410 
411 		restoreArea(oldBounds);
412 		screen._backBuffer1.SHtransBlitFrom(pic[0], pt, false, 0, scaleVal);
413 
414 		screen.slamRect(oldBounds);
415 		screen.slamArea(pt.x, pt.y, picSize.x, picSize.y);
416 
417 		oldBounds = Common::Rect(pt.x, pt.y, pt.x + picSize.x + 1, pt.y + picSize.y + 1);
418 		closeUp += delta;
419 		scaleVal += deltaScale;
420 
421 		events.wait(1);
422 	}
423 
424 	// Handle final drawing of closeup
425 	Common::Rect r(screen._currentScroll.x + SHERLOCK_SCREEN_WIDTH / 2 - pic[0]._width / 2,
426 		screen._currentScroll.y + SHERLOCK_SCREEN_HEIGHT / 2 - pic[0]._height / 2,
427 		screen._currentScroll.x + SHERLOCK_SCREEN_WIDTH / 2 - pic[0]._width / 2 + pic[0]._width,
428 		screen._currentScroll.y + SHERLOCK_SCREEN_HEIGHT / 2 - pic[0]._height / 2 + pic[0]._height);
429 
430 	restoreArea(oldBounds);
431 	screen._backBuffer1.SHtransBlitFrom(pic[0], Common::Point(r.left, r.top));
432 	screen.slamRect(oldBounds);
433 	screen.slamRect(r);
434 
435 	events.wait(60);
436 	events.showCursor();
437 }
438 
439 } // End of namespace Tattoo
440 
441 } // End of namespace Sherlock
442