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