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 ¤tAnimData = _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