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  * Copyright 2020 Google
22  *
23  */
24 #include "hadesch/hadesch.h"
25 #include "hadesch/video.h"
26 #include "hadesch/ambient.h"
27 #include "common/translation.h"
28 
29 namespace Hadesch {
30 static const char *kHighlightImage = "r6010ol0";
31 static const char *kMaterialsImage = "r6010ok0";
32 static const char *kMaterialsMoveImage = "r6020ba0";
33 static const char *kMinotaurImage = "r6040ba0";
34 static const char *kDigits = "0123456789";
35 static const int numSquares = 25;
36 static const char *minotaurStates[] = {
37 	"",
38 	"",
39 	"",
40 	"inactive",
41 	"encourage",
42 	"trapped",
43 	"escaped",
44 	"critical",
45 	"level",
46 	"idle"
47 };
48 
49 enum DaedalusDialogState {
50 	kMinotaur0 = 0,
51 	kMinotaur1 = 1,
52 	kMinotaur2 = 2,
53 	kMinotaurInactive = 3,
54 	kMinotaurEncourage = 4,
55 	kMinotaurTrapped = 5,
56 	kMinotaurEscaped = 6,
57 	kMinotaurCritical = 7,
58 	kMinotaurLevel = 8,
59 	kMinotaurIdle = 9
60 };
61 
62 enum Strength {
63 	kStrengthStraw = 1,
64 	kStrengthWood = 2,
65 	kStrengthBricks = 3,
66 	kStrengthStone = 4
67 };
68 
69 enum Position {
70 	kDown = 0,
71 	kLeft = 1,
72 	kUp = 2,
73 	kRight = 3
74 };
75 
76 static const char *dirnames[] = {
77 	"down",
78 	"left",
79 	"up",
80 	"right"
81 };
82 
83 enum {
84 	kRerenderLabyrinth = 1017001
85 };
86 
87 static const char *daedalusSoundSMK[] = {
88 	"R6100nA0",
89 	"R6100wA0",
90 	"R6100nB0",
91 	"R6100wC0",
92 	"R6100nD0",
93 };
94 
95 static const TranscribedSound daedalusSoundsAIF[] = {
96 	{"R6100nH0", _s("Help us to move the walls so that they are strong enough to stop the minotaur")},
97 	{"R6100nL0", _s("Click on a square to rotate the walls")},
98 	{"R6100nG0", _s("Some walls are already locked in place and won't rotate")},
99 	{"R6100nK0", _s("If you need help, refer to workman's equations")},
100 	{"R6170nA0", _s("Careful, my friend. Some of the walls are not strong enough")},
101 	{"R6150nA0", _s("You're a brave bullfighter, my friend")},
102 	{"R6150nB0", _s("Keep it up. It looks like he's tiring")},
103 	{"R6150nC0", _s("That's taking the bull by the horns")},
104 	{"R6150nD0", _s("Don't give up. You can't beat him")},
105 	{"R6180nA0", _s("You have beaten the Minotaur. You have the makings of a hero")},
106 	{"R6180nC0", _s("You have beaten the beast at last")},
107 	{"R6180nD0", _s("You have done it. The people of Crete are once again safe")},
108 	{"R6170nC0", _s("Let's try again")},
109 	{"R6170nD0", _s("Warn the people of Crete: the Minotaur has escaped. Workers, keep the Minotaur back in the labyrinth")},
110 	{"R6170nE0", _s("I believe you and the Minotaur have not seen the last of one another")},
111 	{"R6170nF0", _s("Ah that was a nobble effort, my friend")},
112 	{"R6160nA0", _s("The Minotaur has broken though a critical wall. Workers, calm on the beast")},
113 	{"R6090eA0", _s("Eh. Hm")},
114 	{"R6190nA0", _s("Ok. Onto level two")},
115 	{"R6190nB0", _s("Onto level three")},
116 };
117 
118 struct Wall {
119 	int _id;
120 	bool _isCritical;
121 	int _inTransit;
122 	Strength _strength;
123 	Position _position;
124 
WallHadesch::Wall125 	Wall() {
126 		_id = -1;
127 		_isCritical = false;
128 		_strength = kStrengthStraw;
129 		_position = kDown;
130 		_inTransit = 0;
131 	}
132 };
133 
134 struct Cell {
135 	Common::Array<Wall> _movableWalls;
136 	Common::Array<Wall> _immovableWalls;
137 	bool _isRotatable;
138 
CellHadesch::Cell139 	Cell() {
140 		_isRotatable = false;
141 	}
142 };
143 
144 struct Labyrinth {
145 	Cell _cells[25];
146 };
147 
148 class MinotaurHandler : public Handler {
149 public:
MinotaurHandler()150 	MinotaurHandler() {
151 		_highlight = -1;
152 
153 		_dialogState = kMinotaur0;
154 		_minotaurX = 1;
155 		_minotaurY = 2;
156 		_minotaurTileId = 7;
157 		_soundCounter = 0;
158 		_soundMax = 5;
159 
160 		_lastChargeSound = -1;
161 		_lastTrappedSound = -1;
162 		_lastEscapedSound = -1;
163 		_levelId = 0;
164 
165 		// consts
166 		xVector = Common::Point(-55, -33);
167 		yVector = Common::Point(+55, -33);
168 	}
169 
handleClick(const Common::String & name)170 	void handleClick(const Common::String &name) override {
171 		if (name.firstChar() >= '0' && name.firstChar() <= '9') {
172 			rotate(name.asUint64());
173 			renderLabyrinth();
174 			return;
175 		}
176 		/* TODO: MNSH: Daedalus */
177 	}
178 
handleEvent(int eventId)179 	void handleEvent(int eventId) override {
180 		Common::SharedPtr<VideoRoom> room = g_vm->getVideoRoom();
181 		switch (eventId) {
182 		case kRerenderLabyrinth:
183 			renderLabyrinth();
184 			break;
185 		case 17953:
186 			g_vm->addTimer(17954, 300);
187 			break;
188 		case 17954:
189 			treatDialogState();
190 			break;
191 		}
192 	}
193 
handleMouseOver(const Common::String & name)194 	void handleMouseOver(const Common::String &name) override {
195 		if (name.firstChar() >= '0' && name.firstChar() <= '9')
196 			_highlight = name.asUint64();
197 		else
198 			_highlight = -1;
199 		renderLabyrinth();
200 	}
handleMouseOut(const Common::String & name)201 	void handleMouseOut(const Common::String &name) override {
202 		_highlight = -1;
203 		renderLabyrinth();
204 	}
205 
prepareRoom()206 	void prepareRoom() override {
207 		Common::SharedPtr<VideoRoom> room = g_vm->getVideoRoom();
208 		room->loadHotZones("minotaur.hot", true);
209 		// TODO: load other puzzles
210 		loadPuzzle("3x3j");
211 		room->addStaticLayer("r6010pA0", 10000);
212 		room->addStaticLayer("r6010tA0", 6400);
213 		room->addStaticLayer("r6010oA0", 5500);
214 		room->addStaticLayer("r6010oB0", 4000);
215 		renderLabyrinth();
216 		g_vm->getHeroBelt()->setColour(HeroBelt::kCool);
217 		setDialogState(kMinotaur1);
218 	}
219 
handleCheat(const Common::String & cheat)220 	bool handleCheat(const Common::String &cheat) override {
221 		for (unsigned i = 0; i < ARRAYSIZE(minotaurStates); i++)
222 			if (minotaurStates[i][0] && minotaurStates[i] == cheat) {
223 				setDialogState(DaedalusDialogState(i));
224 				return true;
225 			}
226 		return false;
227 	}
228 
229 private:
setDialogState(DaedalusDialogState state)230 	void setDialogState(DaedalusDialogState state) {
231 		if (_dialogState) {
232 			// TODO
233 		} else {
234 			_dialogState = state;
235 		}
236 	}
237 
playDaedalusSound(int index)238 	void playDaedalusSound(int index) {
239 		// TODO: balance
240 		_currentSound = index;
241 		Common::SharedPtr<VideoRoom> room = g_vm->getVideoRoom();
242 		if (index < ARRAYSIZE(daedalusSoundSMK))
243 			room->playVideo(daedalusSoundSMK[index], 17953);
244 		else
245 			room->playSpeech(daedalusSoundsAIF[index-ARRAYSIZE(daedalusSoundSMK)], 17953);
246 	}
247 
playDaedalusSoundWrap()248 	void playDaedalusSoundWrap() {
249 		Persistent *persistent = g_vm->getPersistent();
250 		int index = 0;
251 		switch (_dialogState) {
252 		case kMinotaur0:
253 			treatDialogState();
254 			return;
255 		case kMinotaur1:
256 			index = _soundCounter;
257 			break;
258 		case kMinotaur2:
259 			index = _soundCounter + 5;
260 			break;
261 		case kMinotaurInactive:
262 			index = 9;
263 			break;
264 		case kMinotaurEncourage:
265 			index = randomExcept(10, 13, _lastChargeSound);
266 			_lastChargeSound = index;
267 			break;
268 		case kMinotaurTrapped:
269 			if (persistent->_quest == kCreteQuest)
270 				index = 14;
271 			else
272 				index = randomExcept(14, 16, _lastTrappedSound);
273 			_lastTrappedSound = index;
274 			break;
275 		case kMinotaurEscaped:
276 			index = randomExcept(17, 20, _lastEscapedSound);
277 			_lastEscapedSound = index;
278 			break;
279 		case kMinotaurCritical:
280 			index = 21;
281 			break;
282 		case kMinotaurLevel:
283 			index = 23 + _levelId / 15;
284 			break;
285 		case kMinotaurIdle:
286 			index = 22;
287 			break;
288 		}
289 
290 		playDaedalusSound(index);
291 	}
292 
randomExcept(int from,int to,int except)293 	int randomExcept(int from, int to, int except) {
294 		Common::RandomSource &r = g_vm->getRnd();
295 		if (except < from || except > to)
296 			return r.getRandomNumberRng(from, to);
297 		int x = r.getRandomNumberRng(from, to - 1);
298 		if (x >= except)
299 			x++;
300 		return x;
301 	}
302 
treatDialogState()303 	void treatDialogState() {
304 		Common::SharedPtr<VideoRoom> room = g_vm->getVideoRoom();
305 		switch(_dialogState) {
306 		case kMinotaur1:
307 			if (_soundCounter < _soundMax) {
308 				playDaedalusSoundWrap();
309 				_soundCounter++;
310 				return;
311 			}
312 			setDialogState(kMinotaur2);
313 			return;
314 		case kMinotaur2:
315 			if (_soundCounter < _soundMax) {
316 				playDaedalusSoundWrap();
317 				_soundCounter++;
318 				return;
319 			}
320 			room->enableMouse();
321 			setDialogState(kMinotaur0);
322 			return;
323 		default:
324 			// TODO: implement this;
325 			return;
326 		}
327 	}
328 
renderLabyrinth()329 	void renderLabyrinth() {
330 		Common::SharedPtr<VideoRoom> room = g_vm->getVideoRoom();
331 		if (_highlight >= 0)
332 			room->selectFrame(kHighlightImage, 9990, 0,
333 					  getTileBase(_highlight)
334 					  - Common::Point(34, 52));
335 		else
336 			room->stopAnim(kHighlightImage);
337 
338 		for (int cell = 0; cell < numSquares; cell++) {
339 			for (int j = 0; j < ARRAYSIZE(dirnames); j++) {
340 				room->stopAnim(
341 					LayerId(kMaterialsImage, cell, Common::String(dirnames[j]) + "outer"));
342 				room->stopAnim(
343 					LayerId(kMaterialsImage, cell, Common::String(dirnames[j]) + "inner"));
344 				room->stopAnim(
345 					LayerId(kMaterialsMoveImage, cell, "to-" + Common::String(dirnames[j])));
346 			}
347 
348 			for (int j = 0; j < (int) _current._cells[cell]._movableWalls.size(); j++) {
349 				renderWall(cell, _current._cells[cell]._movableWalls[j], false);
350 			}
351 
352 			// Both original engine and us we're not able to handle 3
353 			// walls in the same place. The only way to avoid this from every hapenning
354 			// is to never put an immovable wall between 2 cells with movable walls.
355 			// Hence if we have any movable walls then we can make immovable walls outer
356 			// and they will not conflict with existing labyrinths.
357 			// If we have no movable walls we can easily put all immovable walls as inner
358 
359 			bool immovableAreOuter = !_current._cells[cell]._movableWalls.empty();
360 
361 			for (int j = 0; j < (int) _current._cells[cell]._immovableWalls.size(); j++) {
362 				renderWall(cell, _current._cells[cell]._immovableWalls[j], immovableAreOuter);
363 			}
364 		}
365 
366 		// TODO: which frame?
367 		room->selectFrame(kMinotaurImage, getMinotaurZ(), 30,
368 				  getTileBase(_minotaurX, _minotaurY) - Common::Point(/*114, 117*/30+82, 33*2+52));
369 	}
370 
getMinotaurZ()371 	int getMinotaurZ() {
372 		if (_minotaurX >= 5) {
373 			return 6500;
374 		}
375 
376 		if (_minotaurX < 0) {
377 			return 4500;
378 		}
379 
380 		if (_minotaurY >= 5) {
381 			return 5960;
382 		}
383 
384 		if (_minotaurY < 0) {
385 			return 4500;
386 		}
387 
388 		return 5000 + 150 * (_minotaurX + _minotaurY) + 60;
389 	}
390 
renderWall(int cell,Wall & wall,bool outer)391 	void renderWall(int cell, Wall &wall, bool outer) {
392 		Common::Point delta;
393 		if (wall._inTransit) {
394 			wall._inTransit--;
395  			g_vm->getVideoRoom()->selectFrame(
396 				LayerId(kMaterialsMoveImage, cell, Common::String("to-") + dirnames[wall._position]),
397 				getWallZ(cell, wall._position, outer),
398 				(wall._strength - kStrengthStraw) * 4 + (wall._position + 1) % 4,
399 				getTileBase(cell) + Common::Point(-40, -88));
400 			g_vm->addTimer(kRerenderLabyrinth, kDefaultSpeed);
401 			return;
402 		}
403 
404 		switch (wall._position) {
405 		case kUp:
406 			delta = xVector + xVector + yVector + Common::Point(-8, -3) +
407 				(outer ? Common::Point(0, 0) : Common::Point(+7, +5));
408 			break;
409 		case kDown:
410 			delta = xVector + yVector + Common::Point(-8, -3) +
411 				(outer ? Common::Point(+7, +5) : Common::Point(0, 0));
412 			break;
413 		case kLeft:
414 			delta = xVector + Common::Point(0, -33)
415 				+ (outer ? Common::Point(-7, +5) : Common::Point(0, 0));
416 			break;
417 		case kRight:
418 			delta = xVector + yVector + Common::Point(0, -33)
419 				+ (outer ? Common::Point(0, 0) : Common::Point(-7, +5));
420 			break;
421 		}
422 
423 		Common::Point pos = getTileBase(cell) + delta;
424 
425 		LayerId layer(kMaterialsImage, cell,
426 			      Common::String(dirnames[wall._position]) +
427 			      (outer ? "outer" : "inner"));
428 		int frame = (wall._strength - kStrengthStraw) * 2 + (wall._position % 2);
429 		g_vm->getVideoRoom()->selectFrame(layer, getWallZ(cell, wall._position, outer), frame, pos);
430 	}
431 
getWallZ(int cell,Position pos,bool outer)432 	int getWallZ(int cell, Position pos, bool outer) {
433 		int zValue = 150 * (cell / 5 + cell % 5) + 5000;
434 		switch (pos) {
435 		case kUp:
436 			zValue += outer? 110 : 100;
437 			break;
438 		case kDown:
439 			zValue += outer ? -10 : 0;
440 			break;
441 		case kLeft:
442 			zValue += outer ? 40 : 50;
443 			break;
444 		case kRight:
445 			zValue += outer ? 80 : 70;
446 			break;
447 		}
448 		return zValue;
449 	}
450 
getTileBase(int x,int y)451 	Common::Point getTileBase(int x, int y) {
452 		return Common::Point(320, 456) + xVector * x + yVector * y;
453 	}
454 
getTileBase(int id)455 	Common::Point getTileBase(int id) {
456 		return getTileBase(id / 5, id % 5);
457 	}
458 
rotate(int cell)459 	void rotate(int cell) {
460 		for (int j = 0;
461 		     j < (int) _current._cells[cell]._movableWalls.size();
462 		     j++) {
463 			_current._cells[cell]._movableWalls[j]._position
464 				= (Position) ((_current._cells[cell]._movableWalls[j]._position + 1) % 4);
465 			_current._cells[cell]._movableWalls[j]._inTransit = 1;
466 		}
467 	}
468 
loadPuzzle(const Common::String & name)469 	void loadPuzzle(const Common::String &name) {
470 		Common::SharedPtr<VideoRoom> room = g_vm->getVideoRoom();
471 		Common::SharedPtr<Common::SeekableReadStream> gameStream(room->openFile(name + ".mcf"));
472 		Common::SharedPtr<Common::SeekableReadStream> solStream(room->openFile(name + ".sol"));
473 		Common::SharedPtr<Common::SeekableReadStream> cwStream(room->openFile(name + ".cw"));
474 		readLabStream(_current, gameStream);
475 		readLabStream(_solution, solStream);
476 		for (int cell = 0; cell < numSquares; cell++) {
477 			room->setHotzoneEnabled(Common::String::format("%d", cell),
478 						!_current._cells[cell]._movableWalls.empty());
479 		}
480 	}
481 
readLabStream(Labyrinth & lab,Common::SharedPtr<Common::SeekableReadStream> stream)482 	static void readLabStream(Labyrinth &lab, Common::SharedPtr<Common::SeekableReadStream> stream) {
483 		stream->readLine(); // Level number
484 		int gridSize = stream->readLine().asUint64();
485 		stream->readLine(); // ?
486 		stream->readLine(); // ?
487 		int numLines = stream->readLine().asUint64();
488 		// Extra walls
489 		if (gridSize == 3 || gridSize == 4) {
490 			Wall w;
491 			w._isCritical = false;
492 			w._id = -1;
493 			w._strength = kStrengthStone;
494 			w._position = kRight;
495 			if (gridSize == 3)
496 				lab._cells[4]._immovableWalls.push_back(w);
497 			lab._cells[24]._immovableWalls.push_back(w);
498 		}
499 		for (int i = 0; i < numLines; i++) {
500 			Common::String line = stream->readLine();
501 			size_t cur = 0;
502 			int rawcellid = line.asUint64();
503 			int transformedcellid =
504 				5 * (rawcellid / gridSize) +
505 				(rawcellid % gridSize) + (5 - gridSize)
506 				+ 5 * ((5 - gridSize) / 2);
507 			cur = line.findFirstNotOf(kDigits, cur);
508 			cur = line.findFirstOf(kDigits, cur);
509 			int numWalls = line.substr(cur).asUint64();
510 			cur = line.findFirstNotOf(kDigits, cur);
511 			cur = line.findFirstOf(kDigits, cur);
512 			/*int rotatable =*/ line.substr(cur).asUint64();
513 			cur = line.findFirstNotOf(kDigits, cur);
514 			cur = line.findFirstOf(kDigits, cur);
515 			for (int j = 0; j < numWalls; j++) {
516 				Wall w;
517 				w._isCritical = false;
518 				w._id = line.substr(cur).asUint64();
519 				cur = line.findFirstNotOf(kDigits, cur);
520 				cur = line.findFirstOf(kDigits, cur);
521 				int pos = line.substr(cur).asUint64();
522 				cur = line.findFirstNotOf(kDigits, cur);
523 				cur = line.findFirstOf(kDigits, cur);
524 				w._strength = (Strength) line.substr(cur).asUint64();
525 				cur = line.findFirstNotOf(kDigits, cur);
526 				cur = line.findFirstOf(kDigits, cur);
527 				switch (pos % 4) {
528 				case 0:
529 					w._position = kLeft;
530 					break;
531 				case 1:
532 					w._position = kUp;
533 					break;
534 				case 2:
535 					w._position = kRight;
536 					break;
537 				case 3:
538 					w._position = kDown;
539 					break;
540 				}
541 				if (pos >= 4) {
542 					lab._cells[transformedcellid]._immovableWalls.push_back(w);
543 				} else {
544 					lab._cells[transformedcellid]._movableWalls.push_back(w);
545 				}
546 			}
547 		}
548 	}
549 
550 	Common::Point xVector;
551 	Common::Point yVector;
552 	int _highlight;
553 
554 	DaedalusDialogState _dialogState;
555 	int _minotaurX;
556 	int _minotaurY;
557 	int _minotaurTileId;
558 	int _lastChargeSound;
559 	int _lastTrappedSound;
560 	int _lastEscapedSound;
561 	int _levelId;
562 	int _currentSound;
563 	int _soundCounter;
564 	int _soundMax;
565 
566 	Labyrinth _current;
567 	Labyrinth _solution;
568 };
569 
makeMinotaurHandler()570 Common::SharedPtr<Hadesch::Handler> makeMinotaurHandler() {
571 	return Common::SharedPtr<Hadesch::Handler>(new MinotaurHandler());
572 }
573 
574 }
575