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 "twine/menu/menu.h"
24 #include "audio/mixer.h"
25 #include "backends/audiocd/audiocd.h"
26 #include "backends/keymapper/keymapper.h"
27 #include "common/config-manager.h"
28 #include "common/events.h"
29 #include "common/keyboard.h"
30 #include "common/scummsys.h"
31 #include "common/system.h"
32 #include "common/util.h"
33 #include "graphics/cursorman.h"
34 #include "twine/scene/actor.h"
35 #include "twine/scene/animations.h"
36 #include "twine/audio/music.h"
37 #include "twine/audio/sound.h"
38 #include "twine/movies.h"
39 #include "twine/scene/gamestate.h"
40 #include "twine/scene/grid.h"
41 #include "twine/resources/hqr.h"
42 #include "twine/input.h"
43 #include "twine/menu/interface.h"
44 #include "twine/menu/menuoptions.h"
45 #include "twine/scene/movements.h"
46 #include "twine/renderer/redraw.h"
47 #include "twine/renderer/renderer.h"
48 #include "twine/renderer/screens.h"
49 #include "twine/resources/resources.h"
50 #include "twine/scene/scene.h"
51 #include "twine/shared.h"
52 #include "twine/text.h"
53 #include "twine/twine.h"
54 
55 namespace TwinE {
56 
57 static const uint32 kPlasmaEffectFilesize = 262176;
58 
59 namespace MenuButtonTypes {
60 enum MenuButtonTypesEnum {
61 	kMusicVolume = 1,
62 	kSoundVolume = 2,
63 	kCDVolume = 3,
64 	kSpeechVolume = 4,
65 	kAggressiveMode = 5,
66 	kPolygonDetails = 6,
67 	kShadowSettings = 7,
68 	kSceneryZoom = 8,
69 	kHighResolution = 9,
70 	kWallCollision = 10
71 };
72 }
73 
74 #define checkMenuQuit(callMenu)      \
75 	if ((callMenu) == kQuitEngine) { \
76 		return kQuitEngine;          \
77 	}
78 #define kBackground 9999
79 
80 namespace _priv {
81 
createMainMenu()82 static MenuSettings createMainMenu() {
83 	MenuSettings settings;
84 	settings.setButtonsBoxHeight(200);
85 	settings.addButton(TextId::kNewGame);
86 	settings.addButton(TextId::kContinueGame);
87 	settings.addButton(TextId::kOptions);
88 	settings.addButton(TextId::kQuit);
89 	return settings;
90 }
91 
createGiveUpMenu()92 static MenuSettings createGiveUpMenu() {
93 	MenuSettings settings;
94 	settings.setButtonsBoxHeight(240);
95 	settings.addButton(TextId::kContinue);
96 	settings.addButton(TextId::kGiveUp);
97 	return settings;
98 }
99 
createGiveUpSaveMenu()100 static MenuSettings createGiveUpSaveMenu() {
101 	MenuSettings settings;
102 	settings.setButtonsBoxHeight(240);
103 	settings.addButton(TextId::kContinue);
104 	settings.addButton(TextId::kCreateSaveGame);
105 	settings.addButton(TextId::kGiveUp);
106 	return settings;
107 }
108 
createOptionsMenu()109 static MenuSettings createOptionsMenu() {
110 	MenuSettings settings;
111 	settings.addButton(TextId::kReturnMenu);
112 	settings.addButton(TextId::kVolumeSettings);
113 	settings.addButton(TextId::kSaveManage);
114 	settings.addButton(TextId::kAdvanced);
115 	return settings;
116 }
117 
createAdvancedOptionsMenu()118 static MenuSettings createAdvancedOptionsMenu() {
119 	MenuSettings settings;
120 	settings.addButton(TextId::kReturnMenu);
121 	settings.addButton(TextId::kBehaviourAggressiveManual, MenuButtonTypes::kAggressiveMode);
122 	settings.addButton(TextId::kDetailsPolygonsHigh, MenuButtonTypes::kPolygonDetails);
123 	settings.addButton(TextId::kDetailsShadowHigh, MenuButtonTypes::kShadowSettings);
124 	settings.addButton(TextId::kSceneryZoomOn, MenuButtonTypes::kSceneryZoom);
125 	settings.addButton(TextId::kCustomHighResOptionOn, MenuButtonTypes::kHighResolution);
126 	settings.addButton(TextId::kCustomWallCollisionOff, MenuButtonTypes::kWallCollision);
127 	return settings;
128 }
129 
createSaveManageMenu()130 static MenuSettings createSaveManageMenu() {
131 	MenuSettings settings;
132 	settings.addButton(TextId::kReturnMenu);
133 	settings.addButton(TextId::kCreateSaveGame);
134 	settings.addButton(TextId::kDeleteSaveGame);
135 	return settings;
136 }
137 
createVolumeMenu()138 static MenuSettings createVolumeMenu() {
139 	MenuSettings settings;
140 	settings.addButton(TextId::kReturnMenu);
141 	settings.addButton(TextId::kMusicVolume, MenuButtonTypes::kMusicVolume);
142 	settings.addButton(TextId::kSoundVolume, MenuButtonTypes::kSoundVolume);
143 	settings.addButton(TextId::kCDVolume, MenuButtonTypes::kCDVolume);
144 	settings.addButton(TextId::kSpeechVolume, MenuButtonTypes::kSpeechVolume);
145 	return settings;
146 }
147 
148 } // namespace _priv
149 
getButtonText(Text * text,int buttonIndex)150 const char *MenuSettings::getButtonText(Text *text, int buttonIndex) {
151 	if (_buttonTexts[buttonIndex].empty()) {
152 		const TextId textId = getButtonTextId(buttonIndex);
153 		char dialText[256] = "";
154 		text->getMenuText(textId, dialText, sizeof(dialText));
155 		_buttonTexts[buttonIndex] = dialText;
156 	}
157 	return _buttonTexts[buttonIndex].c_str();
158 }
159 
Menu(TwinEEngine * engine)160 Menu::Menu(TwinEEngine *engine) {
161 	_engine = engine;
162 
163 	_optionsMenuState = _priv::createOptionsMenu();
164 	_giveUpMenuWithSaveState = _priv::createGiveUpSaveMenu();
165 	_volumeMenuState = _priv::createVolumeMenu();
166 	_saveManageMenuState = _priv::createSaveManageMenu();
167 	_giveUpMenuState = _priv::createGiveUpMenu();
168 	_mainMenuState = _priv::createMainMenu();
169 	_advOptionsMenuState = _priv::createAdvancedOptionsMenu();
170 
171 	Common::fill(&_behaviourAnimState[0], &_behaviourAnimState[4], 0);
172 	Common::fill(&_itemAngle[0], &_itemAngle[NUM_INVENTORY_ITEMS], 0);
173 }
174 
~Menu()175 Menu::~Menu() {
176 	free(_plasmaEffectPtr);
177 }
178 
plasmaEffectRenderFrame()179 void Menu::plasmaEffectRenderFrame() {
180 	for (int32 j = 1; j < PLASMA_HEIGHT - 1; j++) {
181 		for (int32 i = 1; i < PLASMA_WIDTH - 1; i++) {
182 			/* Here we calculate the average of all 8 neighbour pixel values */
183 
184 			int16 c;
185 			c = _plasmaEffectPtr[(i - 1) + (j - 1) * PLASMA_WIDTH];  //top-left
186 			c += _plasmaEffectPtr[(i + 0) + (j - 1) * PLASMA_WIDTH]; //top
187 			c += _plasmaEffectPtr[(i + 1) + (j - 1) * PLASMA_WIDTH]; //top-right
188 
189 			c += _plasmaEffectPtr[(i - 1) + (j + 0) * PLASMA_WIDTH]; //left
190 			c += _plasmaEffectPtr[(i + 1) + (j + 0) * PLASMA_WIDTH]; //right
191 
192 			c += _plasmaEffectPtr[(i - 1) + (j + 1) * PLASMA_WIDTH]; // bottom-left
193 			c += _plasmaEffectPtr[(i + 0) + (j + 1) * PLASMA_WIDTH]; // bottom
194 			c += _plasmaEffectPtr[(i + 1) + (j + 1) * PLASMA_WIDTH]; // bottom-right
195 
196 			/* And the 2 least significant bits are used as a
197 			 * randomizing parameter for statistically fading the flames */
198 			c = (c >> 3) | ((c & 0x0003) << 13);
199 
200 			if (!(c & 0x6500) &&
201 			    (j >= (PLASMA_HEIGHT - 4) || c > 0)) {
202 				c--; /*fade this pixel*/
203 			}
204 
205 			/* plot the pixel using the calculated color */
206 			_plasmaEffectPtr[i + (PLASMA_HEIGHT + j) * PLASMA_WIDTH] = (uint8)c;
207 		}
208 	}
209 
210 	// flip the double-buffer while scrolling the effect vertically:
211 	const uint8 *src = _plasmaEffectPtr + (PLASMA_HEIGHT + 1) * PLASMA_WIDTH;
212 	memcpy(_plasmaEffectPtr, src, PLASMA_HEIGHT * PLASMA_WIDTH);
213 }
214 
processPlasmaEffect(const Common::Rect & rect,int32 color)215 void Menu::processPlasmaEffect(const Common::Rect &rect, int32 color) {
216 	const int32 max_value = color + 15;
217 
218 	plasmaEffectRenderFrame();
219 
220 	const uint8 *in = _plasmaEffectPtr + 5 * PLASMA_WIDTH;
221 	uint8 *out = (uint8 *)_engine->_imageBuffer.getBasePtr(0, 0);
222 
223 	for (int32 y = 0; y < PLASMA_HEIGHT / 2; y++) {
224 		int32 yOffset = y * _engine->_imageBuffer.w;
225 		const uint8 *colPtr = &in[y * PLASMA_WIDTH];
226 		for (int32 x = 0; x < PLASMA_WIDTH; x++) {
227 			const uint8 c = MIN(*colPtr / 2 + color, max_value);
228 			/* 2x2 squares sharing the same pixel color: */
229 			const int32 target = 2 * yOffset;
230 			out[target + 0] = c;
231 			out[target + 1] = c;
232 			out[target + _engine->_imageBuffer.w + 0] = c;
233 			out[target + _engine->_imageBuffer.w + 1] = c;
234 			++colPtr;
235 			++yOffset;
236 		}
237 	}
238 	const Common::Rect prect(0, 0, PLASMA_WIDTH, PLASMA_HEIGHT);
239 	_engine->_frontVideoBuffer.blitFrom(_engine->_imageBuffer, prect, rect);
240 }
241 
drawRectBorders(const Common::Rect & rect,int32 colorLeftTop,int32 colorRightBottom)242 void Menu::drawRectBorders(const Common::Rect &rect, int32 colorLeftTop, int32 colorRightBottom) {
243 	_engine->_interface->drawLine(rect.left, rect.top, rect.right, rect.top, colorLeftTop);           // top line
244 	_engine->_interface->drawLine(rect.left, rect.top, rect.left, rect.bottom, colorLeftTop);         // left line
245 	_engine->_interface->drawLine(rect.right, rect.top + 1, rect.right, rect.bottom, colorRightBottom);   // right line
246 	_engine->_interface->drawLine(rect.left + 1, rect.bottom, rect.right, rect.bottom, colorRightBottom); // bottom line
247 }
248 
drawRectBorders(int32 left,int32 top,int32 right,int32 bottom,int32 colorLeftTop,int32 colorRightBottom)249 void Menu::drawRectBorders(int32 left, int32 top, int32 right, int32 bottom, int32 colorLeftTop, int32 colorRightBottom) {
250 	drawRectBorders(Common::Rect(left, top, right, bottom), colorLeftTop, colorLeftTop);
251 }
252 
drawButtonGfx(const MenuSettings * menuSettings,const Common::Rect & rect,int32 buttonId,const char * dialText,bool hover)253 void Menu::drawButtonGfx(const MenuSettings *menuSettings, const Common::Rect &rect, int32 buttonId, const char *dialText, bool hover) {
254 	if (hover) {
255 		if (menuSettings == &_volumeMenuState && buttonId <= MenuButtonTypes::kSpeechVolume && buttonId >= MenuButtonTypes::kMusicVolume) {
256 			int32 newWidth = 0;
257 			switch (buttonId) {
258 			case MenuButtonTypes::kMusicVolume: {
259 				const int volume = _engine->_system->getMixer()->getVolumeForSoundType(Audio::Mixer::kMusicSoundType);
260 				newWidth = _engine->_screens->lerp(rect.left, rect.right, Audio::Mixer::kMaxMixerVolume, volume);
261 				break;
262 			}
263 			case MenuButtonTypes::kSoundVolume: {
264 				const int volume = _engine->_system->getMixer()->getVolumeForSoundType(Audio::Mixer::kSFXSoundType);
265 				newWidth = _engine->_screens->lerp(rect.left, rect.right, Audio::Mixer::kMaxMixerVolume, volume);
266 				break;
267 			}
268 			case MenuButtonTypes::kCDVolume: {
269 				const AudioCDManager::Status status = _engine->_system->getAudioCDManager()->getStatus();
270 				newWidth = _engine->_screens->lerp(rect.left, rect.right, Audio::Mixer::kMaxMixerVolume, status.volume);
271 				break;
272 			}
273 			case MenuButtonTypes::kSpeechVolume: {
274 				const int volume = _engine->_system->getMixer()->getVolumeForSoundType(Audio::Mixer::kSpeechSoundType);
275 				newWidth = _engine->_screens->lerp(rect.left, rect.right, Audio::Mixer::kMaxMixerVolume, volume);
276 				break;
277 			}
278 			}
279 
280 			processPlasmaEffect(rect, COLOR_80);
281 			if (!(_engine->getRandomNumber() % 5)) {
282 				_plasmaEffectPtr[_engine->getRandomNumber() % 140 * 10 + 1900] = 255;
283 			}
284 			_engine->_interface->drawFilledRect(Common::Rect(newWidth, rect.top, rect.right, rect.bottom), COLOR_68);
285 		} else {
286 			processPlasmaEffect(rect, COLOR_64);
287 			if (!(_engine->getRandomNumber() % 5)) {
288 				_plasmaEffectPtr[_engine->getRandomNumber() % PLASMA_WIDTH * 10 + 6400] = 255;
289 			}
290 		}
291 	} else {
292 		_engine->blitWorkToFront(rect);
293 		_engine->_interface->drawTransparentBox(rect, 4);
294 	}
295 
296 	drawRectBorders(rect);
297 
298 	_engine->_text->setFontColor(COLOR_WHITE);
299 	_engine->_text->setFontParameters(2, 8);
300 	const int32 textSize = _engine->_text->getTextSize(dialText);
301 	_engine->_text->drawText((_engine->width() / 2) - (textSize / 2), rect.top + 7, dialText);
302 }
303 
drawButtons(MenuSettings * menuSettings,bool hover)304 int16 Menu::drawButtons(MenuSettings *menuSettings, bool hover) {
305 	int16 buttonNumber = menuSettings->getActiveButton();
306 	const int32 maxButton = menuSettings->getButtonCount();
307 	int32 topHeight = menuSettings->getButtonBoxHeight();
308 
309 	if (topHeight == 0) {
310 		topHeight = 35;
311 	} else {
312 		topHeight = topHeight - (((maxButton - 1) * 6) + (maxButton * 50)) / 2;
313 	}
314 
315 	if (maxButton <= 0) {
316 		return -1;
317 	}
318 
319 	int16 mouseActiveButton = -1;
320 
321 	for (int16 i = 0; i < maxButton; ++i) {
322 		if (menuSettings == &_advOptionsMenuState) {
323 			int16 id = menuSettings->getButtonState(i);
324 			switch (id) {
325 			case MenuButtonTypes::kAggressiveMode:
326 				if (_engine->_actor->_autoAggressive) {
327 					menuSettings->setButtonTextId(i, TextId::kBehaviourAggressiveAuto);
328 				} else {
329 					menuSettings->setButtonTextId(i, TextId::kBehaviourAggressiveManual);
330 				}
331 				break;
332 			case MenuButtonTypes::kPolygonDetails:
333 				if (_engine->_cfgfile.PolygonDetails == 0) {
334 					menuSettings->setButtonTextId(i, TextId::kDetailsPolygonsLow);
335 				} else if (_engine->_cfgfile.PolygonDetails == 1) {
336 					menuSettings->setButtonTextId(i, TextId::kDetailsPolygonsMiddle);
337 				} else {
338 					menuSettings->setButtonTextId(i, TextId::kDetailsPolygonsHigh);
339 				}
340 				break;
341 			case MenuButtonTypes::kShadowSettings:
342 				if (_engine->_cfgfile.ShadowMode == 0) {
343 					menuSettings->setButtonTextId(i, TextId::kShadowsDisabled);
344 				} else if (_engine->_cfgfile.ShadowMode == 1) {
345 					menuSettings->setButtonTextId(i, TextId::kShadowsFigures);
346 				} else {
347 					menuSettings->setButtonTextId(i, TextId::kDetailsShadowHigh);
348 				}
349 				break;
350 			case MenuButtonTypes::kSceneryZoom:
351 				if (_engine->_cfgfile.SceZoom) {
352 					menuSettings->setButtonTextId(i, TextId::kSceneryZoomOn);
353 				} else {
354 					menuSettings->setButtonTextId(i, TextId::kNoSceneryZoom);
355 				}
356 				break;
357 			case MenuButtonTypes::kHighResolution: {
358 				if (ConfMan.getBool("usehighres")) {
359 					menuSettings->setButtonTextId(i, TextId::kCustomHighResOptionOn);
360 				} else {
361 					menuSettings->setButtonTextId(i, TextId::kCustomHighResOptionOff);
362 				}
363 				break;
364 			}
365 			case MenuButtonTypes::kWallCollision: {
366 				if (ConfMan.getBool("wallcollision")) {
367 					menuSettings->setButtonTextId(i, TextId::kCustomWallCollisionOn);
368 				} else {
369 					menuSettings->setButtonTextId(i, TextId::kCustomWallCollisionOff);
370 				}
371 				break;
372 			}
373 			default:
374 				break;
375 			}
376 		}
377 		const int32 menuItemId = menuSettings->getButtonState(i);
378 		const char *text = menuSettings->getButtonText(_engine->_text, i);
379 		const int32 border = 45;
380 		const int32 mainMenuButtonHeightHalf = 25;
381 		const Common::Rect rect(border, topHeight - mainMenuButtonHeightHalf, _engine->width() - border, topHeight + mainMenuButtonHeightHalf);
382 		if (hover) {
383 			if (i == buttonNumber) {
384 				drawButtonGfx(menuSettings, rect, menuItemId, text, hover);
385 			}
386 		} else {
387 			if (i == buttonNumber) {
388 				drawButtonGfx(menuSettings, rect, menuItemId, text, true);
389 			} else {
390 				drawButtonGfx(menuSettings, rect, menuItemId, text, false);
391 			}
392 		}
393 		if (_engine->_input->isMouseHovering(rect)) {
394 			mouseActiveButton = i;
395 		}
396 
397 		topHeight += 56; // increase button top height
398 	}
399 	return mouseActiveButton;
400 }
401 
processMenu(MenuSettings * menuSettings,bool showCredits)402 int32 Menu::processMenu(MenuSettings *menuSettings, bool showCredits) {
403 	int16 currentButton = menuSettings->getActiveButton();
404 	bool buttonsNeedRedraw = true;
405 	const int32 numEntry = menuSettings->getButtonCount();
406 	int32 maxButton = numEntry - 1;
407 	Common::Point mousepos = _engine->_input->getMousePositions();
408 	bool useMouse = true;
409 
410 	_engine->_input->enableKeyMap(uiKeyMapId);
411 
412 	// if we are running the game already, the buttons are just rendered on top of the scene
413 	if (_engine->_scene->isGameRunning()) {
414 		_engine->restoreFrontBuffer();
415 	} else {
416 		_engine->_screens->loadMenuImage(false);
417 	}
418 	uint32 startMillis = _engine->_system->getMillis();
419 	do {
420 		FrameMarker frame(_engine);
421 		const uint32 loopMillis = _engine->_system->getMillis();
422 		_engine->readKeys();
423 
424 		Common::Point newmousepos = _engine->_input->getMousePositions();
425 		if (mousepos != newmousepos) {
426 			useMouse = true;
427 			mousepos = newmousepos;
428 		}
429 
430 		if (_engine->_input->toggleActionIfActive(TwinEActionType::UIDown)) {
431 			currentButton++;
432 			if (currentButton == numEntry) { // if current button is the last, than next button is the first
433 				currentButton = 0;
434 			}
435 			useMouse = false;
436 			buttonsNeedRedraw = true;
437 			startMillis = loopMillis;
438 		} else if (_engine->_input->toggleActionIfActive(TwinEActionType::UIUp)) {
439 			currentButton--;
440 			if (currentButton < 0) { // if current button is the first, than previous button is the last
441 				currentButton = maxButton;
442 			}
443 			useMouse = false;
444 			buttonsNeedRedraw = true;
445 			startMillis = loopMillis;
446 		}
447 
448 		const int16 id = menuSettings->getActiveButtonState();
449 		if (menuSettings == &_advOptionsMenuState) {
450 			switch (id) {
451 			case MenuButtonTypes::kAggressiveMode:
452 				if (_engine->_input->toggleActionIfActive(TwinEActionType::UILeft) || _engine->_input->toggleActionIfActive(TwinEActionType::UIRight) || _engine->_input->toggleActionIfActive(TwinEActionType::UIEnter)) {
453 					_engine->_actor->_autoAggressive = !_engine->_actor->_autoAggressive;
454 					startMillis = loopMillis;
455 				}
456 				break;
457 			case MenuButtonTypes::kPolygonDetails:
458 				if (_engine->_input->toggleActionIfActive(TwinEActionType::UILeft)) {
459 					_engine->_cfgfile.PolygonDetails--;
460 					_engine->_cfgfile.PolygonDetails %= 3;
461 					startMillis = loopMillis;
462 				} else if (_engine->_input->toggleActionIfActive(TwinEActionType::UIRight) || _engine->_input->toggleActionIfActive(TwinEActionType::UIEnter)) {
463 					_engine->_cfgfile.PolygonDetails++;
464 					_engine->_cfgfile.PolygonDetails %= 3;
465 					startMillis = loopMillis;
466 				}
467 				break;
468 			case MenuButtonTypes::kShadowSettings:
469 				if (_engine->_input->toggleActionIfActive(TwinEActionType::UILeft)) {
470 					_engine->_cfgfile.ShadowMode--;
471 					_engine->_cfgfile.ShadowMode %= 3;
472 					startMillis = loopMillis;
473 				} else if (_engine->_input->toggleActionIfActive(TwinEActionType::UIRight) || _engine->_input->toggleActionIfActive(TwinEActionType::UIEnter)) {
474 					_engine->_cfgfile.ShadowMode++;
475 					_engine->_cfgfile.ShadowMode %= 3;
476 					startMillis = loopMillis;
477 				}
478 				break;
479 			case MenuButtonTypes::kSceneryZoom:
480 				if (_engine->_input->toggleActionIfActive(TwinEActionType::UILeft) || _engine->_input->toggleActionIfActive(TwinEActionType::UIRight) || _engine->_input->toggleActionIfActive(TwinEActionType::UIEnter)) {
481 					_engine->_cfgfile.SceZoom = !_engine->_cfgfile.SceZoom;
482 					startMillis = loopMillis;
483 				}
484 				break;
485 			case MenuButtonTypes::kHighResolution:
486 				if (_engine->_input->toggleActionIfActive(TwinEActionType::UILeft) || _engine->_input->toggleActionIfActive(TwinEActionType::UIRight) || _engine->_input->toggleActionIfActive(TwinEActionType::UIEnter)) {
487 					const bool highRes = ConfMan.getBool("usehighres");
488 					ConfMan.setBool("usehighres", !highRes);
489 					startMillis = loopMillis;
490 				}
491 				break;
492 			case MenuButtonTypes::kWallCollision:
493 				if (_engine->_input->toggleActionIfActive(TwinEActionType::UILeft) || _engine->_input->toggleActionIfActive(TwinEActionType::UIRight) || _engine->_input->toggleActionIfActive(TwinEActionType::UIEnter)) {
494 					const bool highRes = ConfMan.getBool("wallcollision");
495 					ConfMan.setBool("wallcollision", !highRes);
496 					startMillis = loopMillis;
497 				}
498 				break;
499 			default:
500 				break;
501 			}
502 		} else if (menuSettings == &_volumeMenuState) {
503 			Audio::Mixer *mixer = _engine->_system->getMixer();
504 			switch (id) {
505 			case MenuButtonTypes::kMusicVolume: {
506 				int volume = mixer->getVolumeForSoundType(Audio::Mixer::SoundType::kMusicSoundType);
507 				if (_engine->_input->isActionActive(TwinEActionType::UILeft)) {
508 					volume -= 4;
509 					startMillis = loopMillis;
510 				} else if (_engine->_input->isActionActive(TwinEActionType::UIRight)) {
511 					volume += 4;
512 					startMillis = loopMillis;
513 				}
514 				_engine->_music->musicVolume(volume);
515 				ConfMan.setInt("music_volume", mixer->getVolumeForSoundType(Audio::Mixer::kMusicSoundType));
516 				break;
517 			}
518 			case MenuButtonTypes::kSoundVolume: {
519 				int volume = mixer->getVolumeForSoundType(Audio::Mixer::kSFXSoundType);
520 				if (_engine->_input->isActionActive(TwinEActionType::UILeft)) {
521 					volume -= 4;
522 					startMillis = loopMillis;
523 				} else if (_engine->_input->isActionActive(TwinEActionType::UIRight)) {
524 					volume += 4;
525 					startMillis = loopMillis;
526 				}
527 
528 				mixer->setVolumeForSoundType(Audio::Mixer::kSFXSoundType, volume);
529 				ConfMan.setInt("sfx_volume", mixer->getVolumeForSoundType(Audio::Mixer::kSFXSoundType));
530 				break;
531 			}
532 			case MenuButtonTypes::kCDVolume: {
533 				AudioCDManager::Status status = _engine->_system->getAudioCDManager()->getStatus();
534 				if (_engine->_input->isActionActive(TwinEActionType::UILeft)) {
535 					status.volume -= 4;
536 					startMillis = loopMillis;
537 				} else if (_engine->_input->isActionActive(TwinEActionType::UIRight)) {
538 					status.volume += 4;
539 					startMillis = loopMillis;
540 				}
541 				status.volume = CLIP(status.volume, 0, 255);
542 				_engine->_system->getAudioCDManager()->setVolume(status.volume);
543 				break;
544 			}
545 			case MenuButtonTypes::kSpeechVolume: {
546 				int volume = mixer->getVolumeForSoundType(Audio::Mixer::kSpeechSoundType);
547 				if (_engine->_input->isActionActive(TwinEActionType::UILeft)) {
548 					volume -= 4;
549 					startMillis = loopMillis;
550 				} else if (_engine->_input->isActionActive(TwinEActionType::UIRight)) {
551 					volume += 4;
552 					startMillis = loopMillis;
553 				}
554 				mixer->setVolumeForSoundType(Audio::Mixer::kSpeechSoundType, volume);
555 				ConfMan.setInt("speech_volume", mixer->getVolumeForSoundType(Audio::Mixer::kSpeechSoundType));
556 				break;
557 			}
558 			default:
559 				break;
560 			}
561 		}
562 
563 		if (buttonsNeedRedraw) {
564 			// draw all buttons
565 			const int16 mouseButtonHovered = drawButtons(menuSettings, false);
566 			if (useMouse && mouseButtonHovered != -1) {
567 				currentButton = mouseButtonHovered;
568 			}
569 			menuSettings->setActiveButton(currentButton);
570 		}
571 
572 		// draw plasma effect for the current selected button
573 		const int16 mouseButtonHovered = drawButtons(menuSettings, true);
574 		if (useMouse && mouseButtonHovered != -1) {
575 			if (mouseButtonHovered != currentButton) {
576 				buttonsNeedRedraw = true;
577 			}
578 			currentButton = mouseButtonHovered;
579 		}
580 
581 		if (_engine->shouldQuit()) {
582 			return kQuitEngine;
583 		}
584 		if (_engine->_input->toggleActionIfActive(TwinEActionType::UIAbort)) {
585 			for (int i = 0; i < menuSettings->getButtonCount(); ++i) {
586 				const TextId textId = menuSettings->getButtonTextId(i);
587 				if (textId == TextId::kReturnMenu || textId == TextId::kReturnGame || textId == TextId::kContinue) {
588 					return (int32)textId;
589 				}
590 			}
591 			startMillis = loopMillis;
592 		}
593 		if (showCredits && loopMillis - startMillis > 11650) {
594 			_engine->_menuOptions->showCredits();
595 			if (_engine->_flaMovies->playFlaMovie(FLA_DRAGON3)) {
596 				if (!_engine->_screens->loadImageDelay(TwineImage(Resources::HQR_RESS_FILE, 15, 16), 3)) {
597 					if (!_engine->_screens->loadImageDelay(TwineImage(Resources::HQR_RESS_FILE, 17, 18), 3)) {
598 						if (!_engine->_screens->loadImageDelay(TwineImage(Resources::HQR_RESS_FILE, 19, 20), 3)) {
599 							if (_engine->_flaMovies->playFlaMovie(FLA_BATEAU)) {
600 								if (_engine->_cfgfile.Version == USA_VERSION) {
601 									_engine->_screens->loadImageDelay(_engine->_resources->relentLogo(), 3);
602 								} else {
603 									_engine->_screens->loadImageDelay(_engine->_resources->lbaLogo(), 3);
604 								}
605 								_engine->_screens->adelineLogo();
606 							}
607 						}
608 					}
609 				}
610 			}
611 			_engine->_text->initTextBank(TextBankId::Options_and_menus);
612 			startMillis = _engine->_system->getMillis();
613 			_engine->_screens->loadMenuImage(false);
614 		}
615 	} while (!_engine->_input->toggleActionIfActive(TwinEActionType::UIEnter));
616 
617 	return (int32)menuSettings->getActiveButtonTextId();
618 }
619 
advoptionsMenu()620 int32 Menu::advoptionsMenu() {
621 	_engine->restoreFrontBuffer();
622 
623 	ScopedCursor scoped(_engine);
624 	for (;;) {
625 		switch (processMenu(&_advOptionsMenuState)) {
626 		case (int32)TextId::kReturnMenu: {
627 			return 0;
628 		}
629 		case kQuitEngine:
630 			return kQuitEngine;
631 		case (int32)TextId::kBehaviourAggressiveManual:
632 		case (int32)TextId::kDetailsPolygonsHigh:
633 		case (int32)TextId::kDetailsShadowHigh:
634 		case (int32)TextId::kSceneryZoomOn:
635 		default:
636 			warning("Unknown menu button handled");
637 			break;
638 		}
639 	}
640 
641 	return 0;
642 }
643 
savemanageMenu()644 int32 Menu::savemanageMenu() {
645 	_engine->restoreFrontBuffer();
646 
647 	ScopedCursor scoped(_engine);
648 	for (;;) {
649 		switch (processMenu(&_saveManageMenuState)) {
650 		case (int32)TextId::kReturnMenu:
651 			return 0;
652 		case (int32)TextId::kCreateSaveGame:
653 			_engine->_menuOptions->saveGameMenu();
654 			break;
655 		case (int32)TextId::kDeleteSaveGame:
656 			_engine->_menuOptions->deleteSaveMenu();
657 			break;
658 		case kQuitEngine:
659 			return kQuitEngine;
660 		default:
661 			warning("Unknown menu button handled");
662 			break;
663 		}
664 	}
665 
666 	return 0;
667 }
668 
volumeMenu()669 int32 Menu::volumeMenu() {
670 	_engine->restoreFrontBuffer();
671 
672 	ScopedCursor scoped(_engine);
673 	for (;;) {
674 		switch (processMenu(&_volumeMenuState)) {
675 		case (int32)TextId::kReturnMenu:
676 			return 0;
677 		case kQuitEngine:
678 			return kQuitEngine;
679 		case (int32)TextId::kMusicVolume:
680 		case (int32)TextId::kSoundVolume:
681 		case (int32)TextId::kCDVolume:
682 		case (int32)TextId::kSpeechVolume:
683 		default:
684 			warning("Unknown menu button handled");
685 			break;
686 		}
687 	}
688 
689 	return 0;
690 }
691 
inGameOptionsMenu()692 void Menu::inGameOptionsMenu() {
693 	_engine->_text->initTextBank(TextBankId::Options_and_menus);
694 	_optionsMenuState.setButtonTextId(0, TextId::kReturnGame);
695 	_engine->saveFrontBuffer();
696 	optionsMenu();
697 	_engine->_text->initSceneTextBank();
698 	_optionsMenuState.setButtonTextId(0, TextId::kReturnMenu);
699 }
700 
optionsMenu()701 int32 Menu::optionsMenu() {
702 	_engine->restoreFrontBuffer();
703 
704 	_engine->_sound->stopSamples();
705 	_engine->_music->playTrackMusic(9); // LBA's Theme
706 
707 	ScopedCursor scoped(_engine);
708 	for (;;) {
709 		switch (processMenu(&_optionsMenuState)) {
710 		case (int32)TextId::kReturnGame:
711 		case (int32)TextId::kReturnMenu: {
712 			return 0;
713 		}
714 		case (int32)TextId::kVolumeSettings: {
715 			checkMenuQuit(volumeMenu()) break;
716 		}
717 		case (int32)TextId::kSaveManage: {
718 			checkMenuQuit(savemanageMenu()) break;
719 		}
720 		case (int32)TextId::kAdvanced: {
721 			checkMenuQuit(advoptionsMenu()) break;
722 		}
723 		case kQuitEngine:
724 			return kQuitEngine;
725 		default:
726 			break;
727 		}
728 	}
729 
730 	return 0;
731 }
732 
733 static const byte cursorArrow[] = {
734 	1, 1, 3, 3, 3, 3, 3, 3, 3, 3, 3,
735 	1, 0, 1, 3, 3, 3, 3, 3, 3, 3, 3,
736 	1, 0, 0, 1, 3, 3, 3, 3, 3, 3, 3,
737 	1, 0, 0, 0, 1, 3, 3, 3, 3, 3, 3,
738 	1, 0, 0, 0, 0, 1, 3, 3, 3, 3, 3,
739 	1, 0, 0, 0, 0, 0, 1, 3, 3, 3, 3,
740 	1, 0, 0, 0, 0, 0, 0, 1, 3, 3, 3,
741 	1, 0, 0, 0, 0, 0, 0, 0, 1, 3, 3,
742 	1, 0, 0, 0, 0, 0, 0, 0, 0, 1, 3,
743 	1, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1,
744 	1, 0, 0, 1, 0, 0, 1, 3, 3, 3, 3,
745 	1, 0, 1, 3, 1, 0, 0, 1, 3, 3, 3,
746 	1, 1, 3, 3, 1, 0, 0, 1, 3, 3, 3,
747 	1, 3, 3, 3, 3, 1, 0, 0, 1, 3, 3,
748 	3, 3, 3, 3, 3, 1, 0, 0, 1, 3, 3,
749 	3, 3, 3, 3, 3, 3, 1, 1, 1, 3, 3};
750 
751 static const byte cursorPalette[] = {
752 	0, 0, 0,
753 	0xff, 0xff, 0xff};
754 
init()755 bool Menu::init() {
756 	// load menu effect file only once
757 	_plasmaEffectPtr = (uint8 *)malloc(kPlasmaEffectFilesize);
758 	memset(_plasmaEffectPtr, 0, kPlasmaEffectFilesize);
759 
760 	CursorMan.pushCursor(cursorArrow, 11, 16, 1, 1, 3);
761 	CursorMan.pushCursorPalette(cursorPalette, 0, 2);
762 	return HQR::getEntry(_plasmaEffectPtr, Resources::HQR_RESS_FILE, RESSHQR_PLASMAEFFECT) > 0;
763 }
764 
run()765 EngineState Menu::run() {
766 	FrameMarker frame(_engine);
767 	_engine->_text->initTextBank(TextBankId::Options_and_menus);
768 
769 	_engine->_music->playTrackMusic(9); // LBA's Theme
770 	_engine->_sound->stopSamples();
771 
772 	ScopedCursor scoped(_engine);
773 	switch (processMenu(&_mainMenuState)) {
774 	case (int32)TextId::kNewGame: {
775 		if (_engine->_menuOptions->newGameMenu()) {
776 			return EngineState::GameLoop;
777 		}
778 		break;
779 	}
780 	case (int32)TextId::kContinueGame: {
781 		if (_engine->_menuOptions->continueGameMenu()) {
782 			return EngineState::LoadedGame;
783 		}
784 		break;
785 	}
786 	case (int32)TextId::kOptions: {
787 		optionsMenu();
788 		break;
789 	}
790 	case kBackground: {
791 		_engine->_screens->loadMenuImage();
792 		break;
793 	}
794 	case (int32)TextId::kQuit:
795 	case kQuitEngine:
796 		debug("quit the game");
797 		return EngineState::QuitGame;
798 	}
799 	return EngineState::Menu;
800 }
801 
giveupMenu()802 int32 Menu::giveupMenu() {
803 	_engine->saveFrontBuffer();
804 	_engine->_sound->pauseSamples();
805 
806 	MenuSettings *localMenu;
807 	if (_engine->_cfgfile.UseAutoSaving) {
808 		localMenu = &_giveUpMenuState;
809 	} else {
810 		localMenu = &_giveUpMenuWithSaveState;
811 	}
812 
813 	ScopedCursor scoped(_engine);
814 
815 	int32 menuId;
816 	do {
817 		FrameMarker frame(_engine);
818 		_engine->_text->initTextBank(TextBankId::Options_and_menus);
819 		menuId = processMenu(localMenu);
820 		switch (menuId) {
821 		case (int32)TextId::kContinue:
822 			_engine->_sound->resumeSamples();
823 			break;
824 		case (int32)TextId::kGiveUp:
825 			_engine->_gameState->giveUp();
826 			return 1;
827 		case (int32)TextId::kCreateSaveGame:
828 			_engine->_menuOptions->saveGameMenu();
829 			break;
830 		case kQuitEngine:
831 			return kQuitEngine;
832 		default:
833 			warning("Unknown menu button handled: %i", menuId);
834 		}
835 		_engine->_text->initSceneTextBank();
836 	} while (menuId != (int32)TextId::kGiveUp && menuId != (int32)TextId::kContinue && menuId != (int32)TextId::kCreateSaveGame);
837 
838 	return 0;
839 }
840 
drawHealthBar(int32 left,int32 right,int32 top,int32 barLeftPadding,int32 barHeight)841 void Menu::drawHealthBar(int32 left, int32 right, int32 top, int32 barLeftPadding, int32 barHeight) {
842 	_engine->_grid->drawSprite(left, top + 3, _engine->_resources->_spriteData[SPRITEHQR_LIFEPOINTS]);
843 	const int32 barLeft = left + barLeftPadding;
844 	const int32 healthBarRight = _engine->_screens->lerp(barLeft, right, 50, _engine->_scene->_sceneHero->_life);
845 	const int32 barBottom = top + barHeight;
846 	_engine->_interface->drawFilledRect(Common::Rect(barLeft, top, healthBarRight, barBottom), COLOR_91);
847 	drawRectBorders(Common::Rect(barLeft, top, right, barBottom));
848 }
849 
drawCloverLeafs(int32 newBoxLeft,int32 boxRight,int32 top)850 void Menu::drawCloverLeafs(int32 newBoxLeft, int32 boxRight, int32 top) {
851 	// Clover leaf boxes
852 	for (int32 i = 0; i < _engine->_gameState->_inventoryNumLeafsBox; i++) {
853 		const int32 leftSpritePos = _engine->_screens->lerp(newBoxLeft, boxRight, 10, i);
854 		_engine->_grid->drawSprite(leftSpritePos, top + 58, _engine->_resources->_spriteData[SPRITEHQR_CLOVERLEAFBOX]);
855 	}
856 
857 	// Clover leafs
858 	for (int32 i = 0; i < _engine->_gameState->_inventoryNumLeafs; i++) {
859 		const int32 leftSpritePos = _engine->_screens->lerp(newBoxLeft, boxRight, 10, i);
860 		_engine->_grid->drawSprite(leftSpritePos + 2, top + 60, _engine->_resources->_spriteData[SPRITEHQR_CLOVERLEAF]);
861 	}
862 }
863 
drawMagicPointsBar(int32 left,int32 right,int32 top,int32 barLeftPadding,int32 barHeight)864 void Menu::drawMagicPointsBar(int32 left, int32 right, int32 top, int32 barLeftPadding, int32 barHeight) {
865 	if (_engine->_gameState->inventoryDisabled()) {
866 		return;
867 	}
868 	if (!_engine->_gameState->hasItem(InventoryItems::kiTunic)) {
869 		return;
870 	}
871 	_engine->_grid->drawSprite(left, top + 1, _engine->_resources->_spriteData[SPRITEHQR_MAGICPOINTS]);
872 	if (_engine->_gameState->_magicLevelIdx <= 0) {
873 		return;
874 	}
875 	const int32 barLeft = left + barLeftPadding;
876 	const int32 barBottom = top + barHeight;
877 	const int32 barRight = _engine->_screens->lerp(barLeft, right, 80, _engine->_gameState->_inventoryMagicPoints);
878 	const Common::Rect pointsRect(barLeft, top, barRight, barBottom);
879 	_engine->_interface->drawFilledRect(pointsRect, COLOR_75);
880 	drawRectBorders(barLeft, top, barLeft + _engine->_gameState->_magicLevelIdx * 80, barBottom);
881 }
882 
drawSpriteAndString(int32 left,int32 top,const SpriteData & spriteData,const Common::String & str,int32 color)883 void Menu::drawSpriteAndString(int32 left, int32 top, const SpriteData &spriteData, const Common::String &str, int32 color) {
884 	_engine->_grid->drawSprite(left, top + 15, spriteData);
885 	_engine->_text->setFontColor(color);
886 	_engine->_text->drawText(left + 30, top + 5, str.c_str());
887 }
888 
drawCoins(int32 left,int32 top)889 void Menu::drawCoins(int32 left, int32 top) {
890 	const Common::String &inventoryNumKashes = Common::String::format("%d", _engine->_gameState->_inventoryNumKashes);
891 	drawSpriteAndString(left, top, _engine->_resources->_spriteData[SPRITEHQR_KASHES], inventoryNumKashes);
892 }
893 
drawKeys(int32 left,int32 top)894 void Menu::drawKeys(int32 left, int32 top) {
895 	const Common::String &inventoryNumKeys = Common::String::format("%d", _engine->_gameState->_inventoryNumKeys);
896 	drawSpriteAndString(left, top, _engine->_resources->_spriteData[SPRITEHQR_KEY], inventoryNumKeys);
897 }
898 
drawInfoMenu(int16 left,int16 top,int16 width)899 void Menu::drawInfoMenu(int16 left, int16 top, int16 width) {
900 	_engine->_interface->resetClip();
901 	const int16 height = 80;
902 	const Common::Rect rect(left, top, left + width, top + height);
903 	drawRectBorders(rect);
904 	Common::Rect filledRect(rect);
905 	filledRect.grow(-1);
906 	_engine->_interface->drawFilledRect(filledRect, COLOR_BLACK);
907 
908 	const int32 boxLeft = left + 9;
909 	const int32 boxRight = left + 325;
910 	const int32 barPadding = 25;
911 	const int32 boxTop = top + 10;
912 	const int32 barHeight = 14;
913 	drawHealthBar(boxLeft, boxRight, boxTop, barPadding, barHeight);
914 	drawMagicPointsBar(boxLeft, boxRight, boxTop + 25, barPadding, barHeight);
915 
916 	const int32 posLeft = left + 340;
917 	drawCoins(posLeft, top);
918 	drawKeys(posLeft, top + 35);
919 	drawCloverLeafs(left + barPadding, boxRight, top);
920 
921 	_engine->copyBlockPhys(left, top, left + width, top + 135);
922 }
923 
calcBehaviourRect(int32 left,int32 top,HeroBehaviourType behaviour) const924 Common::Rect Menu::calcBehaviourRect(int32 left, int32 top, HeroBehaviourType behaviour) const {
925 	const int border = 10;
926 	const int32 padding = 11;
927 	const int32 width = 99;
928 	const int height = 119;
929 
930 	const int32 boxLeft = (int32)behaviour * (width + padding) + left + border;
931 	const int32 boxRight = boxLeft + width;
932 	const int32 boxTop = top + border;
933 	const int32 boxBottom = boxTop + height;
934 	return Common::Rect(boxLeft, boxTop, boxRight, boxBottom);
935 }
936 
isBehaviourHovered(int32 left,int32 top,HeroBehaviourType behaviour) const937 bool Menu::isBehaviourHovered(int32 left, int32 top, HeroBehaviourType behaviour) const {
938 	const Common::Rect &boxRect = calcBehaviourRect(left, top, behaviour);
939 	return _engine->_input->isMouseHovering(boxRect);
940 }
941 
drawBehaviour(int32 left,int32 top,HeroBehaviourType behaviour,int32 angle,bool cantDrawBox)942 void Menu::drawBehaviour(int32 left, int32 top, HeroBehaviourType behaviour, int32 angle, bool cantDrawBox) {
943 	const Common::Rect &boxRect = calcBehaviourRect(left, top, behaviour);
944 
945 	const int animIdx = _engine->_actor->_heroAnimIdx[(byte)behaviour];
946 	const AnimData &currentAnimData = _engine->_resources->_animData[animIdx];
947 
948 	uint currentAnimState = _behaviourAnimState[(byte)behaviour];
949 
950 	if (_engine->_animations->setModelAnimation(currentAnimState, currentAnimData, *_behaviourEntity, &_behaviourAnimData[(byte)behaviour])) {
951 		currentAnimState++; // keyframe
952 		if (currentAnimState >= currentAnimData.getNumKeyframes()) {
953 			currentAnimState = currentAnimData.getLoopFrame();
954 		}
955 		_behaviourAnimState[(byte)behaviour] = currentAnimState;
956 	}
957 
958 	if (!cantDrawBox) {
959 		Common::Rect boxRectCopy(boxRect);
960 		boxRectCopy.grow(1);
961 		drawRectBorders(boxRectCopy);
962 	}
963 
964 	_engine->_interface->saveClip();
965 	_engine->_interface->resetClip();
966 
967 	if (behaviour == _engine->_actor->_heroBehaviour) {
968 		const int titleOffset = 10;
969 		const int titleHeight = 40;
970 		const int32 titleBoxLeft = left + 10;
971 		const int32 titleBoxWidth = 430;
972 		const int32 titleBoxCenter = titleBoxLeft + titleBoxWidth / 2;
973 		const int32 titleBoxRight = titleBoxLeft + titleBoxWidth;
974 		const int32 titleBoxTop = boxRect.bottom + titleOffset;
975 		const int32 titleBoxBottom = titleBoxTop + titleHeight;
976 
977 		_engine->_interface->drawFilledRect(boxRect, COLOR_BRIGHT_BLUE2);
978 
979 		// behaviour menu title
980 		const Common::Rect titleRect(titleBoxLeft, titleBoxTop, titleBoxRight, titleBoxBottom);
981 		_engine->_interface->drawFilledRect(titleRect, COLOR_BLACK);
982 		drawRectBorders(titleRect);
983 
984 		_engine->_text->setFontColor(COLOR_WHITE);
985 
986 		char dialText[256];
987 		_engine->_text->getMenuText(_engine->_actor->getTextIdForBehaviour(), dialText, sizeof(dialText));
988 
989 		_engine->_text->drawText(titleBoxCenter - _engine->_text->getTextSize(dialText) / 2, titleBoxTop + 1, dialText);
990 	} else {
991 		_engine->_interface->drawFilledRect(boxRect, COLOR_BLACK);
992 	}
993 
994 	_engine->_renderer->renderBehaviourModel(boxRect, -600, angle, *_behaviourEntity, _moveMenu);
995 
996 	_engine->_interface->loadClip();
997 }
998 
prepareAndDrawBehaviour(int32 left,int32 top,int32 angle,HeroBehaviourType behaviour)999 void Menu::prepareAndDrawBehaviour(int32 left, int32 top, int32 angle, HeroBehaviourType behaviour) {
1000 	const int animIdx = _engine->_actor->_heroAnimIdx[(byte)behaviour];
1001 	_engine->_animations->setAnimAtKeyframe(_behaviourAnimState[(byte)behaviour], _engine->_resources->_animData[animIdx], *_behaviourEntity, &_behaviourAnimData[(byte)behaviour]);
1002 	drawBehaviour(left, top, behaviour, angle, false);
1003 }
1004 
drawBehaviourMenu(int32 left,int32 top,int32 angle)1005 void Menu::drawBehaviourMenu(int32 left, int32 top, int32 angle) {
1006 	const int32 width = 450;
1007 	const int32 height = 190;
1008 	const int32 right = left + width;
1009 	const int32 bottom = top + height;
1010 
1011 	const Common::Rect titleRect(left, top, right, bottom);
1012 	drawRectBorders(titleRect);
1013 
1014 	Common::Rect boxRect(titleRect);
1015 	boxRect.grow(-1);
1016 	_engine->_interface->drawTransparentBox(boxRect, 2);
1017 
1018 	prepareAndDrawBehaviour(left, top, angle, HeroBehaviourType::kNormal);
1019 	prepareAndDrawBehaviour(left, top, angle, HeroBehaviourType::kAthletic);
1020 	prepareAndDrawBehaviour(left, top, angle, HeroBehaviourType::kAggressive);
1021 	prepareAndDrawBehaviour(left, top, angle, HeroBehaviourType::kDiscrete);
1022 
1023 	_engine->copyBlockPhys(titleRect);
1024 
1025 	drawInfoMenu(titleRect.left, titleRect.bottom + 10, titleRect.width());
1026 }
1027 
processBehaviourMenu()1028 void Menu::processBehaviourMenu() {
1029 	_engine->exitSceneryView();
1030 	if (_engine->_actor->_heroBehaviour == HeroBehaviourType::kProtoPack) {
1031 		_engine->_sound->stopSamples();
1032 		_engine->_actor->setBehaviour(HeroBehaviourType::kNormal);
1033 	}
1034 
1035 	_behaviourEntity = &_engine->_resources->_bodyData[_engine->_scene->_sceneHero->_entity];
1036 
1037 	_engine->_actor->_heroAnimIdx[(byte)HeroBehaviourType::kNormal] = _engine->_actor->_heroAnimIdxNORMAL;
1038 	_engine->_actor->_heroAnimIdx[(byte)HeroBehaviourType::kAthletic] = _engine->_actor->_heroAnimIdxATHLETIC;
1039 	_engine->_actor->_heroAnimIdx[(byte)HeroBehaviourType::kAggressive] = _engine->_actor->_heroAnimIdxAGGRESSIVE;
1040 	_engine->_actor->_heroAnimIdx[(byte)HeroBehaviourType::kDiscrete] = _engine->_actor->_heroAnimIdxDISCRETE;
1041 
1042 	_engine->_movements->setActorAngleSafe(_engine->_scene->_sceneHero->_angle, _engine->_scene->_sceneHero->_angle - ANGLE_90, ANGLE_17, &_moveMenu);
1043 
1044 	_engine->saveFrontBuffer();
1045 
1046 	TextBankId tmpTextBank = _engine->_scene->_sceneTextBank;
1047 	_engine->_scene->_sceneTextBank = TextBankId::None;
1048 
1049 	_engine->_text->initTextBank(TextBankId::Options_and_menus);
1050 
1051 	const int32 left = _engine->width() / 2 - 220;
1052 	const int32 top = _engine->height() / 2 - 140;
1053 	drawBehaviourMenu(left, top, _engine->_scene->_sceneHero->_angle);
1054 
1055 	HeroBehaviourType tmpHeroBehaviour = _engine->_actor->_heroBehaviour;
1056 
1057 	const int animIdx = _engine->_actor->_heroAnimIdx[(byte)_engine->_actor->_heroBehaviour];
1058 	_engine->_animations->setAnimAtKeyframe(_behaviourAnimState[(byte)_engine->_actor->_heroBehaviour], _engine->_resources->_animData[animIdx], *_behaviourEntity, &_behaviourAnimData[(byte)_engine->_actor->_heroBehaviour]);
1059 
1060 	int32 tmpTime = _engine->_lbaTime;
1061 
1062 #if 0
1063 	ScopedCursor scopedCursor(_engine);
1064 #endif
1065 	ScopedKeyMap scopedKeyMap(_engine, uiKeyMapId);
1066 	while (_engine->_input->isActionActive(TwinEActionType::BehaviourMenu) || _engine->_input->isQuickBehaviourActionActive()) {
1067 		FrameMarker frame(_engine, 50);
1068 		_engine->readKeys();
1069 		if (_engine->shouldQuit()) {
1070 			break;
1071 		}
1072 
1073 #if 0
1074 		if (isBehaviourHovered(HeroBehaviourType::kNormal)) {
1075 			_engine->_actor->heroBehaviour = HeroBehaviourType::kNormal;
1076 		} else if (isBehaviourHovered(HeroBehaviourType::kAthletic)) {
1077 			_engine->_actor->heroBehaviour = HeroBehaviourType::kAthletic;
1078 		} else if (isBehaviourHovered(HeroBehaviourType::kAggressive)) {
1079 			_engine->_actor->heroBehaviour = HeroBehaviourType::kAggressive;
1080 		} else if (isBehaviourHovered(HeroBehaviourType::kDiscrete)) {
1081 			_engine->_actor->heroBehaviour = HeroBehaviourType::kDiscrete;
1082 		}
1083 #endif
1084 
1085 		int heroBehaviour = (int)_engine->_actor->_heroBehaviour;
1086 		if (_engine->_input->toggleActionIfActive(TwinEActionType::UILeft)) {
1087 			heroBehaviour--;
1088 		} else if (_engine->_input->toggleActionIfActive(TwinEActionType::UIRight)) {
1089 			heroBehaviour++;
1090 		}
1091 
1092 		if (heroBehaviour < (int)HeroBehaviourType::kNormal) {
1093 			heroBehaviour = (int)HeroBehaviourType::kDiscrete;
1094 		} else if (heroBehaviour >= (int)HeroBehaviourType::kProtoPack) {
1095 			heroBehaviour = (int)HeroBehaviourType::kNormal;
1096 		}
1097 
1098 		_engine->_actor->_heroBehaviour = (HeroBehaviourType)heroBehaviour;
1099 
1100 		if (tmpHeroBehaviour != _engine->_actor->_heroBehaviour) {
1101 			drawBehaviour(left, top, tmpHeroBehaviour, _engine->_scene->_sceneHero->_angle, true);
1102 			tmpHeroBehaviour = _engine->_actor->_heroBehaviour;
1103 			_engine->_movements->setActorAngleSafe(_engine->_scene->_sceneHero->_angle, _engine->_scene->_sceneHero->_angle - ANGLE_90, ANGLE_17, &_moveMenu);
1104 			const int tmpAnimIdx = _engine->_actor->_heroAnimIdx[(byte)_engine->_actor->_heroBehaviour];
1105 			_engine->_animations->setAnimAtKeyframe(_behaviourAnimState[(byte)_engine->_actor->_heroBehaviour], _engine->_resources->_animData[tmpAnimIdx], *_behaviourEntity, &_behaviourAnimData[(byte)_engine->_actor->_heroBehaviour]);
1106 		}
1107 
1108 		drawBehaviour(left, top, _engine->_actor->_heroBehaviour, -1, true);
1109 
1110 		_engine->_lbaTime++;
1111 	}
1112 
1113 	_engine->_lbaTime = tmpTime;
1114 
1115 	_engine->_actor->setBehaviour(_engine->_actor->_heroBehaviour);
1116 	_engine->_gameState->initEngineProjections();
1117 
1118 	_engine->_scene->_sceneTextBank = tmpTextBank;
1119 	_engine->_text->initSceneTextBank();
1120 }
1121 
drawItem(int32 left,int32 top,int32 item)1122 void Menu::drawItem(int32 left, int32 top, int32 item) {
1123 	const int32 itemWidth = 74;
1124 	const int32 itemHeight = 64;
1125 	const int32 itemPadding = 11;
1126 	const int32 itemWidthHalf = itemWidth / 2;
1127 	const int32 itemHeightHalf = itemHeight / 2;
1128 	const int32 itemX = (item / 4) * (itemWidth + itemPadding) + left + itemWidthHalf + itemPadding - 1;
1129 	const int32 itemY = (item % 4) * (itemHeight + itemPadding) + top + itemHeightHalf + itemPadding - 1;
1130 	const Common::Rect rect(itemX - itemWidthHalf, itemY - itemHeightHalf, itemX + itemWidthHalf, itemY + itemHeightHalf);
1131 	const int32 color = _inventorySelectedItem == item ? _inventorySelectedColor : COLOR_BLACK;
1132 
1133 	_engine->_interface->drawFilledRect(rect, color);
1134 
1135 	if (item < NUM_INVENTORY_ITEMS && _engine->_gameState->hasItem((InventoryItems)item) && (!_engine->_gameState->inventoryDisabled() || item == InventoryItems::kiCloverLeaf)) {
1136 		_itemAngle[item] += ANGLE_2;
1137 		_engine->_interface->setClip(rect);
1138 		_engine->_renderer->renderInventoryItem(itemX, itemY, _engine->_resources->_inventoryTable[item], _itemAngle[item], 15000);
1139 		_engine->_interface->resetClip();
1140 		if (item == InventoryItems::kGasItem) {
1141 			_engine->_text->setFontColor(COLOR_WHITE);
1142 			const Common::String &inventoryNumGas = Common::String::format("%d", _engine->_gameState->_inventoryNumGas);
1143 			_engine->_text->drawText(rect.left + 3, rect.bottom - 32, inventoryNumGas.c_str());
1144 		}
1145 	}
1146 
1147 	drawRectBorders(rect);
1148 }
1149 
drawInventoryItems(int32 left,int32 top)1150 void Menu::drawInventoryItems(int32 left, int32 top) {
1151 	const Common::Rect rect(left, top, left + 605, top + 310);
1152 	_engine->_interface->drawTransparentBox(rect, 4);
1153 	drawRectBorders(rect);
1154 
1155 	for (int32 item = 0; item < NUM_INVENTORY_ITEMS; item++) {
1156 		drawItem(left, top, item);
1157 	}
1158 	_engine->_interface->resetClip();
1159 }
1160 
processInventoryMenu()1161 void Menu::processInventoryMenu() {
1162 	int32 tmpAlphaLight = _engine->_scene->_alphaLight;
1163 	int32 tmpBetaLight = _engine->_scene->_betaLight;
1164 
1165 	_engine->saveFrontBuffer();
1166 
1167 	_engine->_renderer->setLightVector(ANGLE_315, ANGLE_334, ANGLE_0);
1168 
1169 	_inventorySelectedColor = COLOR_68;
1170 
1171 	if (_engine->_gameState->_inventoryNumLeafs > 0) {
1172 		_engine->_gameState->giveItem(InventoryItems::kiCloverLeaf);
1173 		// TODO: shouldn't this get reset? } else {
1174 		//	_engine->_gameState->removeItem(InventoryItems::kiCloverLeaf);
1175 	}
1176 
1177 	const int32 left = _engine->width() / 2 - 303;
1178 	const int32 top = _engine->height() / 2 - 210;
1179 	drawInventoryItems(left, top);
1180 
1181 	_engine->_text->initTextBank(TextBankId::Inventory_Intro_and_Holomap);
1182 
1183 	_engine->_text->setFontCrossColor(COLOR_BRIGHT_BLUE);
1184 	_engine->_text->initDialogueBox();
1185 
1186 	ProgressiveTextState textState = ProgressiveTextState::ContinueRunning;
1187 	bool updateItemText = true;
1188 
1189 	//ScopedCursor scopedCursor(_engine);
1190 	ScopedKeyMap scopedKeyMap(_engine, uiKeyMapId);
1191 	for (;;) {
1192 		FrameMarker frame(_engine, 66);
1193 		_engine->readKeys();
1194 		int32 prevSelectedItem = _inventorySelectedItem;
1195 
1196 		if (_engine->_input->toggleAbortAction() || _engine->shouldQuit()) {
1197 			break;
1198 		}
1199 
1200 		const bool cursorDown = _engine->_input->toggleActionIfActive(TwinEActionType::UIDown);
1201 		const bool cursorUp = _engine->_input->toggleActionIfActive(TwinEActionType::UIUp);
1202 		const bool cursorLeft = _engine->_input->toggleActionIfActive(TwinEActionType::UILeft);
1203 		const bool cursorRight = _engine->_input->toggleActionIfActive(TwinEActionType::UIRight);
1204 
1205 		if (cursorDown) {
1206 			_inventorySelectedItem++;
1207 			if (_inventorySelectedItem >= NUM_INVENTORY_ITEMS) {
1208 				_inventorySelectedItem = 0;
1209 			}
1210 			drawItem(left, top, prevSelectedItem);
1211 			updateItemText = true;
1212 		} else if (cursorUp) {
1213 			_inventorySelectedItem--;
1214 			if (_inventorySelectedItem < 0) {
1215 				_inventorySelectedItem = NUM_INVENTORY_ITEMS - 1;
1216 			}
1217 			drawItem(left, top, prevSelectedItem);
1218 			updateItemText = true;
1219 		} else if (cursorLeft) {
1220 			_inventorySelectedItem -= 4;
1221 			if (_inventorySelectedItem < 0) {
1222 				_inventorySelectedItem += NUM_INVENTORY_ITEMS;
1223 			}
1224 			drawItem(left, top, prevSelectedItem);
1225 			updateItemText = true;
1226 		} else if (cursorRight) {
1227 			_inventorySelectedItem += 4;
1228 			if (_inventorySelectedItem >= NUM_INVENTORY_ITEMS) {
1229 				_inventorySelectedItem -= NUM_INVENTORY_ITEMS;
1230 			}
1231 			drawItem(left, top, prevSelectedItem);
1232 			updateItemText = true;
1233 		}
1234 
1235 		if (updateItemText) {
1236 			_engine->_text->initInventoryDialogueBox();
1237 			if (_inventorySelectedItem < NUM_INVENTORY_ITEMS && _engine->_gameState->hasItem((InventoryItems)_inventorySelectedItem) && !_engine->_gameState->inventoryDisabled()) {
1238 				_engine->_text->initInventoryText((InventoryItems)_inventorySelectedItem);
1239 			} else {
1240 				_engine->_text->initInventoryText(InventoryItems::MaxInventoryItems);
1241 			}
1242 			textState = ProgressiveTextState::ContinueRunning;
1243 			updateItemText = false;
1244 		}
1245 
1246 		if (textState == ProgressiveTextState::ContinueRunning) {
1247 			textState = _engine->_text->updateProgressiveText();
1248 		} else {
1249 			_engine->_text->fadeInRemainingChars();
1250 		}
1251 
1252 		if (_engine->_input->toggleActionIfActive(TwinEActionType::UINextPage)) {
1253 			// restart the item description to appear from the beginning
1254 			if (textState == ProgressiveTextState::End) {
1255 				updateItemText = true;
1256 			}
1257 			if (textState == ProgressiveTextState::NextPage) {
1258 				_engine->_text->initInventoryDialogueBox();
1259 				textState = ProgressiveTextState::ContinueRunning;
1260 			}
1261 		}
1262 
1263 		drawItem(left, top, _inventorySelectedItem);
1264 
1265 		if (_inventorySelectedItem < NUM_INVENTORY_ITEMS && _engine->_input->toggleActionIfActive(TwinEActionType::UIEnter) && _engine->_gameState->hasItem((InventoryItems)_inventorySelectedItem) && !_engine->_gameState->inventoryDisabled()) {
1266 			_engine->_loopInventoryItem = _inventorySelectedItem;
1267 			_inventorySelectedColor = COLOR_91;
1268 			drawItem(left, top, _inventorySelectedItem);
1269 			break;
1270 		}
1271 	}
1272 
1273 	_engine->_text->_hasValidTextHandle = false;
1274 
1275 	_engine->_scene->_alphaLight = tmpAlphaLight;
1276 	_engine->_scene->_betaLight = tmpBetaLight;
1277 
1278 	_engine->_gameState->initEngineProjections();
1279 
1280 	_engine->_text->initSceneTextBank();
1281 }
1282 
1283 } // namespace TwinE
1284