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 "mohawk/myst.h"
24 #include "mohawk/myst_areas.h"
25 #include "mohawk/myst_card.h"
26 #include "mohawk/myst_graphics.h"
27 #include "mohawk/myst_state.h"
28 #include "mohawk/cursors.h"
29 #include "mohawk/sound.h"
30 #include "mohawk/video.h"
31 #include "mohawk/myst_stacks/menu.h"
32 
33 #include "common/translation.h"
34 #include "graphics/cursorman.h"
35 #include "gui/message.h"
36 
37 namespace Mohawk {
38 namespace MystStacks {
39 
Menu(MohawkEngine_Myst * vm)40 Menu::Menu(MohawkEngine_Myst *vm) :
41 		MystScriptParser(vm, kMenuStack),
42 		_inGame(false),
43 		_canSave(false),
44 		_wasCursorVisible(true),
45 		_introMoviesRunning(false) {
46 
47 	for (uint i = 0; i < ARRAYSIZE(_menuItemHovered); i++) {
48 		_menuItemHovered[i] = false;
49 	}
50 
51 	setupOpcodes();
52 }
53 
~Menu()54 Menu::~Menu() {
55 }
56 
setupOpcodes()57 void Menu::setupOpcodes() {
58 	// "Stack-Specific" Opcodes
59 	REGISTER_OPCODE(150, Menu, o_menuItemEnter);
60 	REGISTER_OPCODE(151, Menu, o_menuItemLeave);
61 	REGISTER_OPCODE(152, Menu, o_menuResume);
62 	REGISTER_OPCODE(153, Menu, o_menuLoad);
63 	REGISTER_OPCODE(154, Menu, o_menuSave);
64 	REGISTER_OPCODE(155, Menu, o_menuNew);
65 	REGISTER_OPCODE(156, Menu, o_menuOptions);
66 	REGISTER_OPCODE(157, Menu, o_menuQuit);
67 
68 	// "Init" Opcodes
69 	REGISTER_OPCODE(200, Menu, o_playIntroMovies);
70 	REGISTER_OPCODE(201, Menu, o_menuInit);
71 
72 	// "Exit" Opcodes
73 	REGISTER_OPCODE(300, Menu, NOP);
74 	REGISTER_OPCODE(301, Menu, o_menuExit);
75 }
76 
disablePersistentScripts()77 void Menu::disablePersistentScripts() {
78 	_introMoviesRunning = false;
79 }
80 
runPersistentScripts()81 void Menu::runPersistentScripts() {
82 	if (_introMoviesRunning)
83 		introMovies_run();
84 }
85 
getVar(uint16 var)86 uint16 Menu::getVar(uint16 var) {
87 	switch (var) {
88 	case 1000: // New game
89 	case 1001: // Load
90 	case 1004: // Quit
91 	case 1005: // Options
92 		return _menuItemHovered[var - 1000] ? 1 : 0;
93 	case 1002: // Save
94 		if (_canSave) {
95 			return _menuItemHovered[var - 1000] ? 1 : 0;
96 		} else {
97 			return 2;
98 		}
99 	case 1003: // Resume
100 		if (_inGame) {
101 			return _menuItemHovered[var - 1000] ? 1 : 0;
102 		} else {
103 			return 2;
104 		}
105 	default:
106 		return MystScriptParser::getVar(var);
107 	}
108 }
109 
o_menuInit(uint16 var,const ArgumentsArray & args)110 void Menu::o_menuInit(uint16 var, const ArgumentsArray &args) {
111 	_pauseToken = _vm->pauseEngine();
112 
113 	if (_inGame) {
114 		_wasCursorVisible = CursorMan.isVisible();
115 	}
116 
117 	if (!_wasCursorVisible) {
118 		CursorMan.showMouse(true);
119 	}
120 
121 	struct MenuButton {
122 		uint16 highlightedIndex;
123 		uint16 disabledIndex;
124 		Graphics::TextAlign align;
125 	};
126 
127 	static const MenuButton buttons[] = {
128 		{ 1, 0, Graphics::kTextAlignRight },
129 		{ 1, 0, Graphics::kTextAlignRight },
130 		{ 1, 2, Graphics::kTextAlignRight },
131 		{ 1, 2, Graphics::kTextAlignRight },
132 		{ 1, 0, Graphics::kTextAlignRight },
133 		{ 1, 0, Graphics::kTextAlignLeft  }
134 	};
135 
136 	const char **buttonCaptions = getButtonCaptions();
137 
138 	for (uint i = 0; i < ARRAYSIZE(buttons); i++) {
139 		MystAreaImageSwitch *image  = _vm->getCard()->getResource<MystAreaImageSwitch>(2 * i + 0);
140 		MystAreaHover       *hover  = _vm->getCard()->getResource<MystAreaHover>      (2 * i + 1);
141 
142 		Common::U32String str = Common::convertUtf8ToUtf32(buttonCaptions[i]);
143 		drawButtonImages(str, image, buttons[i].align, buttons[i].highlightedIndex, buttons[i].disabledIndex);
144 		hover->setRect(image->getRect());
145 	}
146 }
147 
getButtonCaptions() const148 const char **Menu::getButtonCaptions() const {
149 	static const char *buttonCaptionsEnglish[] = {
150 		"NEW GAME",
151 		"LOAD GAME",
152 		"SAVE GAME",
153 		"RESUME",
154 		"QUIT",
155 		"OPTIONS"
156 	};
157 
158 	static const char *buttonCaptionsFrench[] = {
159 		"NOUVEAU",
160 		"CHARGER",
161 		"SAUVER",
162 		"REPRENDRE",
163 		"QUITTER",
164 		"OPTIONS"
165 	};
166 
167 	static const char *buttonCaptionsGerman[] = {
168 		"NEUES SPIEL",
169 		"SPIEL LADEN",
170 		"SPIEL SPEICHERN",
171 		"FORTSETZEN",
172 		"BEENDEN",
173 		"OPTIONEN"
174 	};
175 
176 	static const char *buttonCaptionsSpanish[] = {
177 		"JUEGO NUEVO",
178 		"CARGAR JUEGO",
179 		"GUARDAR JUEGO",
180 		"CONTINUAR",
181 		"SALIR",
182 		"OPCIONES"
183 	};
184 
185 	static const char *buttonCaptionsPolish[] = {
186 		"NOWA GRA",
187 		"ZAŁADUJ GRĘ",
188 		"ZAPISZ GRĘ",
189 		"POWRÓT",
190 		"WYJŚCIE",
191 		"OPCJE"
192 	};
193 
194 	switch (_vm->getLanguage()) {
195 		case Common::FR_FRA:
196 			return buttonCaptionsFrench;
197 		case Common::DE_DEU:
198 			return buttonCaptionsGerman;
199 		case Common::ES_ESP:
200 			return buttonCaptionsSpanish;
201 		case Common::PL_POL:
202 			return buttonCaptionsPolish;
203 		case Common::EN_ANY:
204 		default:
205 			return buttonCaptionsEnglish;
206 	}
207 }
208 
drawButtonImages(const Common::U32String & text,MystAreaImageSwitch * area,Graphics::TextAlign align,uint16 highlightedIndex,uint16 disabledIndex) const209 void Menu::drawButtonImages(const Common::U32String &text, MystAreaImageSwitch *area, Graphics::TextAlign align, uint16 highlightedIndex, uint16 disabledIndex) const {
210 	Common::Rect backgroundRect = area->getRect();
211 	Common::Rect textBoundingBox = _vm->_gfx->getTextBoundingBox(text, backgroundRect, align);
212 
213 	// Restrict the rectangle to the portion were the text will be drawn
214 	if (align == Graphics::kTextAlignLeft) {
215 		backgroundRect.right = textBoundingBox.right;
216 	} else if (align == Graphics::kTextAlignRight) {
217 		backgroundRect.left = textBoundingBox.left;
218 	} else {
219 		error("Unexpected align: %d", align);
220 	}
221 
222 	// Update the area with the new background rect
223 	area->setRect(backgroundRect);
224 
225 	MystAreaImageSwitch::SubImage idle = area->getSubImage(0);
226 	area->setSubImageRect(0, Common::Rect(backgroundRect.left, idle.rect.top, backgroundRect.right, idle.rect.bottom));
227 
228 	// Align the text to the top of the destination rectangles
229 	int16 deltaY;
230 	if (_vm->getLanguage() == Common::PL_POL) {
231 		deltaY = -2;
232 	} else {
233 		deltaY = backgroundRect.top - textBoundingBox.top;
234 	}
235 
236 	if (highlightedIndex) {
237 		replaceButtonSubImageWithText(text, align, area, highlightedIndex, backgroundRect, deltaY, 215, 216, 219);
238 	}
239 
240 	if (disabledIndex) {
241 		replaceButtonSubImageWithText(text, align, area, disabledIndex, backgroundRect, deltaY, 136, 140, 145);
242 	}
243 
244 	uint16 cardBackground = _vm->getCard()->getBackgroundImageId();
245 	_vm->_gfx->drawText(cardBackground, text, backgroundRect, 181, 184, 189, align, deltaY);
246 }
247 
replaceButtonSubImageWithText(const Common::U32String & text,const Graphics::TextAlign & align,MystAreaImageSwitch * area,uint16 subimageIndex,const Common::Rect & backgroundRect,int16 deltaY,uint8 r,uint8 g,uint8 b) const248 void Menu::replaceButtonSubImageWithText(const Common::U32String &text, const Graphics::TextAlign &align, MystAreaImageSwitch *area,
249 										 uint16 subimageIndex, const Common::Rect &backgroundRect, int16 deltaY,
250 										 uint8 r, uint8 g, uint8 b) const {
251 	uint16 cardBackground = _vm->getCard()->getBackgroundImageId();
252 
253 	MystAreaImageSwitch::SubImage highlighted = area->getSubImage(subimageIndex);
254 	Common::Rect subImageRect(0, 0, backgroundRect.width(), backgroundRect.height());
255 
256 	// Create an image exactly the size of the rendered text with the backdrop as a background
257 	_vm->_gfx->replaceImageWithRect(highlighted.wdib, cardBackground, backgroundRect);
258 	area->setSubImageRect(subimageIndex, subImageRect);
259 
260 	// Draw the text in the subimage
261 	_vm->_gfx->drawText(highlighted.wdib, text, subImageRect, r, g, b, align, deltaY);
262 }
263 
o_menuItemEnter(uint16 var,const ArgumentsArray & args)264 void Menu::o_menuItemEnter(uint16 var, const ArgumentsArray &args) {
265 	_menuItemHovered[var - 1000] = true;
266 	_vm->getCard()->redrawArea(var);
267 }
268 
o_menuItemLeave(uint16 var,const ArgumentsArray & args)269 void Menu::o_menuItemLeave(uint16 var, const ArgumentsArray &args) {
270 	_menuItemHovered[var - 1000] = false;
271 	_vm->getCard()->redrawArea(var);
272 }
273 
o_menuResume(uint16 var,const ArgumentsArray & args)274 void Menu::o_menuResume(uint16 var, const ArgumentsArray &args) {
275 	if (!_inGame) {
276 		return;
277 	}
278 
279 	_vm->resumeFromMainMenu();
280 }
281 
o_menuLoad(uint16 var,const ArgumentsArray & args)282 void Menu::o_menuLoad(uint16 var, const ArgumentsArray &args) {
283 	if (!showConfirmationDialog(_("Are you sure you want to load a saved game? All unsaved progress will be lost."),
284 	                            _("Load game"), _("Cancel"))) {
285 		return;
286 	}
287 
288 	_vm->loadGameDialog();
289 }
290 
o_menuSave(uint16 var,const ArgumentsArray & args)291 void Menu::o_menuSave(uint16 var, const ArgumentsArray &args) {
292 	if (!_canSave) {
293 		return;
294 	}
295 
296 	_vm->saveGameDialog();
297 }
298 
o_menuNew(uint16 var,const ArgumentsArray & args)299 void Menu::o_menuNew(uint16 var, const ArgumentsArray &args) {
300 	if (!showConfirmationDialog(_("Are you sure you want to start a new game? All unsaved progress will be lost."),
301 	                            _("New game"), _("Cancel"))) {
302 		return;
303 	}
304 
305 	_vm->_gameState->reset();
306 	_vm->setTotalPlayTime(0);
307 	_vm->setMainCursor(kDefaultMystCursor);
308 	_vm->changeToStack(kIntroStack, 1, 0, 0);
309 }
310 
o_menuOptions(uint16 var,const ArgumentsArray & args)311 void Menu::o_menuOptions(uint16 var, const ArgumentsArray &args) {
312 	resetButtons();
313 
314 	_vm->runOptionsDialog();
315 }
316 
o_menuQuit(uint16 var,const ArgumentsArray & args)317 void Menu::o_menuQuit(uint16 var, const ArgumentsArray &args) {
318 	if (!showConfirmationDialog(_("Are you sure you want to quit? All unsaved progress will be lost."), _("Quit"),
319 	                            _("Cancel"))) {
320 		return;
321 	}
322 
323 	_vm->changeToStack(kCreditsStack, 10000, 0, 0);
324 }
325 
o_menuExit(uint16 var,const ArgumentsArray & args)326 void Menu::o_menuExit(uint16 var, const ArgumentsArray &args) {
327 	if (_inGame) {
328 		_vm->_gfx->restoreStateForMainMenu();
329 	}
330 
331 	CursorMan.showMouse(_wasCursorVisible);
332 
333 	_pauseToken.clear();
334 }
335 
o_playIntroMovies(uint16 var,const ArgumentsArray & args)336 void Menu::o_playIntroMovies(uint16 var, const ArgumentsArray &args) {
337 	_introMoviesRunning = true;
338 	_introStep = 0;
339 }
340 
introMovies_run()341 void Menu::introMovies_run() {
342 	// Play Intro Movies
343 	// This is all quite messy...
344 
345 	VideoEntryPtr video;
346 
347 	switch (_introStep) {
348 		case 0:
349 			_introStep = 1;
350 			video = _vm->playMovieFullscreen("broder", kIntroStack);
351 			break;
352 		case 1:
353 			if (!_vm->_video->isVideoPlaying())
354 				_introStep = 2;
355 			break;
356 		case 2:
357 			_introStep = 3;
358 			video = _vm->playMovieFullscreen("cyanlogo", kIntroStack);
359 			break;
360 		case 3:
361 			if (!_vm->_video->isVideoPlaying())
362 				_introStep = 4;
363 			break;
364 		default:
365 			_vm->changeToCard(1000, kTransitionCopy);
366 	}
367 }
368 
showConfirmationDialog(const Common::U32String & message,const Common::U32String & confirmButton,const Common::U32String & cancelButton)369 bool Menu::showConfirmationDialog(const Common::U32String &message, const Common::U32String &confirmButton, const Common::U32String &cancelButton) {
370 	if (!_inGame) {
371 		return true;
372 	}
373 
374 	resetButtons();
375 
376 	GUI::MessageDialog dialog(message, confirmButton, cancelButton);
377 
378 	return dialog.runModal() == GUI::kMessageOK;
379 }
380 
resetButtons()381 void Menu::resetButtons() {
382 	for (uint i = 0; i < ARRAYSIZE(_menuItemHovered); i++) {
383 		_menuItemHovered[i] = false;
384 		_vm->getCard()->redrawArea(1000 + i);
385 	}
386 
387 	_vm->doFrame();
388 }
389 
390 
391 } // End of namespace MystStacks
392 } // End of namespace Mohawk
393