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 
24 #include "common/scummsys.h"
25 #include "common/events.h"
26 #include "common/system.h"
27 
28 #include "cine/main_loop.h"
29 #include "cine/object.h"
30 #include "cine/various.h"
31 #include "cine/bg_list.h"
32 #include "cine/sound.h"
33 
34 #include "backends/audiocd/audiocd.h"
35 
36 namespace Cine {
37 
38 struct MouseStatusStruct {
39 	int left;
40 	int right;
41 };
42 
43 MouseStatusStruct mouseData;
44 
45 uint16 mouseRight = 0;
46 uint16 mouseLeft = 0;
47 
48 uint16 mouseUpdateStatus;
49 uint16 dummyU16;
50 
processEvent(Common::Event & event)51 static void processEvent(Common::Event &event) {
52 	switch (event.type) {
53 	case Common::EVENT_LBUTTONDOWN:
54 		mouseLeft = 1;
55 		break;
56 	case Common::EVENT_RBUTTONDOWN:
57 		mouseRight = 1;
58 		break;
59 	case Common::EVENT_MBUTTONDOWN:
60 		mouseLeft = mouseRight = 1;
61 		break;
62 	case Common::EVENT_LBUTTONUP:
63 		mouseLeft = 0;
64 		break;
65 	case Common::EVENT_RBUTTONUP:
66 		mouseRight = 0;
67 		break;
68 	case Common::EVENT_MBUTTONUP:
69 		mouseLeft = mouseRight = 0;
70 		break;
71 	case Common::EVENT_MOUSEMOVE:
72 		break;
73 	case Common::EVENT_WHEELUP:
74 		g_cine->_keyInputList.push_back(Common::KeyState(Common::KEYCODE_UP));
75 		break;
76 	case Common::EVENT_WHEELDOWN:
77 		g_cine->_keyInputList.push_back(Common::KeyState(Common::KEYCODE_DOWN));
78 		break;
79 	case Common::EVENT_KEYDOWN:
80 		switch (event.kbd.keycode) {
81 		case Common::KEYCODE_RETURN:
82 		case Common::KEYCODE_KP_ENTER:
83 		case Common::KEYCODE_KP5:
84 			if (allowPlayerInput) {
85 				mouseLeft = 1;
86 			}
87 			break;
88 		case Common::KEYCODE_ESCAPE:
89 			if (allowPlayerInput) {
90 				mouseRight = 1;
91 			}
92 			break;
93 		case Common::KEYCODE_F1:
94 			if (allowPlayerInput) {
95 				playerCommand = 0; // EXAMINE
96 				makeCommandLine();
97 			}
98 			break;
99 		case Common::KEYCODE_F2:
100 			if (allowPlayerInput) {
101 				playerCommand = 1; // TAKE
102 				makeCommandLine();
103 			}
104 			break;
105 		case Common::KEYCODE_F3:
106 			if (allowPlayerInput && !inMenu) {
107 				playerCommand = 2; // INVENTORY
108 				makeCommandLine();
109 			}
110 			break;
111 		case Common::KEYCODE_F4:
112 			if (allowPlayerInput && !inMenu) {
113 				playerCommand = 3; // USE
114 				makeCommandLine();
115 			}
116 			break;
117 		case Common::KEYCODE_F5:
118 			if (allowPlayerInput) {
119 				playerCommand = 4; // ACTIVATE
120 				makeCommandLine();
121 			}
122 			break;
123 		case Common::KEYCODE_F6:
124 			if (allowPlayerInput) {
125 				playerCommand = 5; // SPEAK
126 				makeCommandLine();
127 			}
128 			break;
129 		case Common::KEYCODE_F9:
130 			if (allowPlayerInput && !inMenu) {
131 				makeActionMenu();
132 				makeCommandLine();
133 			}
134 			break;
135 		case Common::KEYCODE_F10:
136 			if (!inMenu) {
137 				g_cine->makeSystemMenu();
138 			}
139 			break;
140 		case Common::KEYCODE_F11:
141 			renderer->showCollisionPage(true);
142 			break;
143 		case Common::KEYCODE_KP0:
144 			g_cine->setDefaultGameSpeed();
145 			break;
146 		case Common::KEYCODE_MINUS:
147 		case Common::KEYCODE_KP_MINUS:
148 			g_cine->modifyGameSpeed(-1); // Slower
149 			break;
150 		case Common::KEYCODE_PLUS:
151 		case Common::KEYCODE_KP_PLUS:
152 			g_cine->modifyGameSpeed(+1); // Faster
153 			break;
154 		case Common::KEYCODE_KP4:
155 			moveUsingKeyboard(-1, 0); // Left
156 			break;
157 		case Common::KEYCODE_KP6:
158 			moveUsingKeyboard(+1, 0); // Right
159 			break;
160 		case Common::KEYCODE_KP8:
161 			moveUsingKeyboard(0, +1); // Up
162 			break;
163 		case Common::KEYCODE_KP2:
164 			moveUsingKeyboard(0, -1); // Down
165 			break;
166 		case Common::KEYCODE_KP9:
167 			moveUsingKeyboard(+1, +1); // Up & Right
168 			break;
169 		case Common::KEYCODE_KP7:
170 			moveUsingKeyboard(-1, +1); // Up & Left
171 			break;
172 		case Common::KEYCODE_KP1:
173 			moveUsingKeyboard(-1, -1); // Down & Left
174 			break;
175 		case Common::KEYCODE_KP3:
176 			moveUsingKeyboard(+1, -1); // Down & Right
177 			break;
178 		case Common::KEYCODE_LEFT:
179 			// fall through
180 		case Common::KEYCODE_RIGHT:
181 			// fall through
182 		case Common::KEYCODE_UP:
183 			// fall through
184 		case Common::KEYCODE_DOWN:
185 			// fall through
186 		default:
187 			g_cine->_keyInputList.push_back(event.kbd);
188 			break;
189 		}
190 		break;
191 	case Common::EVENT_KEYUP:
192 		switch (event.kbd.keycode) {
193 		case Common::KEYCODE_RETURN:
194 		case Common::KEYCODE_KP_ENTER:
195 		case Common::KEYCODE_KP5:
196 			if (allowPlayerInput) {
197 				mouseLeft = 0;
198 			}
199 			break;
200 		case Common::KEYCODE_ESCAPE:
201 			if (allowPlayerInput) {
202 				mouseRight = 0;
203 			}
204 			break;
205 		case Common::KEYCODE_F11:
206 			renderer->showCollisionPage(false);
207 			break;
208 		case Common::KEYCODE_KP4:   // Left
209 		case Common::KEYCODE_KP6:   // Right
210 		case Common::KEYCODE_KP8:   // Up
211 		case Common::KEYCODE_KP2:   // Down
212 		case Common::KEYCODE_KP9:   // Up & Right
213 		case Common::KEYCODE_KP7:   // Up & Left
214 		case Common::KEYCODE_KP1:   // Down & Left
215 		case Common::KEYCODE_KP3:   // Down & Right
216 			// Stop ego movement made with keyboard when releasing a known key
217 			moveUsingKeyboard(0, 0);
218 			break;
219 		default:
220 			break;
221 		}
222 	default:
223 		break;
224 	}
225 }
226 
manageEvents(CallSource callSource,EventTarget eventTarget,bool useMaxMouseButtonState,Common::Array<Common::Rect> rects)227 void manageEvents(CallSource callSource, EventTarget eventTarget, bool useMaxMouseButtonState, Common::Array<Common::Rect> rects) {
228 	Common::EventManager *eventMan = g_system->getEventManager();
229 	Common::Point mousePos;
230 	uint keysPressed = g_cine->_keyInputList.size();
231 	bool foundTarget = false;
232 	int eventsChecked = 0;
233 	uint16 maxMouseLeft = mouseLeft;
234 	uint16 maxMouseRight = mouseRight;
235 	uint32 waitStart = g_system->getMillis();
236 	uint32 waitEnd = waitStart + g_cine->getTimerDelay();
237 	uint32 frameEnd = waitStart + 20;
238 	bool frameEnded = false;
239 	bool waitEnded = false;
240 	bool checkWaitEnd = (eventTarget == UNTIL_WAIT_ENDED);
241 	bool updateScreen = false;
242 	bool updateAudio = false;
243 
244 	do {
245 		Common::Event event = Common::Event();
246 		int eventsCheckedBeforePolling = eventsChecked;
247 		while (!foundTarget && !frameEnded && (eventMan->pollEvent(event) || eventsChecked == 0)) {
248 			processEvent(event);
249 			eventsChecked++;
250 			maxMouseLeft = MAX<uint16>(mouseLeft, maxMouseLeft);
251 			maxMouseRight = MAX<uint16>(mouseRight, maxMouseRight);
252 
253 			bool mouseButtonDown = (mouseLeft != 0 || mouseRight != 0);
254 			bool mouseButtonUp = !mouseButtonDown;
255 
256 			switch (eventTarget) {
257 			case UNTIL_MOUSE_BUTTON_UP_DOWN_UP:
258 				// fall through
259 			case UNTIL_MOUSE_BUTTON_UP_DOWN:
260 				// fall through
261 			case UNTIL_MOUSE_BUTTON_UP:
262 				// fall through
263 			case UNTIL_MOUSE_BUTTON_UP_AND_WAIT_ENDED:
264 				foundTarget = mouseButtonUp;
265 				break;
266 			case UNTIL_MOUSE_BUTTON_DOWN_UP:
267 				// fall through
268 			case UNTIL_MOUSE_BUTTON_DOWN:
269 				foundTarget = mouseButtonDown;
270 				break;
271 			case UNTIL_MOUSE_BUTTON_DOWN_OR_KEY_UP_OR_DOWN_OR_IN_RECTS:
272 				foundTarget = mouseButtonDown;
273 				if (!g_cine->_keyInputList.empty()) {
274 					Common::KeyState key = g_cine->_keyInputList.back();
275 					if (key.keycode == Common::KEYCODE_UP || key.keycode == Common::KEYCODE_DOWN) {
276 						foundTarget = true;
277 					}
278 				}
279 				mousePos = g_system->getEventManager()->getMousePos();
280 				for (Common::Array<Common::Rect>::iterator it = rects.begin(); it != rects.end(); ++it) {
281 					if (it->contains(mousePos)) {
282 						foundTarget = true;
283 						break;
284 					}
285 				}
286 				break;
287 			case UNTIL_MOUSE_BUTTON_DOWN_OR_KEY_INPUT:
288 				foundTarget = mouseButtonDown || keysPressed < g_cine->_keyInputList.size();
289 				break;
290 			default:
291 				break;
292 			}
293 
294 			uint32 now = g_system->getMillis();
295 			frameEnded = (now >= frameEnd);
296 			waitEnded = (now >= waitEnd);
297 
298 			if (foundTarget) {
299 				switch (eventTarget) {
300 				case UNTIL_MOUSE_BUTTON_UP_DOWN_UP:
301 					eventTarget = UNTIL_MOUSE_BUTTON_DOWN_UP;
302 					foundTarget = false;
303 					break;
304 				case UNTIL_MOUSE_BUTTON_UP_DOWN:
305 					eventTarget = UNTIL_MOUSE_BUTTON_DOWN;
306 					foundTarget = false;
307 					break;
308 				case UNTIL_MOUSE_BUTTON_DOWN_UP:
309 					eventTarget = UNTIL_MOUSE_BUTTON_UP;
310 					foundTarget = false;
311 					break;
312 				case UNTIL_MOUSE_BUTTON_UP_AND_WAIT_ENDED:
313 					eventTarget = UNTIL_WAIT_ENDED;
314 					checkWaitEnd = true;
315 					foundTarget = false;
316 					break;
317 				default:
318 					break;
319 				}
320 			}
321 
322 			foundTarget |= (checkWaitEnd && waitEnded);
323 		}
324 		int eventsCheckedAfterPolling = eventsChecked;
325 
326 		bool eventQueueEmpty = (eventsCheckedBeforePolling == eventsCheckedAfterPolling);
327 
328 		if (eventQueueEmpty) {
329 			uint32 now = g_system->getMillis();
330 			frameEnded = (now >= frameEnd);
331 			waitEnded = (now >= waitEnd);
332 		}
333 
334 		if (eventTarget == UNTIL_WAIT_ENDED) {
335 			foundTarget = waitEnded;
336 		}
337 
338 		if (eventTarget == EMPTY_EVENT_QUEUE) {
339 			foundTarget = eventQueueEmpty;
340 		}
341 
342 		foundTarget |= (checkWaitEnd && waitEnded);
343 		updateScreen = updateAudio = (foundTarget || frameEnded);
344 
345 		if (updateScreen) {
346 			if (callSource != EXECUTE_PLAYER_INPUT) {
347 				g_system->updateScreen();
348 			} else {
349 				// Make the command line (e.g. "EXAMINE DOOR" -> "EXAMINE BUTTON")
350 				// responsive by updating it here.
351 				if (allowPlayerInput && playerCommand != -1 && !mouseLeft && !mouseRight) {
352 					// A player command is given, left and right mouse buttons are up
353 					mousePos = eventMan->getMousePos();
354 					playerCommandMouseLeftRightUp(mousePos.x, mousePos.y);
355 					renderer->drawCommand();
356 				}
357 
358 				renderer->blit();
359 			}
360 		}
361 
362 		if (updateAudio) {
363 			g_system->getAudioCDManager()->update(); // For Future Wars CD version
364 		}
365 
366 		if (frameEnded) {
367 			frameEnd += 20;
368 		}
369 
370 		g_system->delayMillis(10);
371 	} while (!foundTarget && !g_cine->shouldQuit());
372 
373 	if (useMaxMouseButtonState) {
374 		mouseData.left = maxMouseLeft;
375 		mouseData.right = maxMouseRight;
376 	} else {
377 		mouseData.left = mouseLeft;
378 		mouseData.right = mouseRight;
379 	}
380 }
381 
getMouseData(uint16 param,uint16 * pButton,uint16 * pX,uint16 * pY)382 void getMouseData(uint16 param, uint16 *pButton, uint16 *pX, uint16 *pY) {
383 	Common::Point mouse = g_system->getEventManager()->getMousePos();
384 	*pX = mouse.x;
385 	*pY = mouse.y;
386 
387 	*pButton = 0;
388 
389 	if (mouseData.right) {
390 		(*pButton) |= 2;
391 	}
392 
393 	if (mouseData.left) {
394 		(*pButton) |= 1;
395 	}
396 }
397 
398 /** Removes elements from seqList that have their member variable var4 set to value -1. */
purgeSeqList()399 void purgeSeqList() {
400 	Common::List<SeqListElement>::iterator it = g_cine->_seqList.begin();
401 	while (it != g_cine->_seqList.end()) {
402 		if (it->var4 == -1) {
403 			// Erase the element and jump to the next element
404 			it = g_cine->_seqList.erase(it);
405 		} else {
406 			// Let the element be and jump to the next element
407 			++it;
408 		}
409 	}
410 }
411 
mainLoop(int bootScriptIdx)412 void CineEngine::mainLoop(int bootScriptIdx) {
413 	byte di;
414 
415 	if (_preLoad == false) {
416 		resetBgIncrustList();
417 
418 		setTextWindow(0, 0, 20, 200);
419 
420 		errorVar = 0;
421 
422 		addScriptToGlobalScripts(bootScriptIdx);
423 
424 		menuVar = 0;
425 
426 //		gfxRedrawPage(page0c, page0, page0c, page0, -1);
427 //		gfxWaitVBL();
428 //		gfxRedrawMouseCursor();
429 
430 		inMenu = false;
431 		allowPlayerInput = 0;
432 		checkForPendingDataLoadSwitch = 0;
433 
434 		reloadBgPalOnNextFlip = 0;
435 		forbidBgPalReload = 0;
436 		gfxFadeOutCompleted = 0;
437 		gfxFadeInRequested = 0;
438 		safeControlsLastAccessedMs = 0;
439 		lastSafeControlObjIdx = -1;
440 		isDrawCommandEnabled = 0;
441 		waitForPlayerClick = 0;
442 		menuCommandLen = 0;
443 
444 		playerCommand = -1;
445 		g_cine->_commandBuffer = "";
446 
447 		g_cine->_globalVars[VAR_MOUSE_X_POS] = 0;
448 		g_cine->_globalVars[VAR_MOUSE_Y_POS] = 0;
449 		if (g_cine->getGameType() == Cine::GType_OS) {
450 			g_cine->_globalVars[VAR_MOUSE_X_POS_2ND] = 0;
451 			g_cine->_globalVars[VAR_MOUSE_Y_POS_2ND] = 0;
452 			g_cine->_globalVars[VAR_BYPASS_PROTECTION] = 0; // set to 1 to bypass the copy protection
453 			g_cine->_globalVars[VAR_LOW_MEMORY] = 0; // set to 1 to disable some animations, sounds etc.
454 		}
455 
456 		renderer->setBlackPalette(true); // Sets _changePal = true
457 
458 		strcpy(newPrcName, "");
459 		strcpy(newRelName, "");
460 		strcpy(newObjectName, "");
461 		strcpy(newMsgName, "");
462 		strcpy(currentCtName, "");
463 		strcpy(currentPartName, "");
464 
465 		g_sound->stopMusic();
466 	}
467 
468 	do {
469 		// HACK: Force amount of oxygen left to maximum during Operation Stealth's first arcade sequence.
470 		//       This makes it possible to pass the arcade sequence for now.
471 		// FIXME: Remove the hack and make the first arcade sequence normally playable.
472 		/*
473 		if (g_cine->getGameType() == Cine::GType_OS) {
474 			Common::String bgName(renderer->getBgName());
475 			// Check if the background is one of the three backgrounds
476 			// that are only used during the first arcade sequence.
477 			if (bgName == "28.PI1" || bgName == "29.PI1" || bgName == "30.PI1") {
478 				static const uint oxygenObjNum = 202, maxOxygen = 264;
479 				// Force the amount of oxygen left to the maximum.
480 				g_cine->_objectTable[oxygenObjNum].x = maxOxygen;
481 			}
482 		}*/
483 
484 		// HACK: In Operation Stealth after the first arcade sequence jump player's position to avoid getting stuck.
485 		// After the first arcade sequence the player comes up stairs from
486 		// the water in Santa Paragua's downtown in front of the flower shop.
487 		// Previously he was completely stuck after getting up the stairs.
488 		// If the background is the one used in the flower shop scene ("21.PI1")
489 		// and the player is at the exact location after getting up the stairs
490 		// then we just nudge him a tiny bit away from the stairs and voila, he's free!
491 		// Maybe the real problem behind all this is collision data related as it looks
492 		// like there's some boundary right there near position (204, 110) which we can
493 		// jump over by moving the character to (204, 109). The script handling the
494 		// flower shop scene is AIRPORT.PRC's 13th script.
495 		// FIXME: Remove the hack and solve what's really causing the problem in the first place.
496 		if (hacksEnabled && g_cine->getGameType() == Cine::GType_OS) {
497 			if (scumm_stricmp(renderer->getBgName(), "21.PI1") == 0 && g_cine->_objectTable[1].x == 204 && g_cine->_objectTable[1].y == 110) {
498 				g_cine->_objectTable[1].y--; // Move the player character upward on-screen by one pixel
499 			}
500 		}
501 
502 		stopMusicAfterFadeOut();
503 		di = executePlayerInput();
504 
505 		// Clear the zoneQuery table (Operation Stealth specific)
506 		if (g_cine->getGameType() == Cine::GType_OS) {
507 			Common::fill(g_cine->_zoneQuery.begin(), g_cine->_zoneQuery.end(), 0);
508 		}
509 
510 		if (g_cine->getGameType() == Cine::GType_OS) {
511 			processSeqList();
512 		}
513 		executeObjectScripts();
514 		executeGlobalScripts();
515 
516 		purgeObjectScripts();
517 		purgeGlobalScripts();
518 		if (g_cine->getGameType() == Cine::GType_OS) {
519 			purgeSeqList();
520 		}
521 
522 		if (playerCommand == -1) {
523 			setMouseCursor(MOUSE_CURSOR_NORMAL);
524 		} else {
525 			setMouseCursor(MOUSE_CURSOR_CROSS);
526 		}
527 
528 		if (gfxFadeInRequested) {
529 			gfxFadeOutCompleted = 0;
530 		}
531 
532 		if (renderer->ready()) {
533 			renderer->drawFrame(true);
534 		}
535 
536 		// NOTE: In the original Future Wars and Operation Stealth messages
537 		// were removed when running the drawOverlays function which is
538 		// currently called from the renderer's drawFrame function.
539 		removeMessages();
540 
541 		if (waitForPlayerClick) {
542 			_messageLen <<= 3;
543 			if (_messageLen < 800)
544 				_messageLen = 800;
545 
546 			manageEvents(MAIN_LOOP_WAIT_FOR_PLAYER_CLICK, UNTIL_MOUSE_BUTTON_UP_DOWN_UP);
547 			waitForPlayerClick = 0;
548 		}
549 
550 		if (checkForPendingDataLoadSwitch) {
551 			checkForPendingDataLoad();
552 
553 			checkForPendingDataLoadSwitch = 0;
554 		}
555 
556 		if (di) {
557 			if ("quit"[menuCommandLen] == (char)di) {
558 				++menuCommandLen;
559 				if (menuCommandLen == 4) {
560 					quitGame();
561 				}
562 			} else {
563 				menuCommandLen = 0;
564 			}
565 		}
566 	} while (!shouldQuit() && !_restartRequested);
567 
568 	hideMouse();
569 	g_sound->stopMusic();
570 	//if (g_cine->getGameType() == Cine::GType_OS) {
571 	//	freeUnkList();
572 	//}
573 	closePart();
574 }
575 
576 } // End of namespace Cine
577