mirror of
https://github.com/libretro/scummvm.git
synced 2024-12-22 18:02:05 +00:00
459 lines
11 KiB
C++
459 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 &= ~(ANIMATED | DRAWN);
|
|
v->flags |= UPDATE;
|
|
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() {
|
|
unsigned int key, kascii;
|
|
VtEntry *v = &_game.viewTable[0];
|
|
|
|
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 || true) {
|
|
_game.vars[28] = _mouse.x / 2;
|
|
_game.vars[29] = _mouse.y;
|
|
}
|
|
if (key == KEY_PRIORITY) {
|
|
_sprites->eraseBoth();
|
|
_debug.priority = !_debug.priority;
|
|
_picture->showPic();
|
|
_sprites->blitBoth();
|
|
_sprites->commitBoth();
|
|
key = 0;
|
|
}
|
|
|
|
if (key == KEY_STATUSLN) {
|
|
_debug.statusline = !_debug.statusline;
|
|
writeStatus();
|
|
key = 0;
|
|
}
|
|
|
|
// Click-to-walk mouse interface
|
|
if (_game.playerControl && v->flags & ADJ_EGO_XY) {
|
|
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);
|
|
|
|
process_key:
|
|
|
|
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;
|
|
goto process_key;
|
|
}
|
|
|
|
// 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;
|
|
}
|
|
_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 || true)
|
|
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);
|
|
_game.lastController = 0;
|
|
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::kPlatformPC:
|
|
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
|