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 "common/config-manager.h"
24 
25 #include "agi/agi.h"
26 #include "agi/sprite.h"
27 #include "agi/graphics.h"
28 #include "agi/inv.h"
29 #include "agi/text.h"
30 #include "agi/keyboard.h"
31 #include "agi/menu.h"
32 #include "agi/systemui.h"
33 #include "agi/appleIIgs_timedelay_overwrite.h"
34 
35 namespace Agi {
36 
37 /**
38  * Set up new room.
39  * This function is called when ego enters a new room.
40  * @param n room number
41  */
newRoom(int16 newRoomNr)42 void AgiEngine::newRoom(int16 newRoomNr) {
43 	ScreenObjEntry *screenObj;
44 	ScreenObjEntry *screenObjEgo = &_game.screenObjTable[SCREENOBJECTS_EGO_ENTRY];
45 	int i;
46 
47 	// Loading trigger
48 	artificialDelayTrigger_NewRoom(newRoomNr);
49 
50 	debugC(4, kDebugLevelMain, "*** room %d ***", newRoomNr);
51 	_sound->stopSound();
52 
53 	i = 0;
54 	for (screenObj = _game.screenObjTable; screenObj < &_game.screenObjTable[SCREENOBJECTS_MAX]; screenObj++) {
55 		screenObj->objectNr = i++;
56 		screenObj->flags &= ~(fAnimated | fDrawn);
57 		screenObj->flags |= fUpdate;
58 		screenObj->stepTime = 1;
59 		screenObj->stepTimeCount = 1;
60 		screenObj->cycleTime = 1;
61 		screenObj->cycleTimeCount = 1;
62 		screenObj->stepSize = 1;
63 	}
64 	agiUnloadResources();
65 
66 	_game.playerControl = true;
67 	_game.block.active = false;
68 	_game.horizon = 36;
69 	setVar(VM_VAR_PREVIOUS_ROOM, getVar(VM_VAR_CURRENT_ROOM));
70 	setVar(VM_VAR_CURRENT_ROOM, newRoomNr);
71 	setVar(VM_VAR_BORDER_TOUCH_OBJECT, 0);
72 	setVar(VM_VAR_BORDER_CODE, 0);
73 	setVar(VM_VAR_EGO_VIEW_RESOURCE, screenObjEgo->currentViewNr);
74 
75 	agiLoadResource(RESOURCETYPE_LOGIC, newRoomNr);
76 
77 	// Reposition ego in the new room
78 	switch (getVar(VM_VAR_BORDER_TOUCH_EGO)) {
79 	case 1:
80 		screenObjEgo->yPos = SCRIPT_HEIGHT - 1;
81 		break;
82 	case 2:
83 		screenObjEgo->xPos = 0;
84 		break;
85 	case 3:
86 		screenObjEgo->yPos = _game.horizon + 1;
87 		break;
88 	case 4:
89 		screenObjEgo->xPos = SCRIPT_WIDTH - screenObjEgo->xSize;
90 		break;
91 	}
92 
93 	uint16 agiVersion = getVersion();
94 
95 	if (agiVersion < 0x2000) {
96 		warning("STUB: NewRoom(%d)", newRoomNr);
97 
98 		screenObjEgo->flags &= ~fDidntMove;
99 		// animateObject(0);
100 		agiLoadResource(RESOURCETYPE_VIEW, screenObjEgo->currentViewNr);
101 		setView(screenObjEgo, screenObjEgo->currentViewNr);
102 
103 	} else {
104 		if (agiVersion >= 0x3000) {
105 			// this was only done in AGI3
106 			if (screenObjEgo->motionType == kMotionEgo) {
107 				screenObjEgo->motionType = kMotionNormal;
108 				setVar(VM_VAR_EGO_DIRECTION, 0);
109 			}
110 		}
111 
112 		setVar(VM_VAR_BORDER_TOUCH_EGO, 0);
113 		setFlag(VM_FLAG_NEW_ROOM_EXEC, true);
114 
115 		_game.exitAllLogics = true;
116 
117 		_game._vm->_text->statusDraw();
118 		_game._vm->_text->promptRedraw();
119 	}
120 }
121 
resetControllers()122 void AgiEngine::resetControllers() {
123 	int i;
124 
125 	for (i = 0; i < MAX_CONTROLLERS; i++) {
126 		_game.controllerOccured[i] = false;
127 	}
128 }
129 
interpretCycle()130 void AgiEngine::interpretCycle() {
131 	ScreenObjEntry *screenObjEgo = &_game.screenObjTable[SCREENOBJECTS_EGO_ENTRY];
132 	bool oldSound;
133 	byte oldScore;
134 
135 	if (!_game.playerControl)
136 		setVar(VM_VAR_EGO_DIRECTION, screenObjEgo->direction);
137 	else
138 		screenObjEgo->direction = getVar(VM_VAR_EGO_DIRECTION);
139 
140 	checkAllMotions();
141 
142 	oldScore = getVar(VM_VAR_SCORE);
143 	oldSound = getFlag(VM_FLAG_SOUND_ON);
144 
145 	// Reset script heuristic here
146 	resetGetVarSecondsHeuristic();
147 
148 	_game.exitAllLogics = false;
149 	while (runLogic(0) == 0 && !(shouldQuit() || _restartGame)) {
150 		setVar(VM_VAR_WORD_NOT_FOUND, 0);
151 		setVar(VM_VAR_BORDER_TOUCH_OBJECT, 0);
152 		setVar(VM_VAR_BORDER_CODE, 0);
153 		oldScore = getVar(VM_VAR_SCORE);
154 		setFlag(VM_FLAG_ENTERED_CLI, false);
155 		_game.exitAllLogics = false;
156 		_veryFirstInitialCycle = false;
157 		artificialDelay_CycleDone();
158 		resetControllers();
159 	}
160 	_veryFirstInitialCycle = false;
161 	artificialDelay_CycleDone();
162 	resetControllers();
163 
164 	screenObjEgo->direction = getVar(VM_VAR_EGO_DIRECTION);
165 
166 	if (getVar(VM_VAR_SCORE) != oldScore || getFlag(VM_FLAG_SOUND_ON) != oldSound)
167 		_game._vm->_text->statusDraw();
168 
169 	setVar(VM_VAR_BORDER_TOUCH_OBJECT, 0);
170 	setVar(VM_VAR_BORDER_CODE, 0);
171 	setFlag(VM_FLAG_NEW_ROOM_EXEC, false);
172 	setFlag(VM_FLAG_RESTART_GAME, false);
173 	setFlag(VM_FLAG_RESTORE_JUST_RAN, false);
174 
175 	if (_game.gfxMode) {
176 		updateScreenObjTable();
177 	}
178 	_gfx->updateScreen();
179 	//_gfx->doUpdate();
180 }
181 
182 // We return the current key, or 0 if no key was pressed
processAGIEvents()183 uint16 AgiEngine::processAGIEvents() {
184 	uint16 key;
185 	ScreenObjEntry *screenObjEgo = &_game.screenObjTable[SCREENOBJECTS_EGO_ENTRY];
186 
187 	wait(10);
188 	key = doPollKeyboard();
189 
190 	// In AGI Mouse emulation mode we must update the mouse-related
191 	// vars in every interpreter cycle.
192 	//
193 	// We run AGIMOUSE always as a side effect
194 	setVar(VM_VAR_MOUSE_X, _mouse.pos.x / 2);
195 	setVar(VM_VAR_MOUSE_Y, _mouse.pos.y);
196 
197 	if (!cycleInnerLoopIsActive()) {
198 		// Click-to-walk mouse interface
199 		if (_game.playerControl && (screenObjEgo->flags & fAdjEgoXY)) {
200 			int toX = screenObjEgo->move_x;
201 			int toY = screenObjEgo->move_y;
202 
203 			// AGI Mouse games use ego's sprite's bottom left corner for mouse walking target.
204 			// Amiga games use ego's sprite's bottom center for mouse walking target.
205 			// Atari ST and Apple II GS seem to use the bottom left
206 			if (getPlatform() == Common::kPlatformAmiga)
207 				toX -= (screenObjEgo->xSize / 2); // Center ego's sprite horizontally
208 
209 			// Adjust ego's sprite's mouse walking target position (These parameters are
210 			// controlled with the adj.ego.move.to.x.y-command). Note that these values rely
211 			// on the horizontal centering of the ego's sprite at least on the Amiga platform.
212 			toX += _game.adjMouseX;
213 			toY += _game.adjMouseY;
214 
215 			screenObjEgo->direction = getDirection(screenObjEgo->xPos, screenObjEgo->yPos, toX, toY, screenObjEgo->stepSize);
216 
217 			if (screenObjEgo->direction == 0)
218 				inDestination(screenObjEgo);
219 		}
220 	}
221 
222 	handleMouseClicks(key);
223 
224 	if (!cycleInnerLoopIsActive()) {
225 		// no inner loop active at the moment, regular processing
226 
227 		if (key) {
228 			if (!handleController(key)) {
229 				if (key) {
230 					// Only set VAR_KEY, when no controller/direction was detected
231 					setVar(VM_VAR_KEY, key & 0xFF);
232 					if (_text->promptIsEnabled()) {
233 						_text->promptKeyPress(key);
234 					}
235 				}
236 			}
237 		}
238 
239 		if (_menu->delayedExecuteActive()) {
240 			_menu->execute();
241 		}
242 
243 	} else {
244 		// inner loop active
245 		// call specific workers
246 		switch (_game.cycleInnerLoopType) {
247 		case CYCLE_INNERLOOP_GETSTRING: // loop called from TextMgr::stringEdit()
248 		case CYCLE_INNERLOOP_GETNUMBER:
249 			if (key) {
250 				_text->stringKeyPress(key);
251 			}
252 			break;
253 
254 		case CYCLE_INNERLOOP_INVENTORY: // loop called from InventoryMgr::show()
255 			if (key) {
256 				_inventory->keyPress(key);
257 			}
258 			break;
259 
260 		case CYCLE_INNERLOOP_MENU_VIA_KEYBOARD:
261 			if (key) {
262 				_menu->keyPress(key);
263 			}
264 			break;
265 
266 		case CYCLE_INNERLOOP_MENU_VIA_MOUSE:
267 			_menu->mouseEvent(key);
268 			break;
269 
270 		case CYCLE_INNERLOOP_SYSTEMUI_SELECTSAVEDGAMESLOT:
271 			if (key) {
272 				_systemUI->savedGameSlot_KeyPress(key);
273 			}
274 			break;
275 
276 		case CYCLE_INNERLOOP_SYSTEMUI_VERIFICATION:
277 			_systemUI->askForVerificationKeyPress(key);
278 			break;
279 
280 		case CYCLE_INNERLOOP_MESSAGEBOX:
281 			if (key) {
282 				_text->messageBox_KeyPress(key);
283 			}
284 			break;
285 
286 		default:
287 			break;
288 		}
289 	}
290 
291 	_gfx->updateScreen();
292 
293 	return key;
294 }
295 
playGame()296 int AgiEngine::playGame() {
297 	int ec = errOK;
298 	const AgiAppleIIgsDelayOverwriteGameEntry *appleIIgsDelayOverwrite = nullptr;
299 	const AgiAppleIIgsDelayOverwriteRoomEntry *appleIIgsDelayRoomOverwrite = nullptr;
300 
301 	debugC(2, kDebugLevelMain, "initializing...");
302 	debugC(2, kDebugLevelMain, "game version = 0x%x", getVersion());
303 
304 	_sound->stopSound();
305 
306 	// We need to do this accurately and reset the AGI priorityscreen to 4
307 	// otherwise at least the fan game Nick's Quest will go into an endless
308 	// loop, because the game draws views before it draws the first background picture.
309 	// For further study see bug #3451122
310 	_gfx->clear(0, 4);
311 
312 	_game.horizon = 36;
313 	_game.playerControl = false;
314 
315 	setFlag(VM_FLAG_LOGIC_ZERO_FIRST_TIME, true); // not in 2.917
316 	setFlag(VM_FLAG_NEW_ROOM_EXEC, true);         // needed for MUMG and SQ2!
317 	setFlag(VM_FLAG_SOUND_ON, true);              // enable sound
318 	// do not set VM_VAR_TIME_DELAY, original AGI did not do it (in the data segment it was simply set to 0)
319 
320 	_game.gfxMode = true;
321 	_text->promptRow_Set(22);
322 
323 	// We run AGIMOUSE always as a side effect
324 	//if (getFeatures() & GF_AGIMOUSE)
325 		debug(1, "Using AGI Mouse 1.0 protocol");
326 
327 	if (getFeatures() & GF_AGIPAL)
328 		debug(1, "Running AGIPAL game");
329 
330 	debug(0, "Running AGI script.\n");
331 
332 	setFlag(VM_FLAG_ENTERED_CLI, false);
333 	setFlag(VM_FLAG_SAID_ACCEPTED_INPUT, false);
334 	setVar(VM_VAR_WORD_NOT_FOUND, 0);
335 	setVar(VM_VAR_KEY, 0);
336 
337 	debugC(2, kDebugLevelMain, "Entering main loop");
338 	bool firstLoop = !getFlag(VM_FLAG_RESTART_GAME); // Do not restore on game restart
339 
340 	if (firstLoop) {
341 		if (ConfMan.hasKey("save_slot")) {
342 			// quick restore enabled
343 			_game.automaticRestoreGame = true;
344 		}
345 	}
346 
347 	artificialDelay_Reset();
348 
349 	if (getPlatform() == Common::kPlatformApple2GS) {
350 		// Look up, if there is a time delay overwrite table for the current game
351 		appleIIgsDelayOverwrite = appleIIgsDelayOverwriteGameTable;
352 		while (appleIIgsDelayOverwrite->gameId != GID_AGIDEMO) {
353 			if (appleIIgsDelayOverwrite->gameId == getGameID())
354 				break; // game found
355 			appleIIgsDelayOverwrite++;
356 		}
357 	}
358 
359 	do {
360 		processAGIEvents();
361 
362 		inGameTimerUpdate();
363 
364 		uint16 timeDelay = getVar(VM_VAR_TIME_DELAY);
365 
366 		if (getPlatform() == Common::kPlatformApple2GS) {
367 			timeDelay++;
368 			// It seems that either Apple IIgs ran very slowly or that the delay in its interpreter was not working as everywhere else
369 			// Most games on that platform set the delay to 0, which means no delay in DOS
370 			// Gold Rush! even "optimizes" itself when larger sprites are on the screen it sets TIME_DELAY to 0.
371 			// Normally that game runs at TIME_DELAY 1.
372 			// Maybe a script patch for this game would make sense.
373 			// TODO: needs further investigation
374 
375 			int16 timeDelayOverwrite = -99;
376 
377 			// Now check, if we got a time delay overwrite entry for current room
378 			if (appleIIgsDelayOverwrite->roomTable) {
379 				byte curRoom = getVar(VM_VAR_CURRENT_ROOM);
380 				int16 curPictureNr = _picture->getResourceNr();
381 
382 				appleIIgsDelayRoomOverwrite = appleIIgsDelayOverwrite->roomTable;
383 				while (appleIIgsDelayRoomOverwrite->fromRoom >= 0) {
384 					if ((appleIIgsDelayRoomOverwrite->fromRoom <= curRoom) && (appleIIgsDelayRoomOverwrite->toRoom >= curRoom)) {
385 						if ((appleIIgsDelayRoomOverwrite->activePictureNr == curPictureNr) || (appleIIgsDelayRoomOverwrite->activePictureNr == -1)) {
386 							if (appleIIgsDelayRoomOverwrite->onlyWhenPlayerNotInControl) {
387 								if (_game.playerControl) {
388 									// Player is actually currently in control? -> then skip this entry
389 									appleIIgsDelayRoomOverwrite++;
390 									continue;
391 								}
392 							}
393 							timeDelayOverwrite = appleIIgsDelayRoomOverwrite->timeDelayOverwrite;
394 							break;
395 						}
396 					}
397 					appleIIgsDelayRoomOverwrite++;
398 				}
399 
400 				if (timeDelayOverwrite == -99) {
401 					// use default time delay in case no room specific one was found
402 					timeDelayOverwrite = appleIIgsDelayOverwrite->defaultTimeDelayOverwrite;
403 				}
404 			} else {
405 				timeDelayOverwrite = appleIIgsDelayOverwrite->defaultTimeDelayOverwrite;
406 			}
407 
408 			if (timeDelayOverwrite >= 0) {
409 				if (timeDelayOverwrite != timeDelay) {
410 					// delayOverwrite is not the same as the delay taken from the scripts? overwrite it
411 					//warning("AppleIIgs: time delay overwrite from %d to %d", timeDelay, timeDelayOverwrite);
412 
413 					setVar(VM_VAR_TIME_DELAY, timeDelayOverwrite - 1); // adjust for Apple IIgs
414 					timeDelay = timeDelayOverwrite;
415 				}
416 			}
417 		}
418 
419 		// Increment the delay value by one, so that we wait for at least 1 cycle
420 		// In Original AGI 1 cycle was 50 milliseconds, so 20 frames per second
421 		// So TIME_DELAY 1 resulted in around 20 frames per second
422 		//               2 resulted in around 10 frames per second
423 		//               0 however resulted in no limits at all, so the game ran as fast as possible
424 		// We obviously do not want the game to run as fast as possible, so we will use 40 frames per second instead.
425 		timeDelay = timeDelay * 2;
426 		if (!timeDelay)
427 			timeDelay = 1;
428 
429 		// Our cycle counter runs at 25 milliseconds.
430 		// So time delay has to be 1 for the originally unlimited speed - for our 40 fps
431 		//                         2 for 20 frames per second
432 		//                         4 for 10 frames per second
433 		//                         and so on.
434 
435 		if (_passedPlayTimeCycles >= timeDelay) {
436 			// code to check for executed cycles
437 			// TimeDate time;
438 			// g_system->getTimeAndDate(time);
439 			// warning("cycle %d", time.tm_sec);
440 			inGameTimerResetPassedCycles();
441 
442 			interpretCycle();
443 
444 			// Check if the user has asked to load a game from the command line
445 			// or the launcher
446 			if (_game.automaticRestoreGame) {
447 				_game.automaticRestoreGame = false;
448 				checkQuickLoad();
449 			}
450 
451 			setFlag(VM_FLAG_ENTERED_CLI, false);
452 			setFlag(VM_FLAG_SAID_ACCEPTED_INPUT, false);
453 			setVar(VM_VAR_WORD_NOT_FOUND, 0);
454 			setVar(VM_VAR_KEY, 0);
455 		}
456 
457 		if (shouldPerformAutoSave(_lastSaveTime)) {
458 			saveGame(getSavegameFilename(0), "Autosave");
459 		}
460 
461 	} while (!(shouldQuit() || _restartGame));
462 
463 	_sound->stopSound();
464 
465 	return ec;
466 }
467 
runGame()468 int AgiEngine::runGame() {
469 	int ec = errOK;
470 
471 	// Execute the game
472 	do {
473 		debugC(2, kDebugLevelMain, "game loop");
474 		debugC(2, kDebugLevelMain, "game version = 0x%x", getVersion());
475 
476 		if (agiInit() != errOK)
477 			break;
478 
479 		if (_restartGame) {
480 			setFlag(VM_FLAG_RESTART_GAME, true);
481 			// do not set VM_VAR_TIME_DELAY, original AGI did not do it
482 
483 			// Reset in-game timer
484 			inGameTimerReset();
485 
486 			_restartGame = false;
487 		}
488 
489 		// Set computer type (v20 i.e. vComputer) and sound type
490 		switch (getPlatform()) {
491 		case Common::kPlatformAtariST:
492 			setVar(VM_VAR_COMPUTER, kAgiComputerAtariST);
493 			setVar(VM_VAR_SOUNDGENERATOR, kAgiSoundPC);
494 			break;
495 		case Common::kPlatformAmiga:
496 			if (getFeatures() & GF_OLDAMIGAV20)
497 				setVar(VM_VAR_COMPUTER, kAgiComputerAmigaOld);
498 			else
499 				setVar(VM_VAR_COMPUTER, kAgiComputerAmiga);
500 			setVar(VM_VAR_SOUNDGENERATOR, kAgiSoundTandy);
501 			break;
502 		case Common::kPlatformApple2GS:
503 			setVar(VM_VAR_COMPUTER, kAgiComputerApple2GS);
504 			if (getFeatures() & GF_2GSOLDSOUND)
505 				setVar(VM_VAR_SOUNDGENERATOR, kAgiSound2GSOld);
506 			else
507 				setVar(VM_VAR_SOUNDGENERATOR, kAgiSoundTandy);
508 			break;
509 		case Common::kPlatformDOS:
510 		default:
511 			setVar(VM_VAR_COMPUTER, kAgiComputerPC);
512 			setVar(VM_VAR_SOUNDGENERATOR, kAgiSoundPC);
513 			break;
514 		}
515 
516 		// Set monitor type (v26 i.e. vMonitor)
517 		switch (_renderMode) {
518 		case Common::kRenderCGA:
519 			setVar(VM_VAR_MONITOR, kAgiMonitorCga);
520 			break;
521 		case Common::kRenderHercA:
522 		case Common::kRenderHercG:
523 			// Set EGA for now. Some games place text differently, when this is set to kAgiMonitorHercules.
524 			// Text placement was different for Hercules rendering (16x12 instead of 16x16). There also was
525 			// not enough space left for the prompt at the bottom. This was caused by the Hercules resolution.
526 			// We don't have this restriction and we also support the regular prompt for Hercules mode.
527 			// In theory Sierra could have had special Hercules code inside their games.
528 			// TODO: check this.
529 			setVar(VM_VAR_MONITOR, kAgiMonitorEga);
530 			break;
531 		// Don't know if Amiga AGI games use a different value than kAgiMonitorEga
532 		// for vMonitor so I just use kAgiMonitorEga for them (As was done before too).
533 		case Common::kRenderAmiga:
534 		case Common::kRenderApple2GS:
535 		case Common::kRenderAtariST:
536 		case Common::kRenderEGA:
537 		case Common::kRenderVGA:
538 		default:
539 			setVar(VM_VAR_MONITOR, kAgiMonitorEga);
540 			break;
541 		}
542 
543 		setVar(VM_VAR_FREE_PAGES, 180); // Set amount of free memory to realistic value
544 		setVar(VM_VAR_MAX_INPUT_CHARACTERS, 38);
545 		_text->promptDisable();
546 
547 		ec = playGame();
548 		agiDeinit();
549 	} while (_restartGame);
550 
551 	delete _menu;
552 	_menu = NULL;
553 
554 	releaseImageStack();
555 
556 	return ec;
557 }
558 
559 } // End of namespace Agi
560