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 "common/scummsys.h"
24 #include "common/config-manager.h"
25 #include "common/debug-channels.h"
26 #include "common/debug.h"
27 #include "common/events.h"
28 #include "common/file.h"
29 #include "common/random.h"
30 #include "common/fs.h"
31 #include "common/keyboard.h"
32 #include "common/substream.h"
33 #include "common/str.h"
34 
35 #include "graphics/surface.h"
36 #include "graphics/pixelformat.h"
37 
38 #include "engines/util.h"
39 
40 #include "prince/prince.h"
41 #include "prince/graphics.h"
42 #include "prince/script.h"
43 #include "prince/debugger.h"
44 #include "prince/object.h"
45 #include "prince/mob.h"
46 #include "prince/music.h"
47 #include "prince/variatxt.h"
48 #include "prince/font.h"
49 #include "prince/mhwanh.h"
50 #include "prince/cursor.h"
51 #include "prince/archive.h"
52 #include "prince/hero.h"
53 #include "prince/animation.h"
54 #include "prince/curve_values.h"
55 
56 namespace Prince {
57 
debugEngine(const char * s,...)58 void PrinceEngine::debugEngine(const char *s, ...) {
59 	char buf[STRINGBUFLEN];
60 	va_list va;
61 
62 	va_start(va, s);
63 	vsnprintf(buf, STRINGBUFLEN, s, va);
64 	va_end(va);
65 
66 	debug("Prince::Engine %s", buf);
67 }
68 
PrinceEngine(OSystem * syst,const PrinceGameDescription * gameDesc)69 PrinceEngine::PrinceEngine(OSystem *syst, const PrinceGameDescription *gameDesc) :
70 	Engine(syst), _gameDescription(gameDesc), _graph(nullptr), _script(nullptr), _interpreter(nullptr), _flags(nullptr),
71 	_locationNr(0), _debugger(nullptr), _midiPlayer(nullptr), _room(nullptr),
72 	_cursor1(nullptr), _cursor2(nullptr), _cursor3(nullptr), _font(nullptr),
73 	_suitcaseBmp(nullptr), _roomBmp(nullptr), _cursorNr(0), _picWindowX(0), _picWindowY(0), _randomSource("prince"),
74 	_invLineX(134), _invLineY(176), _invLine(5), _invLines(3), _invLineW(70), _invLineH(76), _maxInvW(72), _maxInvH(76),
75 	_invLineSkipX(2), _invLineSkipY(3), _showInventoryFlag(false), _inventoryBackgroundRemember(false),
76 	_mst_shadow(0), _mst_shadow2(0), _candleCounter(0), _invX1(53), _invY1(18), _invWidth(536), _invHeight(438),
77 	_invCurInside(false), _optionsFlag(false), _optionEnabled(0), _invExamY(120), _invMaxCount(2), _invCounter(0),
78 	_optionsMob(-1), _currentPointerNumber(1), _selectedMob(-1), _selectedItem(0), _selectedMode(0),
79 	_optionsWidth(210), _optionsHeight(170), _invOptionsWidth(210), _invOptionsHeight(130), _optionsStep(20),
80 	_invOptionsStep(20), _optionsNumber(7), _invOptionsNumber(5), _optionsColor1(236), _optionsColor2(252),
81 	_dialogWidth(600), _dialogHeight(0), _dialogLineSpace(10), _dialogColor1(220), _dialogColor2(223),
82 	_dialogFlag(false), _dialogLines(0), _dialogText(nullptr), _mouseFlag(1),
83 	_roomPathBitmap(nullptr), _roomPathBitmapTemp(nullptr), _coordsBufEnd(nullptr), _coordsBuf(nullptr), _coords(nullptr),
84 	_traceLineLen(0), _rembBitmapTemp(nullptr), _rembBitmap(nullptr), _rembMask(0), _rembX(0), _rembY(0), _fpX(0), _fpY(0),
85 	_checkBitmapTemp(nullptr), _checkBitmap(nullptr), _checkMask(0), _checkX(0), _checkY(0), _traceLineFirstPointFlag(false),
86 	_tracePointFirstPointFlag(false), _coordsBuf2(nullptr), _coords2(nullptr), _coordsBuf3(nullptr), _coords3(nullptr),
87 	_shanLen(0), _directionTable(nullptr), _currentMidi(0), _lightX(0), _lightY(0), _curveData(nullptr), _curvPos(0),
88 	_creditsData(nullptr), _creditsDataSize(0), _currentTime(0), _zoomBitmap(nullptr), _shadowBitmap(nullptr), _transTable(nullptr),
89 	_flcFrameSurface(nullptr), _shadScaleValue(0), _shadLineLen(0), _scaleValue(0), _dialogImage(nullptr), _mobTranslationData(nullptr),
90 	_mobTranslationSize(0), _missingVoice(false) {
91 
92 	DebugMan.enableDebugChannel("script");
93 
94 	memset(_audioStream, 0, sizeof(_audioStream));
95 }
96 
~PrinceEngine()97 PrinceEngine::~PrinceEngine() {
98 	delete _rnd;
99 	delete _cursor1;
100 	delete _cursor3;
101 	delete _midiPlayer;
102 	delete _script;
103 	delete _flags;
104 	delete _interpreter;
105 	delete _font;
106 	delete _roomBmp;
107 	delete _suitcaseBmp;
108 	delete _variaTxt;
109 	free(_talkTxt);
110 	free(_invTxt);
111 	free(_dialogDat);
112 	delete _graph;
113 	delete _room;
114 	//_debugger is deleted by Engine
115 
116 	if (_cursor2 != nullptr) {
117 		_cursor2->free();
118 		delete _cursor2;
119 	}
120 
121 	for (uint i = 0; i < _objList.size(); i++) {
122 		delete _objList[i];
123 	}
124 	_objList.clear();
125 
126 	free(_objSlot);
127 
128 	for (uint32 i = 0; i < _pscrList.size(); i++) {
129 		delete _pscrList[i];
130 	}
131 	_pscrList.clear();
132 
133 	for (uint i = 0; i < _maskList.size(); i++) {
134 		free(_maskList[i]._data);
135 	}
136 	_maskList.clear();
137 
138 	_drawNodeList.clear();
139 
140 	clearBackAnimList();
141 	_backAnimList.clear();
142 
143 	freeAllNormAnims();
144 	_normAnimList.clear();
145 
146 	for (uint i = 0; i < _allInvList.size(); i++) {
147 		_allInvList[i]._surface->free();
148 		delete _allInvList[i]._surface;
149 	}
150 	_allInvList.clear();
151 
152 	_optionsPic->free();
153 	delete _optionsPic;
154 
155 	_optionsPicInInventory->free();
156 	delete _optionsPicInInventory;
157 
158 	for (uint i = 0; i < _mainHero->_moveSet.size(); i++) {
159 		delete _mainHero->_moveSet[i];
160 	}
161 
162 	for (uint i = 0; i < _secondHero->_moveSet.size(); i++) {
163 		delete _secondHero->_moveSet[i];
164 	}
165 
166 	delete _mainHero;
167 	delete _secondHero;
168 
169 	free(_roomPathBitmap);
170 	free(_roomPathBitmapTemp);
171 	free(_coordsBuf);
172 
173 	_mobPriorityList.clear();
174 
175 	freeAllSamples();
176 
177 	free(_zoomBitmap);
178 	free(_shadowBitmap);
179 	free(_transTable);
180 
181 	free(_curveData);
182 
183 	free(_shadowLine);
184 
185 	free(_creditsData);
186 
187 	if (_dialogImage != nullptr) {
188 		_dialogImage->free();
189 		delete _dialogImage;
190 	}
191 
192 	free(_mobTranslationData);
193 }
194 
init()195 void PrinceEngine::init() {
196 
197 	const Common::FSNode gameDataDir(ConfMan.get("path"));
198 
199 	debugEngine("Adding all path: %s", gameDataDir.getPath().c_str());
200 
201 	if (!(getFeatures() & GF_EXTRACTED)) {
202 		PtcArchive *all = new PtcArchive();
203 		if (!all->open("all/databank.ptc"))
204 			error("Can't open all/databank.ptc");
205 
206 		PtcArchive *voices = new PtcArchive();
207 
208 		if (!(getFeatures() & GF_NOVOICES)) {
209 			if (!voices->open("voices/databank.ptc"))
210 				error("Can't open voices/databank.ptc");
211 		}
212 
213 		PtcArchive *sound = new PtcArchive();
214 		if (!sound->open("sound/databank.ptc"))
215 			error("Can't open sound/databank.ptc");
216 
217 		SearchMan.addSubDirectoryMatching(gameDataDir, "all");
218 
219 		// Prefix the archive names, so that "all" doesn't conflict with the
220 		// "all" directory, if that happens to be named in all lower case.
221 		// It isn't on the CD, but we should try to stay case-insensitive.
222 		SearchMan.add("_all", all);
223 		SearchMan.add("_voices", voices);
224 		SearchMan.add("_sound", sound);
225 	} else {
226 		SearchMan.addSubDirectoryMatching(gameDataDir, "all");
227 		SearchMan.addSubDirectoryMatching(gameDataDir, "voices");
228 		SearchMan.addSubDirectoryMatching(gameDataDir, "sound");
229 	}
230 
231 	if (getFeatures() & GF_TRANSLATED) {
232 		PtcArchive *translation = new PtcArchive();
233 		if (getFeatures() & GF_TRANSLATED) {
234 			if (!translation->openTranslation("prince_translation.dat"))
235 				error("Can't open prince_translation.dat");
236 		}
237 
238 		SearchMan.add("translation", translation);
239 	}
240 
241 	_graph = new GraphicsMan(this);
242 
243 	_rnd = new Common::RandomSource("prince");
244 
245 	_midiPlayer = new MusicPlayer(this);
246 
247 	if (getLanguage() == Common::DE_DEU) {
248 		_font = new Font();
249 		Resource::loadResource(_font, "font3.raw", true);
250 	} else {
251 		_font = new Font();
252 		Resource::loadResource(_font, "font1.raw", true);
253 	}
254 
255 	_suitcaseBmp = new MhwanhDecoder();
256 	Resource::loadResource(_suitcaseBmp, "walizka", true);
257 
258 	_script = new Script(this);
259 	Resource::loadResource(_script, "skrypt.dat", true);
260 
261 	_flags = new InterpreterFlags();
262 	_interpreter = new Interpreter(this, _script, _flags);
263 
264 	_debugger = new Debugger(this, _flags);
265 	setDebugger(_debugger);
266 
267 	_variaTxt = new VariaTxt();
268 	if (getFeatures() & GF_TRANSLATED) {
269 		Resource::loadResource(_variaTxt, "variatxt_translate.dat", true);
270 	} else {
271 		Resource::loadResource(_variaTxt, "variatxt.dat", true);
272 	}
273 
274 	_cursor1 = new Cursor();
275 	Resource::loadResource(_cursor1, "mouse1.cur", true);
276 
277 	_cursor3 = new Cursor();
278 	Resource::loadResource(_cursor3, "mouse2.cur", true);
279 
280 	Common::SeekableReadStream *talkTxtStream;
281 	if (getFeatures() & GF_TRANSLATED) {
282 		talkTxtStream = SearchMan.createReadStreamForMember("talktxt_translate.dat");
283 	} else {
284 		talkTxtStream = SearchMan.createReadStreamForMember("talktxt.dat");
285 	}
286 	if (!talkTxtStream) {
287 		error("Can't load talkTxtStream");
288 		return;
289 	}
290 	_talkTxtSize = talkTxtStream->size();
291 	_talkTxt = (byte *)malloc(_talkTxtSize);
292 	talkTxtStream->read(_talkTxt, _talkTxtSize);
293 
294 	delete talkTxtStream;
295 
296 	Common::SeekableReadStream *invTxtStream;
297 	if (getFeatures() & GF_TRANSLATED) {
298 		invTxtStream = SearchMan.createReadStreamForMember("invtxt_translate.dat");
299 	} else {
300 		invTxtStream = SearchMan.createReadStreamForMember("invtxt.dat");
301 	}
302 	if (!invTxtStream) {
303 		error("Can't load invTxtStream");
304 		return;
305 	}
306 	_invTxtSize = invTxtStream->size();
307 	_invTxt = (byte *)malloc(_invTxtSize);
308 	invTxtStream->read(_invTxt, _invTxtSize);
309 
310 	delete invTxtStream;
311 
312 	loadAllInv();
313 
314 	Common::SeekableReadStream *dialogDatStream = SearchMan.createReadStreamForMember("dialog.dat");
315 	if (!dialogDatStream) {
316 		error("Can't load dialogDatStream");
317 		return;
318 	}
319 
320 	dialogDatStream = Resource::getDecompressedStream(dialogDatStream);
321 
322 	_dialogDatSize = dialogDatStream->size();
323 	_dialogDat = (byte *)malloc(_dialogDatSize);
324 	dialogDatStream->read(_dialogDat, _dialogDatSize);
325 
326 	delete dialogDatStream;
327 
328 	_optionsPic = new Graphics::Surface();
329 	_optionsPic->create(_optionsWidth, _optionsHeight, Graphics::PixelFormat::createFormatCLUT8());
330 	Common::Rect picRect(0, 0, _optionsWidth, _optionsHeight);
331 	_optionsPic->fillRect(picRect, _graph->kShadowColor);
332 
333 	_optionsPicInInventory = new Graphics::Surface();
334 	_optionsPicInInventory->create(_invOptionsWidth, _invOptionsHeight, Graphics::PixelFormat::createFormatCLUT8());
335 	Common::Rect invPicRect(0, 0, _invOptionsWidth, _invOptionsHeight);
336 	_optionsPicInInventory->fillRect(invPicRect, _graph->kShadowColor);
337 
338 	_roomBmp = new Image::BitmapDecoder();
339 
340 	_room = new Room();
341 
342 	_mainHero = new Hero(this, _graph);
343 	_secondHero = new Hero(this, _graph);
344 	_secondHero->_maxBoredom = 140;
345 	_secondHero->loadAnimSet(3);
346 
347 	_roomPathBitmap = (byte *)malloc(kPathBitmapLen);
348 	_roomPathBitmapTemp = (byte *)malloc(kPathBitmapLen);
349 	_coordsBuf = (byte *)malloc(kTracePts * 4);
350 	_coords = _coordsBuf;
351 	_coordsBufEnd = _coordsBuf + kTracePts * 4 - 4;
352 
353 	BackgroundAnim tempBackAnim;
354 	tempBackAnim._seq._currRelative = 0;
355 	for (int i = 0; i < kMaxBackAnims; i++) {
356 		_backAnimList.push_back(tempBackAnim);
357 	}
358 
359 	Anim tempAnim;
360 	tempAnim._animData = nullptr;
361 	tempAnim._shadowData = nullptr;
362 	for (int i = 0; i < kMaxNormAnims; i++) {
363 		_normAnimList.push_back(tempAnim);
364 	}
365 
366 	_objSlot = (uint16 *)malloc(kMaxObjects * sizeof(uint16));
367 	for (int i = 0; i < kMaxObjects; i++) {
368 		_objSlot[i] = 0xFF;
369 	}
370 
371 	_zoomBitmap = (byte *)malloc(kZoomBitmapLen);
372 	_shadowBitmap = (byte *)malloc(2 * kShadowBitmapSize);
373 	_transTable = (byte *)malloc(kTransTableSize);
374 
375 	_curveData = (int16 *)malloc(2 * kCurveLen * sizeof(int16));
376 
377 	_shadowLine = (byte *)malloc(kShadowLineArraySize);
378 
379 	Common::SeekableReadStream *creditsDataStream;
380 	if (getFeatures() & GF_TRANSLATED) {
381 		creditsDataStream = SearchMan.createReadStreamForMember("credits_translate.dat");
382 	} else {
383 		creditsDataStream = SearchMan.createReadStreamForMember("credits.dat");
384 	}
385 	if (!creditsDataStream) {
386 		error("Can't load creditsDataStream");
387 		return;
388 	}
389 	_creditsDataSize = creditsDataStream->size();
390 	_creditsData = (byte *)malloc(_creditsDataSize);
391 	creditsDataStream->read(_creditsData, _creditsDataSize);
392 	delete creditsDataStream;
393 
394 	if (getFeatures() & GF_TRANSLATED) {
395 		loadMobTranslationTexts();
396 	}
397 }
398 
showLogo()399 void PrinceEngine::showLogo() {
400 	MhwanhDecoder logo;
401 	if (Resource::loadResource(&logo, "logo.raw", true)) {
402 		loadSample(0, "LOGO.WAV");
403 		playSample(0, 0);
404 		_graph->draw(_graph->_frontScreen, logo.getSurface());
405 		_graph->change();
406 		_graph->update(_graph->_frontScreen);
407 		setPalette(logo.getPalette());
408 
409 		uint32 logoStart = _system->getMillis();
410 		while (_system->getMillis() < logoStart + 5000) {
411 			Common::Event event;
412 			Common::EventManager *eventMan = _system->getEventManager();
413 			while (eventMan->pollEvent(event)) {
414 				switch (event.type) {
415 				case Common::EVENT_KEYDOWN:
416 					if (event.kbd.keycode == Common::KEYCODE_ESCAPE) {
417 						stopSample(0);
418 						return;
419 					}
420 					break;
421 				case Common::EVENT_LBUTTONDOWN:
422 					stopSample(0);
423 					return;
424 				default:
425 					break;
426 				}
427 			}
428 
429 			if (shouldQuit()) {
430 				return;
431 			}
432 		}
433 	}
434 }
435 
run()436 Common::Error PrinceEngine::run() {
437 	syncSoundSettings();
438 	int startGameSlot = ConfMan.hasKey("save_slot") ? ConfMan.getInt("save_slot") : -1;
439 	init();
440 	if (startGameSlot == -1) {
441 		playVideo("topware.avi");
442 		showLogo();
443 	} else {
444 		loadLocation(59); // load intro location - easiest way to set everything up
445 		loadGame(startGameSlot);
446 	}
447 	mainLoop();
448 	return Common::kNoError;
449 }
450 
pauseEngineIntern(bool pause)451 void PrinceEngine::pauseEngineIntern(bool pause) {
452 	Engine::pauseEngineIntern(pause);
453 	if (pause) {
454 		_midiPlayer->pause();
455 	} else {
456 		_midiPlayer->resume();
457 	}
458 }
459 
setShadowScale(int32 shadowScale)460 void PrinceEngine::setShadowScale(int32 shadowScale) {
461 	shadowScale = 100 - shadowScale;
462 	if (!shadowScale) {
463 		_shadScaleValue = 10000;
464 	} else {
465 		_shadScaleValue = 10000 / shadowScale;
466 	}
467 }
468 
plotShadowLinePoint(int x,int y,int color,void * data)469 void PrinceEngine::plotShadowLinePoint(int x, int y, int color, void *data) {
470 	PrinceEngine *vm = (PrinceEngine *)data;
471 	WRITE_LE_UINT16(&vm->_shadowLine[vm->_shadLineLen * 4], x);
472 	WRITE_LE_UINT16(&vm->_shadowLine[vm->_shadLineLen * 4 + 2], y);
473 	vm->_shadLineLen++;
474 }
475 
playNextFLCFrame()476 bool PrinceEngine::playNextFLCFrame() {
477 	if (!_flicPlayer.isVideoLoaded())
478 		return false;
479 
480 	const Graphics::Surface *s = _flicPlayer.decodeNextFrame();
481 	if (s) {
482 		_graph->drawTransparentSurface(_graph->_frontScreen, 0, 0, s, 255);
483 		_graph->change();
484 		_flcFrameSurface = s;
485 	} else if (_flicLooped) {
486 		_flicPlayer.rewind();
487 		playNextFLCFrame();
488 	} else if (_flcFrameSurface) {
489 		_graph->drawTransparentSurface(_graph->_frontScreen, 0, 0, _flcFrameSurface, 255);
490 		_graph->change();
491 	}
492 
493 	return true;
494 }
495 
loadMobTranslationTexts()496 void PrinceEngine::loadMobTranslationTexts() {
497 	Common::SeekableReadStream *mobTranslationStream = SearchMan.createReadStreamForMember("mob_translate.dat");
498 	if (!mobTranslationStream) {
499 		error("Can't load mob_translate.dat");
500 	}
501 	_mobTranslationSize = mobTranslationStream->size();
502 	_mobTranslationData = (byte *)malloc(_mobTranslationSize);
503 	mobTranslationStream->read(_mobTranslationData, _mobTranslationSize);
504 	delete mobTranslationStream;
505 }
506 
setMobTranslationTexts()507 void PrinceEngine::setMobTranslationTexts() {
508 	int locationOffset = READ_UINT16(_mobTranslationData + (_locationNr - 1) * 2);
509 	if (locationOffset) {
510 		byte *locationText = _mobTranslationData + locationOffset;
511 		for (uint i = 0; i < _mobList.size(); i++) {
512 			byte c;
513 			locationText++;
514 			_mobList[i]._name.clear();
515 			while ((c = *locationText)) {
516 				_mobList[i]._name += c;
517 				locationText++;
518 			}
519 			locationText++;
520 			_mobList[i]._examText.clear();
521 			c = *locationText;
522 			locationText++;
523 			if (c) {
524 				_mobList[i]._examText += c;
525 				do {
526 					c = *locationText;
527 					_mobList[i]._examText += c;
528 					locationText++;
529 				} while (c != 255);
530 			}
531 		}
532 	}
533 }
534 
keyHandler(Common::Event event)535 void PrinceEngine::keyHandler(Common::Event event) {
536 	uint16 nChar = event.kbd.keycode;
537 	switch (nChar) {
538 	case Common::KEYCODE_F1:
539 		if (canLoadGameStateCurrently())
540 			scummVMSaveLoadDialog(false);
541 		break;
542 	case Common::KEYCODE_F2:
543 		if (canSaveGameStateCurrently())
544 			scummVMSaveLoadDialog(true);
545 		break;
546 	case Common::KEYCODE_z:
547 		if (_flags->getFlagValue(Flags::POWERENABLED)) {
548 			_flags->setFlagValue(Flags::MBFLAG, 1);
549 		}
550 		break;
551 	case Common::KEYCODE_x:
552 		if (_flags->getFlagValue(Flags::POWERENABLED)) {
553 			_flags->setFlagValue(Flags::MBFLAG, 2);
554 		}
555 		break;
556 	case Common::KEYCODE_ESCAPE:
557 		_flags->setFlagValue(Flags::ESCAPED2, 1);
558 		break;
559 	default:
560 		break;
561 	}
562 }
563 
printAt(uint32 slot,uint8 color,char * s,uint16 x,uint16 y)564 void PrinceEngine::printAt(uint32 slot, uint8 color, char *s, uint16 x, uint16 y) {
565 	debugC(1, DebugChannel::kEngine, "PrinceEngine::printAt slot %d, color %d, x %02d, y %02d, str %s", slot, color, x, y, s);
566 
567 	if (getLanguage() == Common::DE_DEU)
568 		correctStringDEU(s);
569 
570 	Text &text = _textSlots[slot];
571 	text._str = s;
572 	text._x = x;
573 	text._y = y;
574 	text._color = color;
575 	int lines = calcTextLines(s);
576 	text._time = calcTextTime(lines);
577 }
578 
calcTextLines(const char * s)579 int PrinceEngine::calcTextLines(const char *s) {
580 	int lines = 1;
581 	while (*s) {
582 		if (*s == '\n') {
583 			lines++;
584 		}
585 		s++;
586 	}
587 	return lines;
588 }
589 
calcTextTime(int numberOfLines)590 int PrinceEngine::calcTextTime(int numberOfLines) {
591 	return numberOfLines * 30;
592 }
593 
correctStringDEU(char * s)594 void PrinceEngine::correctStringDEU(char *s) {
595 	while (*s) {
596 		switch (*s) {
597 		case '\xc4':
598 			*s = '\x83';
599 			break;
600 		case '\xd6':
601 			*s = '\x84';
602 			break;
603 		case '\xdc':
604 			*s = '\x85';
605 			break;
606 		case '\xdf':
607 			*s = '\x7f';
608 			break;
609 		case '\xe4':
610 			*s = '\x80';
611 			break;
612 		case '\xf6':
613 			*s = '\x81';
614 			break;
615 		case '\xfc':
616 			*s = '\x82';
617 			break;
618 		default:
619 			break;
620 		}
621 		s++;
622 	}
623 }
624 
getTextWidth(const char * s)625 uint32 PrinceEngine::getTextWidth(const char *s) {
626 	uint16 textW = 0;
627 	while (*s) {
628 		textW += _font->getCharWidth(*s) + _font->getKerningOffset(0, 0);
629 		s++;
630 	}
631 	return textW;
632 }
633 
showTexts(Graphics::Surface * screen)634 void PrinceEngine::showTexts(Graphics::Surface *screen) {
635 	for (uint32 slot = 0; slot < kMaxTexts; slot++) {
636 
637 		if (_showInventoryFlag && slot) {
638 			// only slot 0 for inventory
639 			break;
640 		}
641 
642 		Text& text = _textSlots[slot];
643 		if (!text._str && !text._time) {
644 			continue;
645 		}
646 
647 		int x = text._x;
648 		int y = text._y;
649 
650 		if (!_showInventoryFlag) {
651 			x -= _picWindowX;
652 			y -= _picWindowY;
653 		}
654 
655 		Common::Array<Common::String> lines;
656 		_font->wordWrapText(text._str, _graph->_frontScreen->w, lines);
657 
658 		int wideLine = 0;
659 		for (uint i = 0; i < lines.size(); i++) {
660 			int textLen = getTextWidth(lines[i].c_str());
661 			if (textLen > wideLine) {
662 				wideLine = textLen;
663 			}
664 		}
665 
666 		int leftBorderText = 6;
667 		if (x + wideLine / 2 >  kNormalWidth - leftBorderText) {
668 			x = kNormalWidth - leftBorderText - wideLine / 2;
669 		}
670 
671 		if (x - wideLine / 2 < leftBorderText) {
672 			x = leftBorderText + wideLine / 2;
673 		}
674 
675 		int textSkip = 2;
676 		for (uint i = 0; i < lines.size(); i++) {
677 			int drawX = x - getTextWidth(lines[i].c_str()) / 2;
678 			int drawY = y - 10 - (lines.size() - i) * (_font->getFontHeight() - textSkip);
679 			if (drawX < 0) {
680 				drawX = 0;
681 			}
682 			if (drawY < 0) {
683 				drawY = 0;
684 			}
685 			_font->drawString(screen, lines[i], drawX, drawY, screen->w, text._color);
686 		}
687 
688 		text._time--;
689 		if (!text._time) {
690 			text._str = nullptr;
691 		}
692 	}
693 }
694 
pausePrinceEngine(int fps)695 void PrinceEngine::pausePrinceEngine(int fps) {
696 	int delay = 1000 / fps - int32(_system->getMillis() - _currentTime);
697 	delay = delay < 0 ? 0 : delay;
698 	_system->delayMillis(delay);
699 	_currentTime = _system->getMillis();
700 }
701 
leftMouseButton()702 void PrinceEngine::leftMouseButton() {
703 	_flags->setFlagValue(Flags::ESCAPED2, 1); // skip intro animation
704 	_flags->setFlagValue(Flags::LMOUSE, 1);
705 	if (_flags->getFlagValue(Flags::POWERENABLED)) {
706 		_flags->setFlagValue(Flags::MBFLAG, 1);
707 	}
708 	if (_mouseFlag) {
709 		int option = 0;
710 		int optionEvent = -1;
711 
712 		if (_optionsFlag) {
713 			if (_optionEnabled < _optionsNumber && _optionEnabled != -1) {
714 				option = _optionEnabled;
715 				_optionsFlag = 0;
716 			} else {
717 				return;
718 			}
719 		} else {
720 			_optionsMob = _selectedMob;
721 			if (_optionsMob == -1) {
722 				walkTo();
723 				return;
724 			}
725 			option = 0;
726 		}
727 		//do_option
728 		if (_currentPointerNumber != 2) {
729 			//skip_use_code
730 			int optionScriptOffset = _room->getOptionOffset(option);
731 			if (optionScriptOffset != 0) {
732 				optionEvent = _script->scanMobEvents(_optionsMob, optionScriptOffset);
733 			}
734 			if (optionEvent == -1) {
735 				if (!option) {
736 					walkTo();
737 					return;
738 				} else {
739 					optionEvent = _script->getOptionStandardOffset(option);
740 				}
741 			}
742 		} else if (_selectedMode) {
743 			//give_item
744 			if (_room->_itemGive) {
745 				optionEvent = _script->scanMobEventsWithItem(_optionsMob, _room->_itemGive, _selectedItem);
746 			}
747 			if (optionEvent == -1) {
748 				//standard_giveitem
749 				optionEvent = _script->_scriptInfo.stdGiveItem;
750 			}
751 		} else {
752 			if (_room->_itemUse) {
753 				optionEvent = _script->scanMobEventsWithItem(_optionsMob, _room->_itemUse, _selectedItem);
754 				_flags->setFlagValue(Flags::SELITEM, _selectedItem);
755 			}
756 			if (optionEvent == -1) {
757 				//standard_useitem
758 				optionEvent = _script->_scriptInfo.stdUseItem;
759 			}
760 		}
761 		_interpreter->storeNewPC(optionEvent);
762 		_flags->setFlagValue(Flags::CURRMOB, _selectedMob);
763 		_selectedMob = -1;
764 		_optionsMob = -1;
765 	} else {
766 		if (!_flags->getFlagValue(Flags::POWERENABLED)) {
767 			if (!_flags->getFlagValue(Flags::NOCLSTEXT)) {
768 				for (int slot = 0; slot < kMaxTexts; slot++) {
769 					if (slot != 9) {
770 						Text& text = _textSlots[slot];
771 						if (!text._str) {
772 							continue;
773 						}
774 						text._str = 0;
775 						text._time = 0;
776 					}
777 				}
778 				_mainHero->_talkTime = 0;
779 				_secondHero->_talkTime = 0;
780 			}
781 		}
782 	}
783 }
784 
rightMouseButton()785 void PrinceEngine::rightMouseButton() {
786 	if (_flags->getFlagValue(Flags::POWERENABLED)) {
787 		_flags->setFlagValue(Flags::MBFLAG, 2);
788 	}
789 	if (_mouseFlag && _mouseFlag != 3) {
790 		_mainHero->freeOldMove();
791 		_secondHero->freeOldMove();
792 		_interpreter->storeNewPC(0);
793 		if (_currentPointerNumber < 2) {
794 			enableOptions(true);
795 		} else {
796 			_currentPointerNumber = 1;
797 			changeCursor(1);
798 		}
799 	}
800 }
801 
createDialogBox(int dialogBoxNr)802 void PrinceEngine::createDialogBox(int dialogBoxNr) {
803 	_dialogLines = 0;
804 	int amountOfDialogOptions = 0;
805 	int dialogDataValue = (int)READ_LE_UINT32(_dialogData);
806 
807 	byte c;
808 	int sentenceNumber;
809 	_dialogText = _dialogBoxAddr[dialogBoxNr];
810 	byte *dialogText = _dialogText;
811 
812 	while ((sentenceNumber = *dialogText) != 0xFF) {
813 		dialogText++;
814 		if (!(dialogDataValue & (1 << sentenceNumber))) {
815 			_dialogLines += calcTextLines((const char *)dialogText);
816 			amountOfDialogOptions++;
817 		}
818 		do {
819 			c = *dialogText;
820 			dialogText++;
821 		} while (c);
822 	}
823 
824 	_dialogHeight = _font->getFontHeight() * _dialogLines + _dialogLineSpace * (amountOfDialogOptions + 1);
825 	_dialogImage = new Graphics::Surface();
826 	_dialogImage->create(_dialogWidth, _dialogHeight, Graphics::PixelFormat::createFormatCLUT8());
827 	Common::Rect dBoxRect(0, 0, _dialogWidth, _dialogHeight);
828 	_dialogImage->fillRect(dBoxRect, _graph->kShadowColor);
829 }
830 
dialogRun()831 void PrinceEngine::dialogRun() {
832 
833 	_dialogFlag = true;
834 
835 	while (!shouldQuit()) {
836 
837 		_interpreter->stepBg();
838 		drawScreen();
839 
840 		int dialogX = (640 - _dialogWidth) / 2;
841 		int dialogY = 460 - _dialogHeight;
842 		_graph->drawAsShadowSurface(_graph->_frontScreen, dialogX, dialogY, _dialogImage, _graph->_shadowTable50);
843 
844 		int dialogSkipLeft = 14;
845 		int dialogSkipUp = 10;
846 
847 		int dialogTextX = dialogX + dialogSkipLeft;
848 		int dialogTextY = dialogY + dialogSkipUp;
849 
850 		Common::Point mousePos = _system->getEventManager()->getMousePos();
851 
852 		byte c;
853 		int sentenceNumber;
854 		byte *dialogText = _dialogText;
855 		byte *dialogCurrentText = nullptr;
856 		int dialogSelected = -1;
857 		int dialogDataValue = (int)READ_LE_UINT32(_dialogData);
858 
859 		while ((sentenceNumber = *dialogText) != 0xFF) {
860 			dialogText++;
861 			int actualColor = _dialogColor1;
862 
863 			if (!(dialogDataValue & (1 << sentenceNumber))) {
864 				if (getLanguage() == Common::DE_DEU) {
865 					correctStringDEU((char *)dialogText);
866 				}
867 				Common::Array<Common::String> lines;
868 				_font->wordWrapText((const char *)dialogText, _graph->_frontScreen->w, lines);
869 
870 				Common::Rect dialogOption(dialogTextX, dialogTextY - dialogSkipUp / 2, dialogX + _dialogWidth - dialogSkipLeft, dialogTextY + lines.size() * _font->getFontHeight() + dialogSkipUp / 2 - 1);
871 				if (dialogOption.contains(mousePos)) {
872 					actualColor = _dialogColor2;
873 					dialogSelected = sentenceNumber;
874 					dialogCurrentText = dialogText;
875 				}
876 
877 				for (uint j = 0; j < lines.size(); j++) {
878 					_font->drawString(_graph->_frontScreen, lines[j], dialogTextX, dialogTextY, _graph->_frontScreen->w, actualColor);
879 					dialogTextY += _font->getFontHeight();
880 				}
881 				dialogTextY += _dialogLineSpace;
882 			}
883 			do {
884 				c = *dialogText;
885 				dialogText++;
886 			} while (c);
887 		}
888 
889 		Common::Event event;
890 		Common::EventManager *eventMan = _system->getEventManager();
891 		while (eventMan->pollEvent(event)) {
892 			switch (event.type) {
893 			case Common::EVENT_KEYDOWN:
894 				keyHandler(event);
895 				break;
896 			case Common::EVENT_LBUTTONDOWN:
897 				if (dialogSelected != -1) {
898 					dialogLeftMouseButton(dialogCurrentText, dialogSelected);
899 					_dialogFlag = false;
900 				}
901 				break;
902 			default:
903 				break;
904 			}
905 		}
906 
907 		if (shouldQuit()) {
908 			return;
909 		}
910 
911 		if (!_dialogFlag) {
912 			break;
913 		}
914 
915 
916 		_graph->update(_graph->_frontScreen);
917 		pausePrinceEngine();
918 	}
919 	_dialogImage->free();
920 	delete _dialogImage;
921 	_dialogImage = nullptr;
922 	_dialogFlag = false;
923 }
924 
dialogLeftMouseButton(byte * string,int dialogSelected)925 void PrinceEngine::dialogLeftMouseButton(byte *string, int dialogSelected) {
926 	_interpreter->setString(string);
927 	talkHero(0);
928 
929 	int dialogDataValue = (int)READ_LE_UINT32(_dialogData);
930 	dialogDataValue |= (1u << dialogSelected);
931 	WRITE_LE_UINT32(_dialogData, dialogDataValue);
932 
933 	_flags->setFlagValue(Flags::BOXSEL, dialogSelected + 1);
934 	setVoice(0, 28, dialogSelected + 1);
935 
936 	_flags->setFlagValue(Flags::VOICE_H_LINE, _dialogOptLines[dialogSelected * 4]);
937 	_flags->setFlagValue(Flags::VOICE_A_LINE, _dialogOptLines[dialogSelected * 4 + 1]);
938 	_flags->setFlagValue(Flags::VOICE_B_LINE, _dialogOptLines[dialogSelected * 4 + 2]);
939 
940 	_interpreter->setString(_dialogOptAddr[dialogSelected]);
941 }
942 
talkHero(int slot)943 void PrinceEngine::talkHero(int slot) {
944 	// heroSlot = textSlot (slot 0 or 1)
945 	Text &text = _textSlots[slot];
946 	int lines = calcTextLines((const char *)_interpreter->getString());
947 	int time = lines * 30;
948 
949 	if (slot == 0) {
950 		text._color = 220; // TODO - test this
951 		_mainHero->_state = Hero::kHeroStateTalk;
952 		_mainHero->_talkTime = time;
953 		text._x = _mainHero->_middleX;
954 		text._y = _mainHero->_middleY - _mainHero->_scaledFrameYSize;
955 	} else {
956 		text._color = _flags->getFlagValue(Flags::KOLOR); // TODO - test this
957 		_secondHero->_state = Hero::kHeroStateTalk;
958 		_secondHero->_talkTime = time;
959 		text._x = _secondHero->_middleX;
960 		text._y = _secondHero->_middleY - _secondHero->_scaledFrameYSize;
961 	}
962 	text._time = time;
963 	if (getLanguage() == Common::DE_DEU) {
964 		correctStringDEU((char *)_interpreter->getString());
965 	}
966 	text._str = (const char *)_interpreter->getString();
967 	_interpreter->increaseString();
968 }
969 
getCurve()970 void PrinceEngine::getCurve() {
971 	_flags->setFlagValue(Flags::TORX1, _curveData[_curvPos]);
972 	_flags->setFlagValue(Flags::TORY1, _curveData[_curvPos + 1]);
973 	_curvPos += 2;
974 }
975 
makeCurve()976 void PrinceEngine::makeCurve() {
977 	_curvPos = 0;
978 	int x1 = _flags->getFlagValue(Flags::TORX1);
979 	int y1 = _flags->getFlagValue(Flags::TORY1);
980 	int x2 = _flags->getFlagValue(Flags::TORX2);
981 	int y2 = _flags->getFlagValue(Flags::TORY2);
982 
983 	for (int i = 0; i < kCurveLen; i++) {
984 		int sum1 = x1 * curveValues[i][0];
985 		sum1 += (x2 + (x1 - x2) / 2) * curveValues[i][1];
986 		sum1 += x2 * curveValues[i][2];
987 		sum1 += x2 * curveValues[i][3];
988 
989 		int sum2 = y1 * curveValues[i][0];
990 		sum2 += (y2 - 20) * curveValues[i][1];
991 		sum2 += (y2 - 10) * curveValues[i][2];
992 		sum2 += y2 * curveValues[i][3];
993 
994 		_curveData[i * 2] = (sum1 >> 15);
995 		_curveData[i * 2 + 1] = (sum2 >> 15);
996 	}
997 }
998 
mouseWeirdo()999 void PrinceEngine::mouseWeirdo() {
1000 	if (_mouseFlag == 3) {
1001 		int weirdDir = _randomSource.getRandomNumber(3);
1002 		Common::Point mousePos = _system->getEventManager()->getMousePos();
1003 		switch (weirdDir) {
1004 		case 0:
1005 			mousePos.x += kCelStep;
1006 			break;
1007 		case 1:
1008 			mousePos.x -= kCelStep;
1009 			break;
1010 		case 2:
1011 			mousePos.y += kCelStep;
1012 			break;
1013 		case 3:
1014 			mousePos.y -= kCelStep;
1015 			break;
1016 		default:
1017 			break;
1018 		}
1019 		mousePos.x = CLIP(mousePos.x, (int16) 315, (int16) 639);
1020 		_flags->setFlagValue(Flags::MXFLAG, mousePos.x);
1021 		mousePos.y = CLIP(mousePos.y, (int16) 0, (int16) 170);
1022 		_flags->setFlagValue(Flags::MYFLAG, mousePos.y);
1023 		_system->warpMouse(mousePos.x, mousePos.y);
1024 	}
1025 }
1026 
showPower()1027 void PrinceEngine::showPower() {
1028 	if (_flags->getFlagValue(Flags::POWERENABLED)) {
1029 		int power = _flags->getFlagValue(Flags::POWER);
1030 
1031 		byte *dst = (byte *)_graph->_frontScreen->getBasePtr(kPowerBarPosX, kPowerBarPosY);
1032 		for (int y = 0; y < kPowerBarHeight; y++) {
1033 			byte *dst2 = dst;
1034 			for (int x = 0; x < kPowerBarWidth; x++, dst2++) {
1035 				*dst2 = kPowerBarBackgroundColor;
1036 			}
1037 			dst += _graph->_frontScreen->pitch;
1038 		}
1039 
1040 		if (power) {
1041 			dst = (byte *)_graph->_frontScreen->getBasePtr(kPowerBarPosX, kPowerBarGreenPosY);
1042 			for (int y = 0; y < kPowerBarGreenHeight; y++) {
1043 				byte *dst2 = dst;
1044 				for (int x = 0; x < power + 1; x++, dst2++) {
1045 					if (x < 58) {
1046 						*dst2 = kPowerBarGreenColor1;
1047 					} else {
1048 						*dst2 = kPowerBarGreenColor2;
1049 					}
1050 				}
1051 				dst += _graph->_frontScreen->pitch;
1052 			}
1053 		}
1054 
1055 		_graph->change();
1056 	}
1057 }
1058 
scrollCredits()1059 void PrinceEngine::scrollCredits() {
1060 	byte *scrollAdress = _creditsData;
1061 	while (!shouldQuit()) {
1062 		for (int scrollPos = 0; scrollPos > -23; scrollPos--) {
1063 			const Graphics::Surface *roomSurface = _roomBmp->getSurface();
1064 			if (roomSurface) {
1065 				_graph->draw(_graph->_frontScreen, roomSurface);
1066 			}
1067 			char *s = (char *)scrollAdress;
1068 			int drawY = scrollPos;
1069 			for (int i = 0; i < 22; i++) {
1070 				Common::String line;
1071 				char *linePos = s;
1072 				while ((*linePos != 13)) {
1073 					line += *linePos;
1074 					linePos++;
1075 				}
1076 				if (!line.empty()) {
1077 					int drawX = (kNormalWidth - getTextWidth(line.c_str())) / 2;
1078 					_font->drawString(_graph->_frontScreen, line, drawX, drawY, _graph->_frontScreen->w, 217);
1079 				}
1080 
1081 				char letter1;
1082 				bool gotIt1 = false;
1083 				do {
1084 					letter1 = *s;
1085 					s++;
1086 					if (letter1 == 13) {
1087 						if (*s == 10) {
1088 							s++;
1089 						}
1090 						if (*s != 35) {
1091 							gotIt1 = true;
1092 						}
1093 						break;
1094 					}
1095 				} while (letter1 != 35);
1096 
1097 				if (gotIt1) {
1098 					drawY += 23;
1099 				} else {
1100 					break;
1101 				}
1102 			}
1103 			Common::Event event;
1104 			Common::EventManager *eventMan = _system->getEventManager();
1105 			while (eventMan->pollEvent(event)) {
1106 				if (event.type == Common::EVENT_KEYDOWN) {
1107 					if (event.kbd.keycode == Common::KEYCODE_ESCAPE) {
1108 						blackPalette();
1109 						return;
1110 					}
1111 				}
1112 			}
1113 			if (shouldQuit()) {
1114 				return;
1115 			}
1116 			_graph->change();
1117 			_graph->update(_graph->_frontScreen);
1118 			pausePrinceEngine(kFPS * 2);
1119 		}
1120 		char letter2;
1121 		byte *scan2 = scrollAdress;
1122 		bool gotIt2 = false;
1123 		do {
1124 			letter2 = *scan2;
1125 			scan2++;
1126 			if (letter2 == 13) {
1127 				if (*scan2 == 10) {
1128 					scan2++;
1129 				}
1130 				if (*scan2 != 35) {
1131 					gotIt2 = true;
1132 				}
1133 				break;
1134 			}
1135 		} while (letter2 != 35);
1136 		if (gotIt2) {
1137 			scrollAdress = scan2;
1138 		} else {
1139 			break;
1140 		}
1141 	}
1142 	blackPalette();
1143 }
1144 
mainLoop()1145 void PrinceEngine::mainLoop() {
1146 	changeCursor(0);
1147 	_currentTime = _system->getMillis();
1148 
1149 	while (!shouldQuit()) {
1150 		Common::Event event;
1151 		Common::EventManager *eventMan = _system->getEventManager();
1152 		while (eventMan->pollEvent(event)) {
1153 			switch (event.type) {
1154 			case Common::EVENT_KEYDOWN:
1155 				keyHandler(event);
1156 				break;
1157 			case Common::EVENT_LBUTTONDOWN:
1158 				leftMouseButton();
1159 				break;
1160 			case Common::EVENT_RBUTTONDOWN:
1161 				rightMouseButton();
1162 				break;
1163 			default:
1164 				break;
1165 			}
1166 		}
1167 
1168 		if (shouldQuit()) {
1169 			return;
1170 		}
1171 
1172 		// for "throw a rock" mini-game
1173 		mouseWeirdo();
1174 
1175 		_interpreter->stepBg();
1176 		_interpreter->stepFg();
1177 
1178 		drawScreen();
1179 
1180 		_graph->update(_graph->_frontScreen);
1181 
1182 		openInventoryCheck();
1183 
1184 		pausePrinceEngine();
1185 	}
1186 }
1187 
1188 } // End of namespace Prince
1189