scummvm/engines/agi/cycle.cpp
Filippos Karapetis 5fadff59f9 AGI: Fix bug #3600733 - "AGI FANMADE: function slowing down game"
We now only test for events in testKeypressed() without updating the
game cycle at all (NAGI doesn't update the game cycle either). This
fixes the slowdowns in some animations where have.key() is issued,
like Manannan's lightnings in the intro of KQ3 and the bullets in the
intro of PQ1
2013-05-11 17:09:59 +03:00

467 lines
11 KiB
C++

/* ScummVM - Graphic Adventure Engine
*
* ScummVM is the legal property of its developers, whose names
* are too numerous to list here. Please refer to the COPYRIGHT
* file distributed with this source distribution.
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation; either version 2
* of the License, or (at your option) any later version.
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*
*/
#include "agi/agi.h"
#include "agi/sprite.h"
#include "agi/graphics.h"
#include "agi/keyboard.h"
#include "agi/menu.h"
namespace Agi {
/**
* Set up new room.
* This function is called when ego enters a new room.
* @param n room number
*/
void AgiEngine::newRoom(int n) {
VtEntry *v;
int i;
// Simulate slowww computer.
// Many effects rely on it.
pause(kPauseRoom);
debugC(4, kDebugLevelMain, "*** room %d ***", n);
_sound->stopSound();
i = 0;
for (v = _game.viewTable; v < &_game.viewTable[MAX_VIEWTABLE]; v++) {
v->entry = i++;
v->flags &= ~(fAnimated | fDrawn);
v->flags |= fUpdate;
v->stepTime = 1;
v->stepTimeCount = 1;
v->cycleTime = 1;
v->cycleTimeCount = 1;
v->stepSize = 1;
}
agiUnloadResources();
_game.playerControl = true;
_game.block.active = false;
_game.horizon = 36;
_game.vars[vPrevRoom] = _game.vars[vCurRoom];
_game.vars[vCurRoom] = n;
_game.vars[vBorderTouchObj] = 0;
_game.vars[vBorderCode] = 0;
_game.vars[vEgoViewResource] = _game.viewTable[0].currentView;
agiLoadResource(rLOGIC, n);
// Reposition ego in the new room
switch (_game.vars[vBorderTouchEgo]) {
case 1:
_game.viewTable[0].yPos = _HEIGHT - 1;
break;
case 2:
_game.viewTable[0].xPos = 0;
break;
case 3:
_game.viewTable[0].yPos = HORIZON + 1;
break;
case 4:
_game.viewTable[0].xPos = _WIDTH - _game.viewTable[0].xSize;
break;
}
_game.vars[vBorderTouchEgo] = 0;
setflag(fNewRoomExec, true);
_game.exitAllLogics = true;
writeStatus();
writePrompt();
}
void AgiEngine::resetControllers() {
int i;
for (i = 0; i < MAX_DIRS; i++) {
_game.controllerOccured[i] = false;
}
}
void AgiEngine::interpretCycle() {
int oldSound, oldScore;
if (_game.playerControl)
_game.vars[vEgoDir] = _game.viewTable[0].direction;
else
_game.viewTable[0].direction = _game.vars[vEgoDir];
checkAllMotions();
oldScore = _game.vars[vScore];
oldSound = getflag(fSoundOn);
_game.exitAllLogics = false;
while (runLogic(0) == 0 && !(shouldQuit() || _restartGame)) {
_game.vars[vWordNotFound] = 0;
_game.vars[vBorderTouchObj] = 0;
_game.vars[vBorderCode] = 0;
oldScore = _game.vars[vScore];
setflag(fEnteredCli, false);
_game.exitAllLogics = false;
resetControllers();
}
resetControllers();
_game.viewTable[0].direction = _game.vars[vEgoDir];
if (_game.vars[vScore] != oldScore || getflag(fSoundOn) != oldSound)
writeStatus();
_game.vars[vBorderTouchObj] = 0;
_game.vars[vBorderCode] = 0;
setflag(fNewRoomExec, false);
setflag(fRestartGame, false);
setflag(fRestoreJustRan, false);
if (_game.gfxMode) {
updateViewtable();
_gfx->doUpdate();
}
}
/**
* Update AGI interpreter timer.
*/
void AgiEngine::updateTimer() {
_clockCount++;
if (_clockCount <= TICK_SECONDS)
return;
_clockCount -= TICK_SECONDS;
if (!_game.clockEnabled)
return;
setvar(vSeconds, getvar(vSeconds) + 1);
if (getvar(vSeconds) < 60)
return;
setvar(vSeconds, 0);
setvar(vMinutes, getvar(vMinutes) + 1);
if (getvar(vMinutes) < 60)
return;
setvar(vMinutes, 0);
setvar(vHours, getvar(vHours) + 1);
if (getvar(vHours) < 24)
return;
setvar(vHours, 0);
setvar(vDays, getvar(vDays) + 1);
}
void AgiEngine::newInputMode(InputMode mode) {
if (mode == INPUT_MENU && !getflag(fMenusWork) && !(getFeatures() & GF_MENUS))
return;
_oldMode = _game.inputMode;
_game.inputMode = mode;
}
void AgiEngine::oldInputMode() {
_game.inputMode = _oldMode;
}
// If main_cycle returns false, don't process more events!
int AgiEngine::mainCycle(bool onlyCheckForEvents) {
unsigned int key, kascii;
VtEntry *v = &_game.viewTable[0];
if (!onlyCheckForEvents) {
pollTimer();
updateTimer();
}
key = doPollKeyboard();
// In AGI Mouse emulation mode we must update the mouse-related
// vars in every interpreter cycle.
//
// We run AGIMOUSE always as a side effect
//if (getFeatures() & GF_AGIMOUSE) {
_game.vars[28] = _mouse.x / 2;
_game.vars[29] = _mouse.y;
//}
if (key == KEY_STATUSLN) { // F11
_debug.statusline = !_debug.statusline;
writeStatus();
key = 0;
}
if (key == KEY_PRIORITY) { // F12
_sprites->eraseBoth();
_debug.priority = !_debug.priority;
_picture->showPic();
_sprites->blitBoth();
_sprites->commitBoth();
key = 0;
}
// Click-to-walk mouse interface
if (_game.playerControl && (v->flags & fAdjEgoXY)) {
int toX = v->parm1;
int toY = v->parm2;
// AGI Mouse games use ego's sprite's bottom left corner for mouse walking target.
// Amiga games use ego's sprite's bottom center for mouse walking target.
// TODO: Check what Atari ST AGI and Apple IIGS AGI use for mouse walking target.
if (getPlatform() == Common::kPlatformAmiga)
toX -= (v->xSize / 2); // Center ego's sprite horizontally
// Adjust ego's sprite's mouse walking target position (These parameters are
// controlled with the adj.ego.move.to.x.y-command). Note that these values rely
// on the horizontal centering of the ego's sprite at least on the Amiga platform.
toX += _game.adjMouseX;
toY += _game.adjMouseY;
v->direction = getDirection(v->xPos, v->yPos, toX, toY, v->stepSize);
if (v->direction == 0)
inDestination(v);
}
kascii = KEY_ASCII(key);
if (kascii)
setvar(vKey, kascii);
bool restartProcessKey;
do {
restartProcessKey = false;
switch (_game.inputMode) {
case INPUT_NORMAL:
if (!handleController(key)) {
if (key == 0 || !_game.inputEnabled)
break;
handleKeys(key);
// if ESC pressed, activate menu before
// accept.input from the interpreter cycle
// sets the input mode to normal again
// (closes: #540856)
if (key == KEY_ESCAPE) {
key = 0;
restartProcessKey = true;
}
// commented out to close Sarien bug #438872
//if (key)
// _game.keypress = key;
}
break;
case INPUT_GETSTRING:
handleController(key);
handleGetstring(key);
setvar(vKey, 0); // clear ENTER key
break;
case INPUT_MENU:
_menu->keyhandler(key);
_gfx->doUpdate();
return false;
case INPUT_NONE:
handleController(key);
if (key)
_game.keypress = key;
break;
}
} while (restartProcessKey);
if (!onlyCheckForEvents) {
_gfx->doUpdate();
if (_game.msgBoxTicks > 0)
_game.msgBoxTicks--;
}
return true;
}
int AgiEngine::playGame() {
int ec = errOK;
debugC(2, kDebugLevelMain, "initializing...");
debugC(2, kDebugLevelMain, "game version = 0x%x", getVersion());
_sound->stopSound();
_gfx->clearScreen(0);
_game.horizon = HORIZON;
_game.playerControl = false;
setflag(fLogicZeroFirsttime, true); // not in 2.917
setflag(fNewRoomExec, true); // needed for MUMG and SQ2!
setflag(fSoundOn, true); // enable sound
setvar(vTimeDelay, 2); // "normal" speed
_game.gfxMode = true;
_game.clockEnabled = true;
_game.lineUserInput = 22;
// We run AGIMOUSE always as a side effect
//if (getFeatures() & GF_AGIMOUSE)
debug(1, "Using AGI Mouse 1.0 protocol");
if (getFeatures() & GF_AGIPAL)
debug(1, "Running AGIPAL game");
debug(0, "Running AGI script.\n");
setflag(fEnteredCli, false);
setflag(fSaidAcceptedInput, false);
_game.vars[vWordNotFound] = 0;
_game.vars[vKey] = 0;
debugC(2, kDebugLevelMain, "Entering main loop");
bool firstLoop = !getflag(fRestartGame); // Do not restore on game restart
do {
if (!mainCycle())
continue;
if (getvar(vTimeDelay) == 0 || (1 + _clockCount) % getvar(vTimeDelay) == 0) {
if (!_game.hasPrompt && _game.inputMode == INPUT_NORMAL) {
writePrompt();
_game.hasPrompt = 1;
} else if (_game.hasPrompt && _game.inputMode == INPUT_NONE) {
writePrompt();
_game.hasPrompt = 0;
}
interpretCycle();
// Check if the user has asked to load a game from the command line
// or the launcher
if (firstLoop) {
checkQuickLoad();
firstLoop = false;
}
setflag(fEnteredCli, false);
setflag(fSaidAcceptedInput, false);
_game.vars[vWordNotFound] = 0;
_game.vars[vKey] = 0;
}
if (shouldPerformAutoSave(_lastSaveTime)) {
saveGame(getSavegameFilename(0), "Autosave");
}
} while (!(shouldQuit() || _restartGame));
_sound->stopSound();
return ec;
}
int AgiEngine::runGame() {
int ec = errOK;
// Execute the game
do {
debugC(2, kDebugLevelMain, "game loop");
debugC(2, kDebugLevelMain, "game version = 0x%x", getVersion());
if (agiInit() != errOK)
break;
if (_restartGame) {
setflag(fRestartGame, true);
setvar(vTimeDelay, 2); // "normal" speed
_restartGame = false;
}
// Set computer type (v20 i.e. vComputer) and sound type
switch (getPlatform()) {
case Common::kPlatformAtariST:
setvar(vComputer, kAgiComputerAtariST);
setvar(vSoundgen, kAgiSoundPC);
break;
case Common::kPlatformAmiga:
if (getFeatures() & GF_OLDAMIGAV20)
setvar(vComputer, kAgiComputerAmigaOld);
else
setvar(vComputer, kAgiComputerAmiga);
setvar(vSoundgen, kAgiSoundTandy);
break;
case Common::kPlatformApple2GS:
setvar(vComputer, kAgiComputerApple2GS);
if (getFeatures() & GF_2GSOLDSOUND)
setvar(vSoundgen, kAgiSound2GSOld);
else
setvar(vSoundgen, kAgiSoundTandy);
break;
case Common::kPlatformDOS:
default:
setvar(vComputer, kAgiComputerPC);
setvar(vSoundgen, kAgiSoundPC);
break;
}
// Set monitor type (v26 i.e. vMonitor)
switch (_renderMode) {
case Common::kRenderCGA:
setvar(vMonitor, kAgiMonitorCga);
break;
case Common::kRenderHercG:
case Common::kRenderHercA:
setvar(vMonitor, kAgiMonitorHercules);
break;
// Don't know if Amiga AGI games use a different value than kAgiMonitorEga
// for vMonitor so I just use kAgiMonitorEga for them (As was done before too).
case Common::kRenderAmiga:
case Common::kRenderDefault:
case Common::kRenderEGA:
default:
setvar(vMonitor, kAgiMonitorEga);
break;
}
setvar(vFreePages, 180); // Set amount of free memory to realistic value
setvar(vMaxInputChars, 38);
_game.inputMode = INPUT_NONE;
_game.inputEnabled = false;
_game.hasPrompt = 0;
_game.state = STATE_RUNNING;
ec = playGame();
_game.state = STATE_LOADED;
agiDeinit();
} while (_restartGame);
delete _menu;
_menu = NULL;
releaseImageStack();
return ec;
}
} // End of namespace Agi